@atlassian/clientside-extensions-debug 2.4.0-69e0f9e-kugbhrvw

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.
Files changed (89) hide show
  1. package/LICENSE.txt +30 -0
  2. package/README.md +7 -0
  3. package/dist/cjs/debug-state-env.test.js +63 -0
  4. package/dist/cjs/debug-state-env.test.js.map +1 -0
  5. package/dist/cjs/debug-state.js +155 -0
  6. package/dist/cjs/debug-state.js.map +1 -0
  7. package/dist/cjs/debug-state.test.js +66 -0
  8. package/dist/cjs/debug-state.test.js.map +1 -0
  9. package/dist/cjs/debug-subjects.js +42 -0
  10. package/dist/cjs/debug-subjects.js.map +1 -0
  11. package/dist/cjs/debug-subjects.test.js +35 -0
  12. package/dist/cjs/debug-subjects.test.js.map +1 -0
  13. package/dist/cjs/debug.js +23 -0
  14. package/dist/cjs/debug.js.map +1 -0
  15. package/dist/cjs/debug.test.js +75 -0
  16. package/dist/cjs/debug.test.js.map +1 -0
  17. package/dist/cjs/index.js +23 -0
  18. package/dist/cjs/index.js.map +1 -0
  19. package/dist/cjs/logger/console.js +64 -0
  20. package/dist/cjs/logger/console.js.map +1 -0
  21. package/dist/cjs/logger/console.test.js +200 -0
  22. package/dist/cjs/logger/console.test.js.map +1 -0
  23. package/dist/cjs/logger/default-logger.js +48 -0
  24. package/dist/cjs/logger/default-logger.js.map +1 -0
  25. package/dist/cjs/logger/logger.js +16 -0
  26. package/dist/cjs/logger/logger.js.map +1 -0
  27. package/dist/esm/debug-state-env.test.js +42 -0
  28. package/dist/esm/debug-state-env.test.js.map +1 -0
  29. package/dist/esm/debug-state.js +152 -0
  30. package/dist/esm/debug-state.js.map +1 -0
  31. package/dist/esm/debug-state.test.js +63 -0
  32. package/dist/esm/debug-state.test.js.map +1 -0
  33. package/dist/esm/debug-subjects.js +39 -0
  34. package/dist/esm/debug-subjects.js.map +1 -0
  35. package/dist/esm/debug-subjects.test.js +33 -0
  36. package/dist/esm/debug-subjects.test.js.map +1 -0
  37. package/dist/esm/debug.js +20 -0
  38. package/dist/esm/debug.js.map +1 -0
  39. package/dist/esm/debug.test.js +73 -0
  40. package/dist/esm/debug.test.js.map +1 -0
  41. package/dist/esm/index.js +8 -0
  42. package/dist/esm/index.js.map +1 -0
  43. package/dist/esm/logger/console.js +61 -0
  44. package/dist/esm/logger/console.js.map +1 -0
  45. package/dist/esm/logger/console.test.js +198 -0
  46. package/dist/esm/logger/console.test.js.map +1 -0
  47. package/dist/esm/logger/default-logger.js +46 -0
  48. package/dist/esm/logger/default-logger.js.map +1 -0
  49. package/dist/esm/logger/logger.js +14 -0
  50. package/dist/esm/logger/logger.js.map +1 -0
  51. package/dist/types/debug-state-env.test.d.ts +2 -0
  52. package/dist/types/debug-state-env.test.d.ts.map +1 -0
  53. package/dist/types/debug-state.d.ts +41 -0
  54. package/dist/types/debug-state.d.ts.map +1 -0
  55. package/dist/types/debug-state.test.d.ts +2 -0
  56. package/dist/types/debug-state.test.d.ts.map +1 -0
  57. package/dist/types/debug-subjects.d.ts +25 -0
  58. package/dist/types/debug-subjects.d.ts.map +1 -0
  59. package/dist/types/debug-subjects.test.d.ts +2 -0
  60. package/dist/types/debug-subjects.test.d.ts.map +1 -0
  61. package/dist/types/debug.d.ts +3 -0
  62. package/dist/types/debug.d.ts.map +1 -0
  63. package/dist/types/debug.test.d.ts +2 -0
  64. package/dist/types/debug.test.d.ts.map +1 -0
  65. package/dist/types/index.d.ts +9 -0
  66. package/dist/types/index.d.ts.map +1 -0
  67. package/dist/types/logger/console.d.ts +3 -0
  68. package/dist/types/logger/console.d.ts.map +1 -0
  69. package/dist/types/logger/console.test.d.ts +2 -0
  70. package/dist/types/logger/console.test.d.ts.map +1 -0
  71. package/dist/types/logger/default-logger.d.ts +25 -0
  72. package/dist/types/logger/default-logger.d.ts.map +1 -0
  73. package/dist/types/logger/logger.d.ts +20 -0
  74. package/dist/types/logger/logger.d.ts.map +1 -0
  75. package/lib/__mocks__/debug-state.mock.ts +7 -0
  76. package/lib/__mocks__/debug-subjects.mock.ts +4 -0
  77. package/lib/debug-state-env.test.ts +53 -0
  78. package/lib/debug-state.test.ts +87 -0
  79. package/lib/debug-state.ts +198 -0
  80. package/lib/debug-subjects.test.ts +40 -0
  81. package/lib/debug-subjects.ts +78 -0
  82. package/lib/debug.test.ts +95 -0
  83. package/lib/debug.ts +24 -0
  84. package/lib/index.ts +23 -0
  85. package/lib/logger/console.test.ts +240 -0
  86. package/lib/logger/console.ts +73 -0
  87. package/lib/logger/default-logger.ts +67 -0
  88. package/lib/logger/logger.ts +26 -0
  89. package/package.json +80 -0
