Skip to content

Commit fd17510

Browse files
authored
refactor: DH-14692 Stop exporting client from support logs (#2368)
Cherry-pick #2279 - Move `exportLogs` and `logInit` to the `log` package so they could be reused in Enterprise - Remove `@deephaven/redux` and `@deephaven/jsapi-shim` dependencies from `LogExport.ts` - Serialize Maps in redux data - Unit tests for `getReduxDataString`
1 parent 52a4728 commit fd17510

File tree

8 files changed

+164
-43
lines changed

8 files changed

+164
-43
lines changed

package-lock.json

+4-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/code-studio/src/index.tsx

+5-2
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@ import { Provider } from 'react-redux';
55
import { LoadingOverlay, preloadTheme } from '@deephaven/components';
66
import { ApiBootstrap } from '@deephaven/jsapi-bootstrap';
77
import { store } from '@deephaven/redux';
8-
import logInit from './log/LogInit';
8+
import { logInit } from '@deephaven/log';
99

10-
logInit();
10+
logInit(
11+
parseInt(import.meta.env.VITE_LOG_LEVEL ?? '', 10),
12+
import.meta.env.VITE_ENABLE_LOG_PROXY === 'true'
13+
);
1114

1215
preloadTheme();
1316

packages/code-studio/src/settings/SettingsMenu.tsx

+12-6
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,18 @@ import {
1818
Logo,
1919
Tooltip,
2020
} from '@deephaven/components';
21-
import { ServerConfigValues, User } from '@deephaven/redux';
21+
import { ServerConfigValues, User, store } from '@deephaven/redux';
2222
import {
2323
BROADCAST_CHANNEL_NAME,
2424
BROADCAST_LOGOUT_MESSAGE,
2525
makeMessage,
2626
} from '@deephaven/jsapi-utils';
2727
import { PluginModuleMap } from '@deephaven/plugin';
28+
import { exportLogs, logHistory } from '@deephaven/log';
2829
import FormattingSectionContent from './FormattingSectionContent';
2930
import LegalNotice from './LegalNotice';
3031
import SettingsMenuSection from './SettingsMenuSection';
3132
import ShortcutSectionContent from './ShortcutsSectionContent';
32-
import { exportLogs } from '../log/LogExport';
3333
import './SettingsMenu.scss';
3434
import ColumnSpecificSectionContent from './ColumnSpecificSectionContent';
3535
import {
@@ -134,10 +134,16 @@ export class SettingsMenu extends Component<
134134
handleExportSupportLogs(): void {
135135
const { serverConfigValues, pluginData } = this.props;
136136
const pluginInfo = getFormattedPluginInfo(pluginData);
137-
exportLogs(undefined, {
138-
...Object.fromEntries(serverConfigValues),
139-
pluginInfo,
140-
});
137+
exportLogs(
138+
logHistory,
139+
{
140+
uiVersion: import.meta.env.npm_package_version,
141+
userAgent: navigator.userAgent,
142+
...Object.fromEntries(serverConfigValues),
143+
pluginInfo,
144+
},
145+
store.getState()
146+
);
141147
}
142148

143149
render(): ReactElement {

packages/log/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@
2121
"build:babel": "babel ./src --out-dir ./dist --extensions \".ts,.tsx,.js,.jsx\" --source-maps --root-mode upward"
2222
},
2323
"dependencies": {
24-
"event-target-shim": "^6.0.2"
24+
"event-target-shim": "^6.0.2",
25+
"jszip": "^3.10.1"
2526
},
2627
"files": [
2728
"dist"

packages/log/src/LogExport.test.ts

+90
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { getReduxDataString } from './LogExport';
2+
3+
describe('getReduxDataString', () => {
4+
it('should return a JSON string of the redux data', () => {
5+
const reduxData = {
6+
key1: 'value1',
7+
key2: 2,
8+
key3: true,
9+
};
10+
const result = getReduxDataString(reduxData);
11+
const expected = JSON.stringify(reduxData, null, 2);
12+
expect(result).toBe(expected);
13+
});
14+
15+
it('should handle circular references', () => {
16+
const reduxData: Record<string, unknown> = {
17+
key1: 'value1',
18+
};
19+
reduxData.key2 = reduxData;
20+
const result = getReduxDataString(reduxData);
21+
const expected = JSON.stringify(
22+
{
23+
key1: 'value1',
24+
key2: 'Circular ref to root',
25+
},
26+
null,
27+
2
28+
);
29+
expect(result).toBe(expected);
30+
});
31+
32+
it('should handle BigInt values', () => {
33+
const reduxData = {
34+
key1: BigInt('12345678901234567890'),
35+
};
36+
const result = getReduxDataString(reduxData);
37+
const expected = JSON.stringify(
38+
{
39+
key1: '12345678901234567890',
40+
},
41+
null,
42+
2
43+
);
44+
expect(result).toBe(expected);
45+
});
46+
47+
it('should apply blacklist paths', () => {
48+
const reduxData = {
49+
key1: 'should be blacklisted',
50+
key2: {
51+
'key2.1': 'should also be blacklisted',
52+
},
53+
key3: 'value',
54+
};
55+
const result = getReduxDataString(reduxData, [
56+
['key1'],
57+
['key2', 'key2.1'],
58+
]);
59+
const expected = JSON.stringify(
60+
{
61+
key2: {},
62+
key3: 'value',
63+
},
64+
null,
65+
2
66+
);
67+
expect(result).toBe(expected);
68+
});
69+
70+
it('should stringify Maps', () => {
71+
const reduxData = {
72+
key1: new Map([
73+
['key1.1', 'value1.1'],
74+
['key1.2', 'value1.2'],
75+
]),
76+
};
77+
const result = getReduxDataString(reduxData);
78+
const expected = JSON.stringify(
79+
{
80+
key1: [
81+
['key1.1', 'value1.1'],
82+
['key1.2', 'value1.2'],
83+
],
84+
},
85+
null,
86+
2
87+
);
88+
expect(result).toBe(expected);
89+
});
90+
});

packages/code-studio/src/log/LogExport.ts packages/log/src/LogExport.ts

+40-28
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
/* eslint-disable import/prefer-default-export */
21
import JSZip from 'jszip';
3-
import dh from '@deephaven/jsapi-shim';
4-
import { store } from '@deephaven/redux';
5-
import { logHistory } from './LogInit';
6-
7-
const FILENAME_DATE_FORMAT = 'yyyy-MM-dd-HHmmss';
2+
import type LogHistory from './LogHistory';
83

94
// List of objects to blacklist
105
// '' represents the root object
@@ -41,6 +36,10 @@ function stringifyReplacer(blacklist: string[][]) {
4136
}
4237
}
4338

39+
if (value instanceof Map) {
40+
return Array.from(value.entries());
41+
}
42+
4443
// not in blacklist, return value
4544
return value;
4645
};
@@ -66,7 +65,7 @@ function makeSafeToStringify(
6665
blacklist: string[][],
6766
path = 'root',
6867
potentiallyCircularValues: Map<Record<string, unknown>, string> = new Map([
69-
[obj, ''],
68+
[obj, 'root'],
7069
])
7170
): Record<string, unknown> {
7271
const output: Record<string, unknown> = {};
@@ -104,48 +103,61 @@ function makeSafeToStringify(
104103
return output;
105104
}
106105

107-
function getReduxDataString(blacklist: string[][]): string {
108-
const reduxData = store.getState();
106+
export function getReduxDataString(
107+
reduxData: Record<string, unknown>,
108+
blacklist: string[][] = []
109+
): string {
109110
return JSON.stringify(
110111
makeSafeToStringify(reduxData, blacklist),
111112
stringifyReplacer(blacklist),
112113
2 // Indent w/ 2 spaces
113114
);
114115
}
115116

116-
function getMetadata(
117-
blacklist: string[][],
118-
meta?: Record<string, unknown>
119-
): string {
120-
const metadata = {
121-
uiVersion: import.meta.env.npm_package_version,
122-
userAgent: navigator.userAgent,
123-
...meta,
124-
};
117+
function getFormattedMetadata(metadata?: Record<string, unknown>): string {
118+
return JSON.stringify(metadata, null, 2);
119+
}
120+
121+
/** Format a date to a string that can be used as a file name
122+
* @param date Date to format
123+
* @returns A string formatted as YYYY-MM-DD-HHMMSS
124+
*/
125+
function formatDate(date: Date): string {
126+
const year = date.getFullYear();
127+
const month = String(date.getMonth() + 1).padStart(2, '0');
128+
const day = String(date.getDate()).padStart(2, '0');
129+
const h = String(date.getHours()).padStart(2, '0');
130+
const m = String(date.getMinutes()).padStart(2, '0');
131+
const s = String(date.getSeconds()).padStart(2, '0');
125132

126-
return JSON.stringify(metadata, stringifyReplacer(blacklist), 2);
133+
return `${year}-${month}-${day}-${h}${m}${s}`;
127134
}
128135

129136
/**
130137
* Export support logs with the given name.
131-
* @param fileNamePrefix The zip file name without the .zip extension. Ex: test will be saved as test.zip
138+
* @param logHistory Log history to include in the console.txt file
132139
* @param metadata Additional metadata to include in the metadata.json file
133-
* @param blacklist List of JSON paths to blacklist. A JSON path is a list representing the path to that value (e.g. client.data would be `['client', 'data']`)
140+
* @param reduxData Redux data to include in the redux.json file
141+
* @param blacklist List of JSON paths to blacklist in redux data. A JSON path is a list representing the path to that value (e.g. client.data would be `['client', 'data']`)
142+
* @param fileNamePrefix The zip file name without the .zip extension. Ex: test will be saved as test.zip
134143
* @returns A promise that resolves successfully if the log archive is created and downloaded successfully, rejected if there's an error
135144
*/
136145
export async function exportLogs(
137-
fileNamePrefix = `${dh.i18n.DateTimeFormat.format(
138-
FILENAME_DATE_FORMAT,
139-
new Date()
140-
)}_support_logs`,
146+
logHistory: LogHistory,
141147
metadata?: Record<string, unknown>,
142-
blacklist: string[][] = DEFAULT_PATH_BLACKLIST
148+
reduxData?: Record<string, unknown>,
149+
blacklist: string[][] = DEFAULT_PATH_BLACKLIST,
150+
fileNamePrefix = `${formatDate(new Date())}_support_logs`
143151
): Promise<void> {
144152
const zip = new JSZip();
145153
const folder = zip.folder(fileNamePrefix) as JSZip;
146154
folder.file('console.txt', logHistory.getFormattedHistory());
147-
folder.file('redux.json', getReduxDataString(blacklist));
148-
folder.file('metadata.json', getMetadata(blacklist, metadata));
155+
if (metadata != null) {
156+
folder.file('metadata.json', getFormattedMetadata(metadata));
157+
}
158+
if (reduxData != null) {
159+
folder.file('redux.json', getReduxDataString(reduxData, blacklist));
160+
}
149161

150162
const blob = await zip.generateAsync({ type: 'blob' });
151163
const link = document.createElement('a');

packages/code-studio/src/log/LogInit.ts packages/log/src/LogInit.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { LogProxy, LogHistory, Logger, Log } from '@deephaven/log';
1+
import Log from './Log';
2+
import type Logger from './Logger';
3+
import LogHistory from './LogHistory';
4+
import LogProxy from './LogProxy';
25

36
declare global {
47
interface Window {
@@ -11,10 +14,10 @@ declare global {
1114
export const logProxy = new LogProxy();
1215
export const logHistory = new LogHistory(logProxy);
1316

14-
export default function logInit(): void {
15-
Log.setLogLevel(parseInt(import.meta.env.VITE_LOG_LEVEL ?? '', 10));
17+
export function logInit(logLevel = 2, enableProxy = true): void {
18+
Log.setLogLevel(logLevel);
1619

17-
if (import.meta.env.VITE_ENABLE_LOG_PROXY === 'true') {
20+
if (enableProxy) {
1821
logProxy.enable();
1922
logHistory.enable();
2023
}
@@ -26,3 +29,5 @@ export default function logInit(): void {
2629
window.DHLogHistory = logHistory;
2730
}
2831
}
32+
33+
export default logInit;

packages/log/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export { default as Logger } from './Logger';
66
export { default as LogHistory } from './LogHistory';
77
export { default as LogProxy } from './LogProxy';
88
export { default as LoggerLevel } from './LoggerLevel';
9+
export * from './LogExport';
10+
export * from './LogInit';

0 commit comments

Comments
 (0)