@hpcc-js/comms 3.15.2 → 3.15.5

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@hpcc-js/comms",
3
- "version": "3.15.2",
3
+ "version": "3.15.5",
4
4
  "description": "hpcc-js - Communications",
5
5
  "type": "module",
6
6
  "main": "./dist/node/index.cjs",
@@ -74,13 +74,13 @@
74
74
  "wsdl-all": "npm-run-all --aggregate-output -c --serial build --parallel wsdl-*"
75
75
  },
76
76
  "dependencies": {
77
- "@hpcc-js/util": "^3.5.2",
78
- "@xmldom/xmldom": "0.9.8",
79
- "undici": "7.24.4"
77
+ "@hpcc-js/util": "^3.5.5",
78
+ "@xmldom/xmldom": "0.9.9",
79
+ "undici": "7.24.7"
80
80
  },
81
81
  "devDependencies": {
82
- "@hpcc-js/ddl-shim": "^3.3.2",
83
- "@hpcc-js/esbuild-plugins": "^1.8.5",
82
+ "@hpcc-js/ddl-shim": "^3.3.5",
83
+ "@hpcc-js/esbuild-plugins": "^1.8.7",
84
84
  "@kubernetes/client-node": "1.4.0",
85
85
  "@types/d3-request": "1.0.9",
86
86
  "@types/d3-time-format": "2.3.4",
@@ -90,7 +90,7 @@
90
90
  "d3-format": "^1",
91
91
  "d3-time-format": "^2",
92
92
  "data-uri-to-buffer": "6.0.2",
93
- "soap": "1.8.0",
93
+ "soap": "1.9.0",
94
94
  "typescript-formatter": "^7.2.2"
95
95
  },
96
96
  "repository": {
@@ -110,5 +110,5 @@
110
110
  "esp",
111
111
  "HPCC-Platform"
112
112
  ],
113
- "gitHead": "eab3a48367063ed368bdd82c05de6a822ee308aa"
113
+ "gitHead": "5eae9caec3cca9d223da280602ebed6a5e3eb7c8"
114
114
  }
@@ -685,7 +685,7 @@ export class Workunit extends StateObject<UWorkunitState, IWorkunitState> implem
685
685
  const scope = scopes[i];
686
686
  const props: { [key: string]: any } = {};
687
687
  const formattedProps: { [key: string]: any } = {};