@@ -0,0 +1,40 @@
1
+ import { Subject } from '@atlassian/clientside-extensions-base';
2
+ import { DebugSubjects, observeDebugSubject, registerDebugSubject } from './debug-subjects';
3
+
4
+ describe('debug-subject', () => {
5
+ const debugFactory = (): Subject<string> => new Subject<string>();
6
+
7
+ describe('registerDebugSubject', () => {
8
+ it('should create an register a subject under a given namespace', () => {
9
+ const subject = registerDebugSubject(DebugSubjects.Logger, debugFactory);
10
+ expect(subject).toBeInstanceOf(Subject);
11
+ });
12
+
13
+ it('should not create a new instance for a previously registered key', () => {
14
+ const subject = registerDebugSubject(DebugSubjects.State, debugFactory);
15
+ const sameSubject = registerDebugSubject(DebugSubjects.State, debugFactory);
16
+ expect(subject).toBe(sameSubject);
17
+ });
18
+ });
19
+
20
+ describe('observeDebugSubject', () => {
21
+ it('should throw if the subject does not exist yet', () => {
22
+ // @ts-expect-error We are using a wrong subject key here
23
+ const unregisteredSubject = () => observeDebugSubject('Foo', () => {});
24
+
25
+ expect(unregisteredSubject).toThrow(`No subject registered for key: "Foo"`);
26
+ });
27
+
28
+ it('should subscribe to a previously registered subject', () => {
29
+ const SUBJECT = DebugSubjects.State;
30
+ const TEST_PAYLOAD = 'some payload';
31
+
32
+ const observer = jest.fn();
33
+ const subject = registerDebugSubject(SUBJECT, debugFactory);
34
+ observeDebugSubject(SUBJECT, observer);
35
+
36
+ subject.notify(TEST_PAYLOAD);
37
+ expect(observer).toBeCalledWith(TEST_PAYLOAD);
38
+ });
39
+ });
40
+ });
@@ -0,0 +1,78 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import type { Observer, Subject } from '@atlassian/clientside-extensions-base';
3
+
4
+ type ExposedClientExtensionSubjects = {
5
+ __initialized: PropertyDescriptor;
6
+ subjects: PropertyDescriptor;
7
+ };
8
+
9
+ export const enum DebugSubjects {
10
+ Logger = 'logger',
11
+ State = 'state',
12
+ }
13
+
14
+ type DebugSubjectsMap<Subject> = {
15
+ [key in DebugSubjects]: Subject;
16
+ };
17
+
18
+ declare global {
19
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
20
+ interface Window {
21
+ // Due to historical name changes the "p" still represents the old "plugin" nomenclature
22
+ ____c_p_s: ExposedClientExtensionSubjects;
23
+ }
24
+ }
25
+
26
+ const defineSubjectGlobal = () => {
27
+ // initialize as boolean
28
+ const subjectGlobal = Object.create(null);
29
+
30
+ const properties: ExposedClientExtensionSubjects = {
31
+ __initialized: { value: true, writable: false },
32
+ subjects: {
33
+ value: subjectGlobal,
34
+ writable: false,
35
+ },
36
+ };
37
+
38
+ Object.defineProperties(subjectGlobal, properties);
39
+
40
+ window.____c_p_s = subjectGlobal;
41
+ };
42
+
43
+ if (!('____c_p_s' in window) || !window.____c_p_s.__initialized) {
44
+ defineSubjectGlobal();
45
+ }
46
+
47
+ type UnpackPayloadType<SubjectType> = SubjectType extends Subject<infer U> ? U : never;
48
+
49
+ /**
50
+ * Registers a debug subject as singleton
51
+ *
52
+ * @param key
53
+ * @param subjectFactory
54
+ */
55
+ export const registerDebugSubject = <SubjectType extends Subject<PayloadType>, PayloadType = UnpackPayloadType<SubjectType>>(
56
+ key: DebugSubjects,
57
+ subjectFactory: () => SubjectType,
58
+ ): SubjectType => {
59
+ const debugSubjects = window.____c_p_s.subjects as DebugSubjectsMap<SubjectType>;
60
+
61
+ // good old singletons - ensure we do not register the same subject twice.
62
+ // we also can't fail as multiple instances might try to access the same, so instead we return the previously registered one.
63
+ if (!(key in debugSubjects)) {
64
+ debugSubjects[key] = subjectFactory();
65
+ }
66
+
67
+ return debugSubjects[key];
68
+ };
69
+
70
+ export const observeDebugSubject = <PayloadType>(key: DebugSubjects, observer: Observer<PayloadType>) => {
71
+ const debugSubjects = window.____c_p_s.subjects as DebugSubjectsMap<Subject<PayloadType>>;
72
+
73
+ if (!(key in debugSubjects)) {
74
+ throw new Error(`No subject registered for key: "${key}"`);
75
+ }
76
+
77
+ return debugSubjects[key].subscribe(observer);
78
+ };
@@ -0,0 +1,95 @@
1
+ import { mocked } from 'ts-jest/utils';
2
+ import { getLogLevel, isDebugEnabled, isLoggingEnabled } from './debug-state';
3
+ import { onDebug } from './debug';
4
+ import { LogLevel } from './logger/logger';
5
+ import { observeLogger } from './logger/default-logger';
6
+
7
+ jest.mock('./debug-state');
8
+
9
+ describe('onDebug util', () => {
10
+ const orgConsole = global.console;
11
+
12
+ beforeEach(() => {
13
+ mocked(isDebugEnabled).mockReturnValue(false);
14
+ mocked(isLoggingEnabled).mockReturnValue(false);
15
+ mocked(getLogLevel).mockReturnValue(LogLevel.debug);
16
+
17
+ // Silent down console
18
+ global.console = {
19
+ ...orgConsole,
20
+ error: jest.fn(),
21
+ log: jest.fn(),
22
+ groupCollapsed: jest.fn(),
23
+ };
24
+ });
25
+
26
+ afterEach(() => {
27
+ mocked(isDebugEnabled).mockClear();
28
+ mocked(isLoggingEnabled).mockClear();
29
+ mocked(getLogLevel).mockClear();
30
+
31
+ global.console = orgConsole;
32
+ });
33
+
34
+ it('should only execute the given callback when debug and logging is enabled', () => {
35
+ const debugCallbackSpy = jest.fn();
36
+
37
+ // disable
38
+ mocked(isDebugEnabled).mockReturnValue(false);
39
+ mocked(isLoggingEnabled).mockReturnValue(false);
40
+
41
+ onDebug(debugCallbackSpy);
42
+
43
+ expect(debugCallbackSpy).not.toHaveBeenCalled();
44
+
45
+ // enable
46
+ mocked(isDebugEnabled).mockReturnValue(true);
47
+ mocked(isLoggingEnabled).mockReturnValue(true);
48
+
49
+ onDebug(debugCallbackSpy);
50
+
51
+ expect(debugCallbackSpy).toHaveBeenCalled();
52
+ });
53
+
54
+ it('should not notify the observer when logging is disabled', () => {
55
+ const payload = {
56
+ message: 'foo',
57
+ level: LogLevel.error,
58
+ };
59
+ const loggerCallback = jest.fn(() => payload);
60
+ const loggerObserver = jest.fn();
61
+
62
+ // need to enable debug in order to run the debugCallback
63
+ mocked(isDebugEnabled).mockReturnValue(true);
64
+ // disable
65
+ mocked(isLoggingEnabled).mockReturnValue(false);
66
+
67
+ observeLogger(loggerObserver);
68
+ onDebug(loggerCallback);
69
+
70
+ expect(loggerCallback).not.toHaveBeenCalled();
71
+ expect(loggerObserver).not.toHaveBeenCalled();
72
+
73
+ // enable
74
+ mocked(isLoggingEnabled).mockReturnValue(true);
75
+ onDebug(loggerCallback);
76
+
77
+ expect(loggerCallback).toHaveBeenCalledTimes(1);
78
+ expect(loggerObserver).toHaveBeenCalledWith(payload);
79
+ });
80
+
81
+ it('should not throw an error when logger callback fails', () => {
82
+ mocked(isDebugEnabled).mockReturnValue(true);
83
+ mocked(isLoggingEnabled).mockReturnValue(true);
84
+
85
+ const callOnDebug = () => {
86
+ const loggerCallback = () => {
87
+ throw new Error('ups');
88
+ };
89
+
90
+ onDebug(loggerCallback);
91
+ };
92
+
93
+ expect(callOnDebug).not.toThrow();
94
+ });
95
+ });
package/lib/debug.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { isDebugEnabled, isLoggingEnabled } from './debug-state';
2
+
3
+ import type { LoggerCallback, LoggerPayload } from './logger/logger';
4
+ import { _loggerSubject, LogLevel } from './logger/logger';
5
+
6
+ // eslint-disable-next-line import/prefer-default-export
7
+ export const onDebug = (callback: LoggerCallback) => {
8
+ // Return early if both debug and logging is disabled
9
+ if (!isDebugEnabled() || !isLoggingEnabled()) {
10
+ return;
11
+ }
12
+
13
+ let payload: LoggerPayload | undefined;
14
+
15
+ try {
16
+ payload = callback(LogLevel);
17
+ } catch (e) {
18
+ // eslint-disable-next-line no-empty
19
+ }
20
+
21
+ if (payload) {
22
+ _loggerSubject.notify(payload);
23
+ }
24
+ };
package/lib/index.ts ADDED
@@ -0,0 +1,23 @@
1
+ export { onDebug } from './debug';
2
+ export type { LoggerPayload } from './logger/logger';
3
+ export { LogLevel } from './logger/logger';
4
+
5
+ export {
6
+ /** @deprecated in 2.1.0 */ observeLogger,
7
+ /** @since in 2.1.0 */ setLogger,
8
+ _deregisterDefaultLogger,
9
+ } from './logger/default-logger';
10
+
11
+ export { /** @since in 2.1.0 */ consoleLogger } from './logger/console';
12
+
13
+ export {
14
+ isDebugEnabled,
15
+ isLoggingEnabled,
16
+ isDiscoveryEnabled,
17
+ isValidationEnabled,
18
+ setDebugEnabled,
19
+ setLoggingEnabled,
20
+ setDiscoveryEnabled,
21
+ setValidationEnabled,
22
+ observeStateChange,
23
+ } from './debug-state';
@@ -0,0 +1,240 @@
1
+ import { mocked } from 'ts-jest/utils';
2
+ import type { LoggerPayload } from './logger';
3
+ import { LogLevel } from './logger';
4
+ import { consoleLogger } from './console';
5
+ import { getLogLevel } from '../debug-state';
6
+
7
+ jest.mock('../debug-state');
8
+
9
+ describe('console logger', () => {
10
+ const orgConsole = global.console;
11
+
12
+ beforeEach(() => {
13
+ global.console = {
14
+ ...orgConsole,
15
+ log: jest.fn(),
16
+ error: jest.fn(),
17
+ debug: jest.fn(),
18
+ info: jest.fn(),
19
+ warn: jest.fn(),
20
+ groupCollapsed: jest.fn(),
21
+ };
22
+ });
23
+
24
+ afterEach(() => {
25
+ mocked(getLogLevel).mockClear();
26
+
27
+ global.console = orgConsole;
28
+ });
29
+
30
+ const getEventPayload = (level: LogLevel): LoggerPayload => ({
31
+ level,
32
+ message: 'Hello!',
33
+ });
34
+
35
+ describe('"DEBUG" level', () => {
36
+ it.each([
37
+ // prettier-ignore-start
38
+ [LogLevel.debug],
39
+ // prettier-ignore-end
40
+ ])('should display "DEBUG" log when log level is set to "%s"', (logLevel: LogLevel) => {
41
+ // given
42
+ mocked(getLogLevel).mockReturnValue(logLevel);
43
+ const event = getEventPayload(LogLevel.debug);
44
+
45
+ // when
46
+ consoleLogger(event);
47
+
48
+ // then
49
+ expect(global.console.debug).toHaveBeenCalledWith(expect.stringContaining('Hello!'));
50
+ expect(global.console.groupCollapsed).toHaveBeenCalled();
51
+ });
52
+
53
+ it.each([
54
+ // prettier-ignore-start
55
+ [LogLevel.error],
56
+ [LogLevel.warn],
57
+ [LogLevel.deprecation],
58
+ [LogLevel.info],
59
+ // prettier-ignore-end
60
+ ])('should not display "DEBUG" log when log level is set to "%s"', (logLevel: LogLevel) => {
61
+ // given
62
+ mocked(getLogLevel).mockReturnValue(logLevel);
63
+ const event = getEventPayload(LogLevel.debug);
64
+
65
+ // when
66
+ consoleLogger(event);
67
+
68
+ // then
69
+ expect(global.console.debug).not.toHaveBeenCalled();
70
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
71
+ });
72
+ });
73
+
74
+ describe('"ERROR" level', () => {
75
+ it.each([
76
+ // prettier-ignore-start
77
+ [LogLevel.debug],
78
+ [LogLevel.error],
79
+ // prettier-ignore-end
80
+ ])('should display "ERROR" log when log level is set to "%s"', (logLevel: LogLevel) => {
81
+ // given
82
+ mocked(getLogLevel).mockReturnValue(logLevel);
83
+ const event = getEventPayload(LogLevel.error);
84
+
85
+ // when
86
+ consoleLogger(event);
87
+
88
+ // then
89
+ expect(global.console.error).toHaveBeenCalledWith(expect.stringContaining('Hello!'));
90
+
91
+ if (logLevel === LogLevel.debug) {
92
+ // eslint-disable-next-line jest/no-conditional-expect
93
+ expect(global.console.groupCollapsed).toHaveBeenCalled();
94
+ } else {
95
+ // eslint-disable-next-line jest/no-conditional-expect
96
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
97
+ }
98
+ });
99
+
100
+ it.each([
101
+ // prettier-ignore-start
102
+ [LogLevel.warn],
103
+ [LogLevel.deprecation],
104
+ [LogLevel.info],
105
+ // prettier-ignore-end
106
+ ])('should not display "ERROR" log when log level is set to "%s"', (logLevel: LogLevel) => {
107
+ // given
108
+ mocked(getLogLevel).mockReturnValue(logLevel);
109
+ const event = getEventPayload(LogLevel.error);
110
+
111
+ // when
112
+ consoleLogger(event);
113
+
114
+ // then
115
+ expect(global.console.error).not.toHaveBeenCalled();
116
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
117
+ });
118
+ });
119
+
120
+ describe('"WARN" level', () => {
121
+ it.each([
122
+ // prettier-ignore-start
123
+ [LogLevel.debug],
124
+ [LogLevel.error],
125
+ [LogLevel.warn],
126
+ // prettier-ignore-end
127
+ ])('should display "WARN" log when log level is set to "%s"', (logLevel: LogLevel) => {
128
+ // given
129
+ mocked(getLogLevel).mockReturnValue(logLevel);
130
+ const event = getEventPayload(LogLevel.warn);
131
+
132
+ // when
133
+ consoleLogger(event);
134
+
135
+ // then
136
+ expect(global.console.warn).toHaveBeenCalledWith(expect.stringContaining('Hello!'));
137
+
138
+ if (logLevel === LogLevel.debug) {
139
+ // eslint-disable-next-line jest/no-conditional-expect
140
+ expect(global.console.groupCollapsed).toHaveBeenCalled();
141
+ } else {
142
+ // eslint-disable-next-line jest/no-conditional-expect
143
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
144
+ }
145
+ });
146
+
147
+ it.each([
148
+ // prettier-ignore-start
149
+ [LogLevel.deprecation],
150
+ [LogLevel.info],
151
+ // prettier-ignore-end
152
+ ])('should not display "WARN" log when log level is set to "%s"', (logLevel: LogLevel) => {
153
+ // given
154
+ mocked(getLogLevel).mockReturnValue(logLevel);
155
+ const event = getEventPayload(LogLevel.warn);
156
+
157
+ // when
158
+ consoleLogger(event);
159
+
160
+ // then
161
+ expect(global.console.warn).not.toHaveBeenCalled();
162
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
163
+ });
164
+ });
165
+
166
+ describe('"DEPRECATION" level', () => {
167
+ it.each([
168
+ // prettier-ignore-start
169
+ [LogLevel.debug],
170
+ [LogLevel.error],
171
+ [LogLevel.warn],
172
+ // prettier-ignore-end
173
+ ])('should display "DEPRECATION" log when log level is set to "%s"', (logLevel: LogLevel) => {
174
+ // given
175
+ mocked(getLogLevel).mockReturnValue(logLevel);
176
+ const event = getEventPayload(LogLevel.deprecation);
177
+
178
+ // when
179
+ consoleLogger(event);
180
+
181
+ // then
182
+ expect(global.console.warn).toHaveBeenCalledWith(expect.stringContaining('Hello!'));
183
+
184
+ if (logLevel === LogLevel.debug) {
185
+ // eslint-disable-next-line jest/no-conditional-expect
186
+ expect(global.console.groupCollapsed).toHaveBeenCalled();
187
+ } else {
188
+ // eslint-disable-next-line jest/no-conditional-expect
189
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
190
+ }
191
+ });
192
+
193
+ it.each([
194
+ // prettier-ignore-start
195
+ [LogLevel.info],
196
+ // prettier-ignore-end
197
+ ])('should not display "DEPRECATION" log when log level is set to "%s"', (logLevel: LogLevel) => {
198
+ // given
199
+ mocked(getLogLevel).mockReturnValue(logLevel);
200
+ const event = getEventPayload(LogLevel.deprecation);
201
+
202
+ // when
203
+ consoleLogger(event);
204
+
205
+ // then
206
+ expect(global.console.warn).not.toHaveBeenCalled();
207
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
208
+ });
209
+ });
210
+
211
+ describe('"INFO" level', () => {
212
+ it.each([
213
+ // prettier-ignore-start
214
+ [LogLevel.debug],
215
+ [LogLevel.error],
216
+ [LogLevel.warn],
217
+ [LogLevel.deprecation],
218
+ [LogLevel.info],
219
+ // prettier-ignore-end
220
+ ])('should display "INFO" log when log level is set to "%s"', (logLevel: LogLevel) => {
221
+ // given
222
+ mocked(getLogLevel).mockReturnValue(logLevel);
223
+ const event = getEventPayload(LogLevel.info);
224
+
225
+ // when
226
+ consoleLogger(event);
227
+
228
+ // then
229
+ expect(global.console.info).toHaveBeenCalledWith(expect.stringContaining('Hello!'));
230
+
231
+ if (logLevel === LogLevel.debug) {
232
+ // eslint-disable-next-line jest/no-conditional-expect
233
+ expect(global.console.groupCollapsed).toHaveBeenCalled();
234
+ } else {
235
+ // eslint-disable-next-line jest/no-conditional-expect
236
+ expect(global.console.groupCollapsed).not.toHaveBeenCalled();
237
+ }
238
+ });
239
+ });
240
+ });
@@ -0,0 +1,73 @@
1
+ import type { LoggerPayload } from './logger';
2
+ import { LogLevel } from './logger';
3
+ import { getLogLevel } from '../debug-state';
4
+
5
+ const getLoggingFromLevel = (level: LogLevel): keyof typeof console => {
6
+ switch (level) {
7
+ case LogLevel.error:
8
+ return 'error';
9
+ case LogLevel.debug:
10
+ return 'debug';
11
+ case LogLevel.info:
12
+ return 'info';
13
+ case LogLevel.warn:
14
+ case LogLevel.deprecation:
15
+ return 'warn';
16
+ default:
17
+ return 'debug';
18
+ }
19
+ };
20
+
21
+ // Naive implementation but we can't use bitwise operators :(
22
+ const debugLevels = Object.values(LogLevel);
23
+ const errorLevels = debugLevels.filter((level) => level !== LogLevel.debug);
24
+ const warnLevels = errorLevels.filter((level) => level !== LogLevel.error);
25
+ const deprecationLevels = warnLevels.filter((level) => level !== LogLevel.warn);
26
+ const infoLevels = deprecationLevels.filter((level) => level !== LogLevel.deprecation);
27
+
28
+ const levelsMap: { [key: string]: string[] } = {
29
+ [LogLevel.debug]: debugLevels,
30
+ [LogLevel.error]: errorLevels,
31
+ [LogLevel.warn]: warnLevels,
32
+ [LogLevel.deprecation]: deprecationLevels,
33
+ [LogLevel.info]: infoLevels,
34
+ };
35
+
36
+ function shouldDisplayLog(logLevel: LogLevel, eventLogLevel: LogLevel) {
37
+ if (logLevel === LogLevel.debug) {
38
+ return true;
39
+ }
40
+
41
+ const levels = levelsMap[logLevel];
42
+
43
+ return Array.isArray(levels) ? levels.includes(eventLogLevel) : false;
44
+ }
45
+
46
+ // eslint-disable-next-line import/prefer-default-export
47
+ export const consoleLogger = (payload: LoggerPayload) => {
48
+ const eventLogLevel = payload.level;
49
+ const logLevel = getLogLevel();
50
+
51
+ if (!shouldDisplayLog(logLevel, eventLogLevel)) {
52
+ return;
53
+ }
54
+
55
+ const consoleKey = getLoggingFromLevel(eventLogLevel);
56
+ const deprecationWarning =
57
+ payload.level === LogLevel.deprecation
58
+ ? `⚠️⚠️⚠️ DEPRECATION WARNING ⚠️⚠️⚠️
59
+ `
60
+ : '';
61
+
62
+ console[consoleKey](`${deprecationWarning}[Atlassian Client-side Extensions]: ${payload.message}`);
63
+
64
+ // Verbose logging
65
+ if (logLevel === LogLevel.debug) {
66
+ console.groupCollapsed(' <click here for more details about above message>');
67
+ console.log({
68
+ components: payload.components,
69
+ meta: payload.meta,
70
+ });
71
+ console.groupEnd();
72
+ }
73
+ };
@@ -0,0 +1,67 @@
1
+ /* eslint-disable no-underscore-dangle */
2
+ import type { Observer, Subscription } from '@atlassian/clientside-extensions-base';
3
+ import { consoleLogger } from './console';
4
+ import { DebugSubjects, observeDebugSubject } from '../debug-subjects';
5
+ import type { LoggerPayload } from './logger';
6
+ import { _loggerSubject } from './logger';
7
+
8
+ declare global {
9
+ interface Window {
10
+ // clientside extension default logging deregister
11
+ ____c_p_d_l_d?: Subscription;
12
+ }
13
+ }
14
+
15
+ /**
16
+ * We need to keep and expose the "observeLogger" function for a legacy reason.
17
+ * It should be removed in CSEv3
18
+ *
19
+ * @deprecated
20
+ */
21
+ export const observeLogger = (observer: Observer<LoggerPayload>): Subscription => {
22
+ return observeDebugSubject<LoggerPayload>(DebugSubjects.Logger, observer);
23
+ };
24
+
25
+ const registerConsoleLogger = (): Subscription => {
26
+ // Singleton workaround as this module may be executed more than once.
27
+ // We only ever want one global default logger, however.
28
+ if (window.____c_p_d_l_d !== undefined) {
29
+ return window.____c_p_d_l_d;
30
+ }
31
+
32
+ window.____c_p_d_l_d = observeLogger(consoleLogger);
33
+
34
+ return window.____c_p_d_l_d;
35
+ };
36
+
37
+ const deregisterLogger = (): void => {
38
+ if (window.____c_p_d_l_d === undefined) {
39
+ return;
40
+ }
41
+
42
+ window.____c_p_d_l_d.unsubscribe();
43
+ };
44
+
45
+ export const defaultLogger = registerConsoleLogger();
46
+
47
+ /**
48
+ * Added in CSE 2.1.0
49
+ *
50
+ * @param logger
51
+ * @public
52
+ */
53
+ export const setLogger = (logger: Observer<LoggerPayload>): void => {
54
+ if (typeof logger !== 'function') {
55
+ throw new Error(`Client-side Extension: The provided logger needs to be a function`);
56
+ }
57
+
58
+ deregisterLogger();
59
+ _loggerSubject.prune();
60
+
61
+ observeDebugSubject<LoggerPayload>(DebugSubjects.Logger, logger);
62
+ };
63
+
64
+ const deregisterDefaultLogger = () => defaultLogger.unsubscribe();
65
+
66
+ // @VisibleForTesting
67
+ export { deregisterDefaultLogger as _deregisterDefaultLogger };
@@ -0,0 +1,26 @@
1
+ import { ReplaySubject } from '@atlassian/clientside-extensions-base';
2
+ import { DebugSubjects, registerDebugSubject } from '../debug-subjects';
3
+
4
+ export enum LogLevel {
5
+ debug = 'DEBUG', // Logs all the things
6
+ error = 'ERROR',
7
+ warn = 'WARN',
8
+ deprecation = 'DEPRECATION',
9
+ info = 'INFO', // The lowest level
10
+ }
11
+
12
+ export type LoggerCallback = (levels: typeof LogLevel) => LoggerPayload;
13
+
14
+ export type LoggerPayload = {
15
+ level: LogLevel;
16
+ message: string;
17
+ components?: string | string[];
18
+ meta?: {
19
+ [key: string]: unknown;
20
+ };
21
+ };
22
+
23
+ const loggerSubject = registerDebugSubject(DebugSubjects.Logger, () => new ReplaySubject<LoggerPayload>(20));
24
+
25
+ // Exported for internal use only
26
+ export { loggerSubject as _loggerSubject };