688
- if (scope.Id && scope.Properties?.Property) {
688
+ if (scope.Properties?.Property) {
689
689
  for (const scopeProperty of scope.Properties.Property) {
690
690
  const measure = scopeProperty.Measure;
691
691
  const name = scopeProperty.Name;
@@ -829,7 +829,7 @@ export class Workunit extends StateObject<UWorkunitState, IWorkunitState> implem
829
829
  return this.WUDetails(request).then((response) => {
830
830
  const retVal: Scope[] = [];
831
831
 
832
- // Recreate Scope Hierarchy and dedup ---
832
+ // Recreate scope hierarchy and dedup ---
833
833
  const scopeMap: { [key: string]: Scope } = {};
834
834
  response.Scopes.Scope.forEach((rawScope) => {
835
835
  if (scopeMap[rawScope.ScopeName]) {
@@ -40,7 +40,7 @@ export const enum TargetAudience {
40
40
  Audit = "ADT"
41
41
  }
42
42
 
43
- //properties here are "LogType" values in Ws_logaccess.GetLogAccessInfo
43
+ // properties here are "LogType" values in Ws_logaccess.GetLogAccessInfo
44
44
  export interface LogLine {
45
45
  audience?: string;
46
46
  class?: string;
@@ -59,6 +59,142 @@ export interface GetLogsExResponse {
59
59
  total: number,
60
60
  }
61
61
 
62
+ const knownLogManagerTypes = new Set(["azureloganalyticscurl", "elasticstack", "grafanacurl"]);
63
+ const logColumnTypeValues = new Set(Object.values(WsLogaccess.LogColumnType));
64
+
65
+ function getLogCategory(searchField: string): WsLogaccess.LogAccessType {
66
+ switch (searchField) {
67
+ case WsLogaccess.LogColumnType.workunits:
68
+ case "hpcc.log.jobid":
69
+ return WsLogaccess.LogAccessType.ByJobID;
70
+ case WsLogaccess.LogColumnType.audience:
71
+ case "hpcc.log.audience":
72
+ return WsLogaccess.LogAccessType.ByTargetAudience;
73
+ case WsLogaccess.LogColumnType.class:
74
+ case "hpcc.log.class":
75
+ return WsLogaccess.LogAccessType.ByLogType;
76
+ case WsLogaccess.LogColumnType.components:
77
+ case "kubernetes.container.name":
78
+ return WsLogaccess.LogAccessType.ByComponent;
79
+ default:
80
+ return WsLogaccess.LogAccessType.ByFieldName;
81
+ }
82
+ }
83
+
84
+ // Explicit list of filter-bearing keys on GetLogsExRequest.
85
+ // Using an allowlist avoids accidentally treating control fields (StartDate, LogLineLimit, etc.)
86
+ // as log filters if the server ever returns a column whose name collides with them.
87
+ const FILTER_KEYS = ["audience", "class", "workunits", "message", "processid", "logid", "threadid", "timestamp", "components", "instance"] as const;
88
+
89
+ function buildFilters(request: GetLogsExRequest, columnMap: Record<string, string>): WsLogaccess.leftFilter[] {
90
+ const filters: WsLogaccess.leftFilter[] = [];
91
+ for (const key of FILTER_KEYS) {
92
+ const value = request[key];
93
+ if (value == null || value === "" || (Array.isArray(value) && value.length === 0)) {
94
+ continue;
95
+ }
96
+ if (!(key in columnMap)) continue;
97
+
98
+ const isKnownLogType = logColumnTypeValues.has(key as WsLogaccess.LogColumnType);
99
+ let searchField: string = isKnownLogType ? key : columnMap[key];
100
+ const logCategory = getLogCategory(searchField);
101
+ if (logCategory === WsLogaccess.LogAccessType.ByFieldName) {
102
+ searchField = columnMap[key];
103
+ }
104
+
105
+ const appendWildcard = logCategory === WsLogaccess.LogAccessType.ByComponent;
106
+ const rawValues: string[] = Array.isArray(value) ? value : [value as string];
107
+ for (const raw of rawValues) {
108
+ filters.push({
109
+ LogCategory: logCategory,
110
+ SearchField: searchField,
111
+ // append wildcard to end of search value to include ephemeral
112
+ // containers that aren't listed in ECL Watch's filters
113
+ SearchByValue: appendWildcard ? raw + "*" : raw
114
+ });
115
+ }
116
+ }
117
+ return filters;
118
+ }
119
+
120
+ // Builds a left-leaning OR chain from filters that share the same SearchField.
121
+ function buildOrGroup(group: WsLogaccess.leftFilter[]): WsLogaccess.BinaryLogFilter {
122
+ const root: WsLogaccess.BinaryLogFilter = { leftFilter: group[0] } as WsLogaccess.BinaryLogFilter;
123
+ let node = root;
124
+ for (let i = 1; i < group.length; i++) {
125
+ node.Operator = WsLogaccess.LogAccessFilterOperator.OR;
126
+ if (i === group.length - 1) {
127
+ node.rightFilter = group[i] as WsLogaccess.rightFilter;
128
+ } else {
129
+ node.rightBinaryFilter = { BinaryLogFilter: [{ leftFilter: group[i] } as WsLogaccess.BinaryLogFilter] };
130
+ node = node.rightBinaryFilter.BinaryLogFilter[0];
131
+ }
132
+ }
133
+ return root;
134
+ }
135
+
136
+ // Recursively AND-chains two or more groups into a BinaryLogFilter (used for nesting beyond depth 1).
137
+ function buildAndChain(groups: WsLogaccess.leftFilter[][]): WsLogaccess.BinaryLogFilter {
138
+ const [firstGroup, ...remainingGroups] = groups;
139
+ const node: WsLogaccess.BinaryLogFilter = {} as WsLogaccess.BinaryLogFilter;
140
+ if (firstGroup.length === 1) {
141
+ node.leftFilter = firstGroup[0];
142
+ } else {
143
+ node.leftBinaryFilter = { BinaryLogFilter: [buildOrGroup(firstGroup)] };
144
+ }
145
+ if (remainingGroups.length === 0) return node;
146
+ node.Operator = WsLogaccess.LogAccessFilterOperator.AND;
147
+ if (remainingGroups.length === 1) {
148
+ const [secondGroup] = remainingGroups;
149
+ if (secondGroup.length === 1) {
150
+ node.rightFilter = secondGroup[0] as WsLogaccess.rightFilter;
151
+ } else {
152
+ node.rightBinaryFilter = { BinaryLogFilter: [buildOrGroup(secondGroup)] };
153
+ }
154
+ } else {
155
+ node.rightBinaryFilter = { BinaryLogFilter: [buildAndChain(remainingGroups)] };
156
+ }
157
+ return node;
158
+ }
159
+
160
+ // Groups filters by SearchField, OR-chains each group, then AND-chains the groups together.
161
+ // This ensures e.g. [class_INF, class_ERR, audience_USR] always produces
162
+ // (class_INF OR class_ERR) AND audience_USR regardless of input order.
163
+ function buildFilterTree(filters: WsLogaccess.leftFilter[]): WsLogaccess.Filter {
164
+ const groupMap = new Map<string, WsLogaccess.leftFilter[]>();
165
+ for (const f of filters) {
166
+ const existing = groupMap.get(f.SearchField);
167
+ if (existing) existing.push(f); else groupMap.set(f.SearchField, [f]);
168
+ }
169
+ const groups = [...groupMap.values()];
170
+
171
+ if (groups.length === 0) {
172
+ return { leftFilter: { LogCategory: WsLogaccess.LogAccessType.All } as WsLogaccess.leftFilter };
173
+ }
174
+
175
+ const [firstGroup, ...remainingGroups] = groups;
176
+ const filter: WsLogaccess.Filter = {};
177
+ if (firstGroup.length === 1) {
178
+ filter.leftFilter = firstGroup[0];
179
+ } else {
180
+ filter.leftBinaryFilter = { BinaryLogFilter: [buildOrGroup(firstGroup)] };
181
+ }
182
+
183
+ if (remainingGroups.length === 0) return filter;
184
+ filter.Operator = WsLogaccess.LogAccessFilterOperator.AND;
185
+ if (remainingGroups.length === 1) {
186
+ const [secondGroup] = remainingGroups;
187
+ if (secondGroup.length === 1) {
188
+ filter.rightFilter = secondGroup[0] as WsLogaccess.rightFilter;
189
+ } else {
190
+ filter.rightBinaryFilter = { BinaryLogFilter: [buildOrGroup(secondGroup)] };
191
+ }
192
+ } else {
193
+ filter.rightBinaryFilter = { BinaryLogFilter: [buildAndChain(remainingGroups)] };
194
+ }
195
+ return filter;
196
+ }
197
+
62
198
  export class LogaccessService extends LogaccessServiceBase {
63
199
 
64
200
  protected _logAccessInfo: Promise<WsLogaccess.GetLogAccessInfoResponse>;
@@ -74,36 +210,31 @@ export class LogaccessService extends LogaccessServiceBase {
74
210
  return super.GetLogs(request);
75
211
  }
76
212
 
213
+ private convertLogLine(columnMap: Record<string, string>, line: any): LogLine {
214
+ const retVal: LogLine = {};
215
+ const fields = line?.fields ? Object.assign({}, ...line.fields) : null;
216
+ for (const key in columnMap) {
217
+ retVal[key] = fields ? fields[columnMap[key]] ?? "" : "";
218
+ }
219
+ return retVal;
220
+ }
221
+
77
222
  async GetLogsEx(request: GetLogsExRequest): Promise<GetLogsExResponse> {
78
223
  const logInfo = await this.GetLogAccessInfo();
79
- const columnMap = {};
224
+ const columnMap: Record<string, string> = {};
80
225
  logInfo.Columns.Column.forEach(column => columnMap[column.LogType] = column.Name);
81
226
 
82
- const convertLogLine = (line: any) => {
83
- const retVal: LogLine = {};
84
- for (const key in columnMap) {
85
- if (line?.fields) {
86
- retVal[key] = Object.assign({}, ...line.fields)[columnMap[key]] ?? "";
87
- } else {
88
- retVal[key] = "";
89
- }
90
- }
91
- return retVal;
227
+ const filters = buildFilters(request, columnMap);
228
+ const range: Record<string, string> = {
229
+ StartDate: request.StartDate instanceof Date ? request.StartDate.toISOString() : new Date(0).toISOString()
92
230
  };
231
+ if (request.EndDate instanceof Date) {
232
+ range.EndDate = request.EndDate.toISOString();
233
+ }
93
234
 
94
235
  const getLogsRequest: WsLogaccess.GetLogsRequest = {
95
- Filter: {
96
- leftBinaryFilter: {
97
- BinaryLogFilter: [{
98
- leftFilter: {
99
- LogCategory: WsLogaccess.LogAccessType.All,
100
- },
101
- } as WsLogaccess.BinaryLogFilter]
102
- }
103
- },
104
- Range: {
105
- StartDate: new Date(0).toISOString(),
106
- },
236
+ Filter: buildFilterTree(filters),
237
+ Range: range,
107
238
  LogLineStartFrom: request.LogLineStartFrom ?? 0,
108
239
  LogLineLimit: request.LogLineLimit ?? 100,
109
240
  SelectColumnMode: WsLogaccess.LogSelectColumnMode.DEFAULT,
@@ -117,142 +248,14 @@ export class LogaccessService extends LogaccessServiceBase {
117
248
  }
118
249
  };
119
250
 
120
- const filters: WsLogaccess.leftFilter[] = [];
121
- const logTypes = Object.values(WsLogaccess.LogColumnType);
122
- for (const key in request) {
123
- if (request[key] == null || request[key] === "" || (Array.isArray(request[key]) && request[key].length === 0)) {
124
- continue;
125
- }
126
- let searchField;
127
- if (key in columnMap) {
128
- if (logTypes.includes(key as WsLogaccess.LogColumnType)) {
129
- searchField = key;
130
- } else {
131
- searchField = columnMap[key];
132
- }
133
- }
134
- let logCategory;
135
- if (searchField) {
136
- switch (searchField) {
137
- case WsLogaccess.LogColumnType.workunits:
138
- case "hpcc.log.jobid":
139
- logCategory = WsLogaccess.LogAccessType.ByJobID;
140
- break;
141
- case WsLogaccess.LogColumnType.audience:
142
- case "hpcc.log.audience":
143
- logCategory = WsLogaccess.LogAccessType.ByTargetAudience;
144
- break;
145
- case WsLogaccess.LogColumnType.class:
146
- case "hpcc.log.class":
147
- logCategory = WsLogaccess.LogAccessType.ByLogType;
148
- break;
149
- case WsLogaccess.LogColumnType.components:
150
- case "kubernetes.container.name":
151
- logCategory = WsLogaccess.LogAccessType.ByComponent;
152
- break;
153
- default:
154
- logCategory = WsLogaccess.LogAccessType.ByFieldName;
155
- searchField = columnMap[key];
156
- }
157
- if (Array.isArray(request[key])) {
158
- request[key].forEach(value => {
159
- if (logCategory === WsLogaccess.LogAccessType.ByComponent) {
160
- value += "*";
161
- }
162
- filters.push({
163
- LogCategory: logCategory,
164
- SearchField: searchField,
165
- SearchByValue: value
166
- });
167
- });
168
- } else {
169
- let value = request[key];
170
- if (logCategory === WsLogaccess.LogAccessType.ByComponent) {
171
- // append wildcard to end of search value to include ephemeral
172
- // containers that aren't listed in ECL Watch's filters
173
- value += "*";
174
- }
175
- filters.push({
176
- LogCategory: logCategory,
177
- SearchField: searchField,
178
- SearchByValue: value
179
- });
180
- }
181
- }
182
- }
183
-
184
- if (filters.length > 2) {
185
- let binaryLogFilter = getLogsRequest.Filter.leftBinaryFilter.BinaryLogFilter[0];
186
- filters.forEach((filter, i) => {
187
- let operator = WsLogaccess.LogAccessFilterOperator.AND;
188
- if (i > 0) {
189
- if (filters[i - 1].SearchField === filter.SearchField) {
190
- operator = WsLogaccess.LogAccessFilterOperator.OR;
191
- }
192
- if (i === filters.length - 1) {
193
- binaryLogFilter.Operator = operator;
194
- binaryLogFilter.rightFilter = filter as WsLogaccess.rightFilter;
195
- } else {
196
- binaryLogFilter.Operator = operator;
197
- binaryLogFilter.rightBinaryFilter = {
198
- BinaryLogFilter: [{
199
- leftFilter: filter
200
- } as WsLogaccess.BinaryLogFilter]
201
- };
202
- binaryLogFilter = binaryLogFilter.rightBinaryFilter.BinaryLogFilter[0];
203
- }
204
- } else {
205
- binaryLogFilter.leftFilter = filter as WsLogaccess.leftFilter;
206
- }
207
- });
208
- } else {
209
- delete getLogsRequest.Filter.leftBinaryFilter;
210
- getLogsRequest.Filter.leftFilter = {
211
- LogCategory: WsLogaccess.LogAccessType.All
212
- } as WsLogaccess.leftFilter;
213
- if (filters[0]?.SearchField) {
214
- getLogsRequest.Filter.leftFilter = {
215
- LogCategory: filters[0]?.LogCategory,
216
- SearchField: filters[0]?.SearchField,
217
- SearchByValue: filters[0]?.SearchByValue
218
- };
219
- }
220
- if (filters[1]?.SearchField) {
221
- getLogsRequest.Filter.Operator = WsLogaccess.LogAccessFilterOperator.AND;
222
- if (filters[0].SearchField === filters[1].SearchField) {
223
- getLogsRequest.Filter.Operator = WsLogaccess.LogAccessFilterOperator.OR;
224
- }
225
- getLogsRequest.Filter.rightFilter = {
226
- LogCategory: filters[1]?.LogCategory,
227
- SearchField: filters[1]?.SearchField,
228
- SearchByValue: filters[1]?.SearchByValue
229
- };
230
- }
231
- }
232
-
233
- if (request.StartDate) {
234
- getLogsRequest.Range.StartDate = request.StartDate.toISOString();
235
- }
236
- if (request.EndDate) {
237
- getLogsRequest.Range.EndDate = request.EndDate.toISOString();
238
- }
239
-
240
251
  return this.GetLogs(getLogsRequest).then(response => {
241
252
  try {
242
253
  const logLines = JSON.parse(response.LogLines);
243
- let lines = [];
244
- switch (logInfo.RemoteLogManagerType) {
245
- case "azureloganalyticscurl":
246
- case "elasticstack":
247
- case "grafanacurl":
248
- lines = logLines.lines?.map(convertLogLine) ?? [];
249
- break;
250
- default:
251
- logger.warning(`Unknown RemoteLogManagerType: ${logInfo.RemoteLogManagerType}`);
252
- lines = [];
253
- }
254
+ const lines = knownLogManagerTypes.has(logInfo.RemoteLogManagerType)
255
+ ? (logLines.lines?.map((line: any) => this.convertLogLine(columnMap, line)) ?? [])
256
+ : (logger.warning(`Unknown RemoteLogManagerType: ${logInfo.RemoteLogManagerType}`), []);
254
257
  return {
255
- lines: lines,
258
+ lines,
256
259
  total: response.TotalLogLinesAvailable ?? 10000
257
260
  };
258
261
  } catch (e: any) {
@@ -50,5 +50,6 @@ export declare class LogaccessService extends LogaccessServiceBase {
50
50
  protected _logAccessInfo: Promise<WsLogaccess.GetLogAccessInfoResponse>;
51
51
  GetLogAccessInfo(request?: WsLogaccess.GetLogAccessInfoRequest): Promise<WsLogaccess.GetLogAccessInfoResponse>;
52
52
  GetLogs(request: WsLogaccess.GetLogsRequest): Promise<WsLogaccess.GetLogsResponse>;
53
+ private convertLogLine;
53
54
  GetLogsEx(request: GetLogsExRequest): Promise<GetLogsExResponse>;
54
55
  }