@mspcopilot/n8n-nodes-connectwise 0.1.7 → 0.1.9

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.
@@ -116,8 +116,8 @@ class ConnectWisePsaApi {
116
116
  displayName: "Check for Updates",
117
117
  name: "checkForUpdates",
118
118
  type: "boolean",
119
- default: !1,
120
- hint: "Current version: 0.1.7 (community edition)",
119
+ default: !0,
120
+ hint: "Current version: 0.1.9 (community edition)",
121
121
  description: "Check for new versions of this node package and show notifications when updates are available"
122
122
  } ];
123
123
  async authenticate(credentials, requestOptions) {
@@ -31,7 +31,7 @@ __export(ConnectWisePsa_node_exports, {
31
31
 
32
32
  module.exports = __toCommonJS(ConnectWisePsa_node_exports);
33
33
 
34
- var import_n8n_workflow = require("n8n-workflow"), import_methods = require("./methods"), import_fieldOptions = require("./methods/fieldOptions"), import_activity = require("./schema/resources/activity.schema"), import_company = require("./schema/resources/company.schema"), import_contact = require("./schema/resources/contact.schema"), import_diagnostics = require("./schema/resources/diagnostics.schema"), import_members = require("./schema/resources/members.schema"), import_reports = require("./schema/resources/reports.schema"), import_ticket = require("./schema/resources/ticket.schema"), import_ticketNote = require("./schema/resources/ticketNote.schema"), import_ticketTask = require("./schema/resources/ticketTask.schema"), import_timeEntry = require("./schema/resources/timeEntry.schema"), import_custom = require("./schema/resources/custom.schema");
34
+ var import_n8n_workflow = require("n8n-workflow"), import_methods = require("./methods"), import_hint_collector = require("./helpers/hint-collector"), import_fieldOptions = require("./methods/fieldOptions"), import_activity = require("./schema/resources/activity.schema"), import_company = require("./schema/resources/company.schema"), import_contact = require("./schema/resources/contact.schema"), import_diagnostics = require("./schema/resources/diagnostics.schema"), import_members = require("./schema/resources/members.schema"), import_reports = require("./schema/resources/reports.schema"), import_ticket = require("./schema/resources/ticket.schema"), import_ticketNote = require("./schema/resources/ticketNote.schema"), import_ticketTask = require("./schema/resources/ticketTask.schema"), import_timeEntry = require("./schema/resources/timeEntry.schema"), import_custom = require("./schema/resources/custom.schema");
35
35
 
36
36
  const resources = {
37
37
  activity: {
@@ -74,7 +74,7 @@ const resources = {
74
74
 
75
75
  class ConnectWisePsa {
76
76
  constructor() {
77
- console.log(`🔌 ConnectWise PSA Node Loaded | 🏗️ Build: ${"community".toUpperCase()} (${"prod".toUpperCase()} mode)`);
77
+ console.log(`🔌 ConnectWise PSA Node Loaded | 📦 v0.1.9 | 🏗️ Build: ${"community".toUpperCase()} (${"prod".toUpperCase()} mode)`);
78
78
  }
79
79
  description={
80
80
  displayName: "ConnectWise PSA",
@@ -159,26 +159,31 @@ class ConnectWisePsa {
159
159
  };
160
160
  async execute() {
161
161
  const items = this.getInputData(), returnData = [], resource = this.getNodeParameter("resource", 0), operation = this.getNodeParameter("operation", 0);
162
- for (let i = 0; i < items.length; i++) try {
163
- const resourceData = resources[resource];
164
- if (!resourceData) throw new import_n8n_workflow.NodeOperationError(this.getNode(), `Resource ${resource} not found`);
165
- const responseData = await resourceData.execute.call(this, operation, i);
166
- returnData.push(...responseData);
167
- } catch (error) {
168
- if (this.continueOnFail()) {
169
- const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray({
170
- error: error.message
171
- }), {
172
- itemData: {
173
- item: i
174
- }
175
- });
176
- returnData.push(...executionData);
177
- continue;
162
+ (0, import_hint_collector.initHints)(this);
163
+ try {
164
+ for (let i = 0; i < items.length; i++) try {
165
+ const resourceData = resources[resource];
166
+ if (!resourceData) throw new import_n8n_workflow.NodeOperationError(this.getNode(), `Resource ${resource} not found`);
167
+ const responseData = await resourceData.execute.call(this, operation, i);
168
+ returnData.push(...responseData);
169
+ } catch (error) {
170
+ if (this.continueOnFail()) {
171
+ const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray({
172
+ error: error.message
173
+ }), {
174
+ itemData: {
175
+ item: i
176
+ }
177
+ });
178
+ returnData.push(...executionData);
179
+ continue;
180
+ }
181
+ throw error;
178
182
  }
179
- throw error;
183
+ return [ returnData ];
184
+ } finally {
185
+ (0, import_hint_collector.flushHints)(this);
180
186
  }
181
- return [ returnData ];
182
187
  }
183
188
  }
184
189
 
@@ -29,18 +29,20 @@ __export(common_descriptions_exports, {
29
29
  CHANGELOG_URL: () => CHANGELOG_URL,
30
30
  DOCURL_PREFIX: () => DOCURL_PREFIX,
31
31
  DOCURL_SUFFIX: () => DOCURL_SUFFIX,
32
+ SUPPORTER_URL: () => SUPPORTER_URL,
32
33
  VERSION_CHECK_CACHE_TTL: () => VERSION_CHECK_CACHE_TTL,
33
34
  VERSION_CHECK_URL: () => VERSION_CHECK_URL
34
35
  });
35
36
 
36
37
  module.exports = __toCommonJS(common_descriptions_exports);
37
38
 
38
- const DOCURL_PREFIX = "mspcopilot.io/docs", DOCURL_SUFFIX = "?utm_source=n8n_node_connectwise", CHANGELOG_URL = "mspcopilot.io/changelog${DOCURL_SUFFIX}", VERSION_CHECK_URL = "https://djstgzbunlbcnghwfzja.supabase.co/functions/v1/version-check/n8n-nodes-connectwise", VERSION_CHECK_CACHE_TTL = 6e5;
39
+ const DOCURL_PREFIX = "mspcopilot.io/docs", DOCURL_SUFFIX = "?utm_source=n8n_node_connectwise", CHANGELOG_URL = "mspcopilot.io/changelog${DOCURL_SUFFIX}", SUPPORTER_URL = "mspcopilot.io/support?utm_source=n8n", VERSION_CHECK_URL = "https://api.mspcopilot.io/version-check/n8n-nodes-connectwise", VERSION_CHECK_CACHE_TTL = 36e5;
39
40
 
40
41
  0 && (module.exports = {
41
42
  CHANGELOG_URL: CHANGELOG_URL,
42
43
  DOCURL_PREFIX: DOCURL_PREFIX,
43
44
  DOCURL_SUFFIX: DOCURL_SUFFIX,
45
+ SUPPORTER_URL: SUPPORTER_URL,
44
46
  VERSION_CHECK_CACHE_TTL: VERSION_CHECK_CACHE_TTL,
45
47
  VERSION_CHECK_URL: VERSION_CHECK_URL
46
48
  });
@@ -46,10 +46,10 @@ function collectFieldData(context, schema, operation, index) {
46
46
  for (const field of schema.fields) if (!UI_CONTROL_FIELDS.includes(field.name) && field.x_noSubmit !== !0 && isPropertyShown(field)) if (relevantProperties.push(field),
47
47
  field.type === "resourceLocator") try {
48
48
  const value = context.getNodeParameter(field.name, index);
49
- if (value != null && typeof value == "object") {
50
- const valueObj = value;
51
- valueObj.mode === "id" || valueObj.mode === "identifier" ? schemaFields[field.name] = valueObj.value : valueObj.mode === "list" && valueObj.value && (schemaFields[field.name] = valueObj.value.value || valueObj.value);
52
- }
49
+ if (value != null) if (typeof value == "object" && "value" in value) {
50
+ const inner = value.value;
51
+ schemaFields[field.name] = typeof inner == "object" ? inner.value : inner;
52
+ } else schemaFields[field.name] = value;
53
53
  } catch {} else collectFromProperty(field, schemaFields);
54
54
  try {
55
55
  const additionalFieldsParam = context.getNodeParameter("additionalFields", index, {});
@@ -0,0 +1,187 @@
1
+ var __defProp = Object.defineProperty;
2
+
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+
9
+ var __export = (target, all) => {
10
+ for (var name in all) __defProp(target, name, {
11
+ get: all[name],
12
+ enumerable: !0
13
+ });
14
+ }, __copyProps = (to, from, except, desc) => {
15
+ if (from && typeof from == "object" || typeof from == "function") for (let key of __getOwnPropNames(from)) !__hasOwnProp.call(to, key) && key !== except && __defProp(to, key, {
16
+ get: () => from[key],
17
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
18
+ });
19
+ return to;
20
+ };
21
+
22
+ var __toCommonJS = mod => __copyProps(__defProp({}, "__esModule", {
23
+ value: !0
24
+ }), mod);
25
+
26
+ var hint_collector_exports = {};
27
+
28
+ __export(hint_collector_exports, {
29
+ flushHints: () => flushHints,
30
+ hint: () => hint,
31
+ initHints: () => initHints
32
+ });
33
+
34
+ module.exports = __toCommonJS(hint_collector_exports);
35
+
36
+ var import_common = require("../descriptions/common.descriptions");
37
+
38
+ const HINT_TYPES = {
39
+ items_retrieved: {
40
+ aggregate: "sum",
41
+ template: (total, count) => count === 1 ? `Retrieved ${total} items` : `Retrieved ${total} items across ${count} API calls`
42
+ },
43
+ pagination_limit: {
44
+ aggregate: "dedupe",
45
+ template: data => `Pagination limit reached: Retrieved ${data.itemCount} items across ${data.maxPages} pages. Additional records may exist.`
46
+ },
47
+ cache_hit: {
48
+ aggregate: "count",
49
+ template: count => count === 1 ? "1 request served from cache" : `${count} requests served from cache`
50
+ },
51
+ cache_stored: {
52
+ aggregate: "latest",
53
+ template: ttl => `Response cached for ${ttl}`
54
+ },
55
+ cache_invalidated: {
56
+ aggregate: "count",
57
+ template: count => count === 1 ? "Cache entry invalidated" : `${count} cache entries invalidated`
58
+ },
59
+ cache_upgrade: {
60
+ aggregate: "dedupe",
61
+ template: () => `✨ Caching is available for supporters only. Learn more at <a href="https://${import_common.SUPPORTER_URL}">${import_common.SUPPORTER_URL.split("?")[0]}</a>`
62
+ },
63
+ cache_error: {
64
+ aggregate: "dedupe",
65
+ template: error => `⚠️ Cache error: ${error}`
66
+ },
67
+ version_update: {
68
+ aggregate: "dedupe",
69
+ template: version => `🎉 ConnectWise PSA Node v${version} is available! <a href="https://${import_common.CHANGELOG_URL}" target="_blank">View changelog</a>`
70
+ },
71
+ pro_upgrade: {
72
+ aggregate: "dedupe",
73
+ template: () => `✨ This helpful feature is available to supporters. See <a href="https://${import_common.SUPPORTER_URL}">${import_common.SUPPORTER_URL.split("?")[0]}</a> for other benefits.<br><br><hr><br>Simplify and speed up your development with these other functions. <ul><li>Smart Alert Consolidation</li><li>Reports</li><li>Ticket Management</li></ul>`
74
+ },
75
+ custom: {
76
+ aggregate: "dedupe",
77
+ template: message => message
78
+ }
79
+ }, collectors = /* @__PURE__ */ new WeakMap;
80
+
81
+ function initHints(ctx) {
82
+ collectors.set(ctx, {
83
+ values: /* @__PURE__ */ new Map
84
+ });
85
+ }
86
+
87
+ function hint(ctx, type, value) {
88
+ const collector = collectors.get(ctx);
89
+ if (!collector) {
90
+ emitHintDirectly(ctx, type, value);
91
+ return;
92
+ }
93
+ let values = collector.values.get(type);
94
+ switch (values || (values = [], collector.values.set(type, values)), (HINT_TYPES[type] || HINT_TYPES.custom).aggregate) {
95
+ case "dedupe":
96
+ values.length === 0 && values.push(value), type === "custom" && !values.includes(value) && values.push(value);
97
+ break;
98
+
99
+ case "count":
100
+ values.push(1);
101
+ break;
102
+
103
+ case "sum":
104
+ values.push(typeof value == "number" ? value : 0);
105
+ break;
106
+
107
+ case "latest":
108
+ values.length = 0, values.push(value);
109
+ break;
110
+ }
111
+ }
112
+
113
+ function flushHints(ctx) {
114
+ const collector = collectors.get(ctx);
115
+ if (!collector) return;
116
+ if (!("addExecutionHints" in ctx)) {
117
+ collectors.delete(ctx);
118
+ return;
119
+ }
120
+ const hints = [];
121
+ for (const [type, values] of collector.values) {
122
+ if (values.length === 0) continue;
123
+ const config = HINT_TYPES[type] || HINT_TYPES.custom;
124
+ let message;
125
+ switch (config.aggregate) {
126
+ case "dedupe":
127
+ if (type === "custom") {
128
+ for (const val of values) val !== void 0 && hints.push({
129
+ message: config.template(val),
130
+ location: "outputPane"
131
+ });
132
+ continue;
133
+ }
134
+ message = config.template(values[0]);
135
+ break;
136
+
137
+ case "count":
138
+ message = config.template(values.length);
139
+ break;
140
+
141
+ case "sum":
142
+ const total = values.reduce((acc, v) => acc + (typeof v == "number" ? v : 0), 0);
143
+ message = config.template(total, values.length);
144
+ break;
145
+
146
+ case "latest":
147
+ message = config.template(values[values.length - 1]);
148
+ break;
149
+
150
+ default:
151
+ message = config.template(values[0]);
152
+ }
153
+ hints.push({
154
+ message: message,
155
+ location: "outputPane"
156
+ });
157
+ }
158
+ hints.length > 0 && ctx.addExecutionHints(...hints), collectors.delete(ctx);
159
+ }
160
+
161
+ function emitHintDirectly(ctx, type, value) {
162
+ if (!("addExecutionHints" in ctx)) return;
163
+ const config = HINT_TYPES[type] || HINT_TYPES.custom;
164
+ let message;
165
+ switch (config.aggregate) {
166
+ case "count":
167
+ message = config.template(1);
168
+ break;
169
+
170
+ case "sum":
171
+ message = config.template(value ?? 0, 1);
172
+ break;
173
+
174
+ default:
175
+ message = config.template(value);
176
+ }
177
+ ctx.addExecutionHints({
178
+ message: message,
179
+ location: "outputPane"
180
+ });
181
+ }
182
+
183
+ 0 && (module.exports = {
184
+ flushHints: flushHints,
185
+ hint: hint,
186
+ initHints: initHints
187
+ });
@@ -35,13 +35,12 @@ __export(utils_exports, {
35
35
 
36
36
  module.exports = __toCommonJS(utils_exports);
37
37
 
38
+ var import_hint_collector = require("./hint-collector");
39
+
38
40
  const ms = require("ms");
39
41
 
40
42
  function returnProOnlyMessage(context, itemIndex = 0) {
41
- return "addExecutionHints" in context && context.addExecutionHints({
42
- message: '✨ This helpful feature is available to supporters. See <a href="https://mspcopilot.io/support?utm_source=n8n">MSPCopilot.io/support</a> for other benefits.<br><br><hr><br>Simplify and speed up your development with these other functions. <ul><li>Smart Alert Consolidation</li><li>Reports</li><li>Ticket Management</li></ul>',
43
- location: "outputPane"
44
- }), context.helpers.constructExecutionMetaData(context.helpers.returnJsonArray([]), {
43
+ return (0, import_hint_collector.hint)(context, "pro_upgrade"), context.helpers.constructExecutionMetaData(context.helpers.returnJsonArray([]), {
45
44
  itemData: {
46
45
  item: itemIndex
47
46
  }
@@ -36,6 +36,11 @@ module.exports = __toCommonJS(reference_generator_exports);
36
36
 
37
37
  var import_transport = require("../../transport"), import_references = require("../references"), import_reports_descriptions = require("../reports-descriptions");
38
38
 
39
+ function buildRecordUrl(credentials, recordType, recordId) {
40
+ const isCustom = credentials.site === "custom", apiHost = isCustom ? credentials.customServer : credentials.site, hostname = !isCustom && apiHost.startsWith("api-") ? apiHost.replace("api-", "") : apiHost, version = (credentials.apiPath || import_transport.DEFAULT_API_PATH).split("/")[0];
41
+ return `https://${hostname}/${version}/services/system_io/router/openrecord.rails?recordType=${recordType}&recid=${recordId}&companyName=${credentials.companyId}`;
42
+ }
43
+
39
44
  const NOT_SPECIFIED_VALUE = "", NOT_SPECIFIED_DISPLAY = " ";
40
45
 
41
46
  function getNestedProperty(obj, path) {
@@ -90,14 +95,20 @@ async function loadOptionsCore(config, referenceKey) {
90
95
  }
91
96
  let endpoint;
92
97
  if (typeof configWithDefaults.endpoint == "function") {
93
- endpoint = configWithDefaults.endpoint(allParams);
94
- const serviceBoardsMatch = endpoint.match(/^\/service\/boards\/([^\/]+)/);
95
- if (serviceBoardsMatch) {
96
- const boardId = serviceBoardsMatch[1];
97
- if (isNaN(Number(boardId))) return [ {
98
- name: "Cannot Lookup by Name, Use Expression to Specify Value",
99
- value: NOT_SPECIFIED_VALUE
100
- } ];
98
+ if (endpoint = configWithDefaults.endpoint(allParams), endpoint.includes("/undefined/") || endpoint.includes("//") || endpoint.endsWith("/undefined")) return [ {
99
+ name: NOT_SPECIFIED_DISPLAY,
100
+ value: NOT_SPECIFIED_VALUE
101
+ } ];
102
+ const numericIdPatterns = [ /^\/service\/boards\/([^\/]+)/, /^\/project\/projects\/([^\/]+)/ ];
103
+ for (const pattern of numericIdPatterns) {
104
+ const match = endpoint.match(pattern);
105
+ if (match) {
106
+ const idValue = match[1];
107
+ if (isNaN(Number(idValue))) return [ {
108
+ name: NOT_SPECIFIED_DISPLAY,
109
+ value: NOT_SPECIFIED_VALUE
110
+ } ];
111
+ }
101
112
  }
102
113
  } else endpoint = configWithDefaults.endpoint;
103
114
  const items = await import_transport.connectWiseApiRequest.call(this, "GET", endpoint, {}, queryParams, {
@@ -152,9 +163,18 @@ async function searchConnectWise(referenceKey, filter) {
152
163
  const baseConditions = config.conditions ? config.conditions(params) : "";
153
164
  return conditions && baseConditions ? `${baseConditions} and ${conditions}` : conditions || baseConditions;
154
165
  }
155
- };
166
+ }, options = await loadOptionsCore.call(this, searchConfig, referenceKey);
167
+ if (config.recordType) try {
168
+ const credentials = await this.getCredentials("connectWisePsaApi");
169
+ return {
170
+ results: options.map(option => option.value === "" ? option : {
171
+ ...option,
172
+ url: buildRecordUrl(credentials, config.recordType, option.value)
173
+ })
174
+ };
175
+ } catch {}
156
176
  return {
157
- results: await loadOptionsCore.call(this, searchConfig, referenceKey)
177
+ results: options
158
178
  };
159
179
  }
160
180
 
@@ -45,7 +45,8 @@ const allReferences = {
45
45
  conditions: () => "deletedFlag=false",
46
46
  fields: "id,name,identifier",
47
47
  pageSize: 300,
48
- orderBy: "_info/lastUpdated desc"
48
+ orderBy: "_info/lastUpdated desc",
49
+ recordType: "CompanyFV"
49
50
  },
50
51
  company_countries: {
51
52
  endpoint: "/company/countries"
@@ -115,16 +115,6 @@ const companyFields = [ {
115
115
  searchable: !0
116
116
  }
117
117
  } ]
118
- }, {
119
- displayName: 'For information on how to create/update companies, check the <a href="temp">documentation</a>',
120
- name: "importantNotice",
121
- type: "notice",
122
- default: "",
123
- displayOptions: {
124
- show: {
125
- operation: [ "create", "update" ]
126
- }
127
- }
128
118
  }, {
129
119
  displayName: "Identifier",
130
120
  name: "identifier",
@@ -681,6 +671,7 @@ const companyFields = [ {
681
671
  description: "Manage ConnectWise companies",
682
672
  identifierField: "identifier",
683
673
  nameField: "name",
674
+ selectorField: "company",
684
675
  fieldDetection: {
685
676
  endpoint: "/company/companies",
686
677
  sampleSize: 3
@@ -38,6 +38,8 @@ __export(diagnostics_schema_exports, {
38
38
 
39
39
  module.exports = __toCommonJS(diagnostics_schema_exports);
40
40
 
41
+ var import_hint_collector = require("../../helpers/hint-collector");
42
+
41
43
  const diagnosticsFields = [ {
42
44
  displayName: "Operation",
43
45
  name: "operation",
@@ -70,10 +72,8 @@ async function execute(operation, i) {
70
72
  clearedCount: clearedCount,
71
73
  message: `Cleared ${clearedCount} cache entries`
72
74
  };
73
- return this.addExecutionHints({
74
- message: `Cache cleared: ${clearedCount} entries removed`,
75
- location: "outputPane"
76
- }), this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(result), {
75
+ return (0, import_hint_collector.hint)(this, "custom", `Cache cleared: ${clearedCount} entries removed`),
76
+ this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(result), {
77
77
  itemData: {
78
78
  item: i
79
79
  }
@@ -88,10 +88,7 @@ async function execute(operation, i) {
88
88
  error: originalError,
89
89
  message: friendlyMessage
90
90
  };
91
- return this.addExecutionHints({
92
- message: friendlyMessage,
93
- location: "outputPane"
94
- }), this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(result), {
91
+ return (0, import_hint_collector.hint)(this, "custom", friendlyMessage), this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(result), {
95
92
  itemData: {
96
93
  item: i
97
94
  }
@@ -147,18 +147,31 @@ const ticketFields = [ {
147
147
  }, {
148
148
  displayName: "Company",
149
149
  name: "company",
150
- type: "options",
151
- default: "",
152
- description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
153
- typeOptions: {
154
- loadOptionsMethod: "loadOptions_company_companies"
150
+ type: "resourceLocator",
151
+ default: {
152
+ mode: "list",
153
+ value: ""
155
154
  },
155
+ description: 'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
156
156
  displayOptions: {
157
157
  show: {
158
158
  operation: [ "create", "update" ],
159
159
  ticketType: [ "service" ]
160
160
  }
161
161
  },
162
+ modes: [ {
163
+ displayName: "ID",
164
+ name: "id",
165
+ type: "string"
166
+ }, {
167
+ displayName: "From List",
168
+ name: "list",
169
+ type: "list",
170
+ typeOptions: {
171
+ searchListMethod: "searchList_company_companies",
172
+ searchable: !0
173
+ }
174
+ } ],
162
175
  x_submitAs: "company.id"
163
176
  }, {
164
177
  displayName: "Board ID or Name",
@@ -247,7 +260,12 @@ const ticketFields = [ {
247
260
  },
248
261
  displayOptions: {
249
262
  show: {
250
- operation: [ "create", "update" ]
263
+ operation: [ "create", "update" ],
264
+ board: [ {
265
+ _cnd: {
266
+ exists: !0
267
+ }
268
+ } ]
251
269
  }
252
270
  },
253
271
  x_detectField: "auto"
@@ -285,7 +303,8 @@ const ticketFields = [ {
285
303
  }
286
304
  } ]
287
305
  }
288
- }
306
+ },
307
+ x_submitAs: "type.id"
289
308
  }, {
290
309
  displayName: "Sub Type",
291
310
  name: "subType",
@@ -306,7 +325,8 @@ const ticketFields = [ {
306
325
  }
307
326
  } ]
308
327
  }
309
- }
328
+ },
329
+ x_submitAs: "subType.id"
310
330
  }, {
311
331
  displayName: "Item",
312
332
  name: "item",
@@ -326,7 +346,8 @@ const ticketFields = [ {
326
346
  }
327
347
  } ]
328
348
  }
329
- }
349
+ },
350
+ x_submitAs: "item.id"
330
351
  }, {
331
352
  displayName: "Initial Description",
332
353
  name: "initialDescription",
@@ -408,7 +408,7 @@ const COMMON_FIELDS_NAME = "Common Fields", staticFieldDefinitions = {
408
408
  value: "__ALL_FIELDS_OVERRIDE__"
409
409
  }, {
410
410
  name: COMMON_FIELDS_NAME,
411
- value: "id,summary,recordType,board/id,board/name,status/id,status/name,company/id,company/identifier,company/name,agreement/name,agreement/type,contact/name,contactEmailAddress,team/name,priority/name,source/name,customerUpdatedFlag,closedFlag,location/name,department/identifier,sla/name,_info/lastUpdated,_info/updatedBy,_info/dateEntered,__customFields,$$customFields,type/name,subType/name,item/name,owner/identifier,budgetHours,actualHours,resources,closedDate,closedBy,project/id,project/name,phase/name"
411
+ value: "id,summary,recordType,board/id,board/name,status/id,status/name,company/id,company/identifier,company/name,agreement/name,agreement/type,contact/id,contact/name,contactEmailAddress,team/name,priority/name,source/name,customerUpdatedFlag,closedFlag,location/name,department/identifier,sla/name,_info/lastUpdated,_info/updatedBy,_info/dateEntered,__customFields,$$customFields,type/name,subType/name,item/name,owner/identifier,budgetHours,actualHours,resources,closedDate,closedBy,project/id,project/name,phase/name"
412
412
  }, {
413
413
  name: "ID",
414
414
  value: "id"
@@ -1440,7 +1440,7 @@ const COMMON_FIELDS_NAME = "Common Fields", staticFieldDefinitions = {
1440
1440
  value: "__ALL_FIELDS_OVERRIDE__"
1441
1441
  }, {
1442
1442
  name: COMMON_FIELDS_NAME,
1443
- value: "id,ticket/id,ticket/summary,text,detailDescriptionFlag,internalAnalysisFlag,resolutionFlag,issueFlag,member/id,member/identifier,member/name,contact/id,contact/name,dateCreated,createdBy,_info"
1443
+ value: "id,ticketId,ticket/id,ticket/summary,text,detailDescriptionFlag,internalAnalysisFlag,resolutionFlag,issueFlag,member/id,member/identifier,member/name,contact/id,contact/name,dateCreated,createdBy,_info"
1444
1444
  }, {
1445
1445
  name: "ID",
1446
1446
  value: "id"
@@ -37,17 +37,27 @@ module.exports = __toCommonJS(cache_exports);
37
37
 
38
38
  var import_utils = require("../helpers/utils");
39
39
 
40
- let cacheClient = null, Redis = null;
40
+ let cacheClient = null, Redis = null, lastConnectionError = null;
41
+
42
+ function getBestErrorMessage(operationError) {
43
+ return lastConnectionError || operationError;
44
+ }
41
45
 
42
46
  async function getCacheClient() {
43
47
  return null;
44
48
  }
45
49
 
46
50
  async function getCached(key) {
47
- return null;
51
+ return {
52
+ data: null
53
+ };
48
54
  }
49
55
 
50
- async function setCached(key, data, ttl) {}
56
+ async function setCached(key, data, ttl) {
57
+ return {
58
+ success: !1
59
+ };
60
+ }
51
61
 
52
62
  async function deleteCached(key) {
53
63
  return !1;
@@ -41,7 +41,7 @@ __export(client_exports, {
41
41
 
42
42
  module.exports = __toCommonJS(client_exports);
43
43
 
44
- var import_n8n_workflow = require("n8n-workflow"), import_logging = require("../helpers/logging");
44
+ var import_n8n_workflow = require("n8n-workflow"), import_logging = require("../helpers/logging"), import_hint_collector = require("../helpers/hint-collector");
45
45
 
46
46
  const DEFAULT_API_PATH = "v4_6_release/apis/3.0";
47
47
 
@@ -92,7 +92,7 @@ async function connectWiseApiRequest(method, endpoint, body = {}, qs = {}, optio
92
92
  console.warn("Version check module load failed:", error);
93
93
  }
94
94
  if ("getNodeParameter" in this && method === "GET") try {
95
- this.getNodeParameter("cacheResponse", 0, !1) && "addExecutionHints" in this;
95
+ const cacheResponse = this.getNodeParameter("cacheResponse", 0, !1);
96
96
  } catch {}
97
97
  return await connectWiseHttpRequest.call(this, method, endpoint, body, qs, options);
98
98
  }
@@ -134,9 +134,9 @@ async function connectWiseHttpRequest(method, endpoint, body = {}, qs = {}, opti
134
134
  if (pageNumber > maxPages) {
135
135
  const itemCount = isReportFormat ? reportResult?.row_values?.length || 0 : returnData.length;
136
136
  console.log(`⚠️ Reached maximum page limit (${maxPages} pages, ${itemCount} records). Some results may be missing.`),
137
- "addExecutionHints" in this && this.addExecutionHints({
138
- message: `Pagination limit reached: Retrieved ${itemCount} items across ${maxPages} pages. Additional records may exist.`,
139
- location: "outputPane"
137
+ (0, import_hint_collector.hint)(this, "pagination_limit", {
138
+ itemCount: itemCount,
139
+ maxPages: maxPages
140
140
  });
141
141
  break;
142
142
  }
@@ -162,23 +162,18 @@ async function connectWiseHttpRequest(method, endpoint, body = {}, qs = {}, opti
162
162
  hasMultiplePages = !0;
163
163
  }
164
164
  if (hasMultiplePages) {
165
- const currentPageCount = isReportFormat ? responseData.row_values?.length || 0 : responseData.length, totalCount = isReportFormat ? reportResult.row_values.length : returnData.length;
166
- console.log(`✅ Page ${pageNumber}: received ${currentPageCount} items (total so far: ${totalCount})`);
165
+ const currentPageCount = isReportFormat ? responseData.row_values?.length || 0 : responseData.length, totalCount2 = isReportFormat ? reportResult.row_values.length : returnData.length;
166
+ console.log(`✅ Page ${pageNumber}: received ${currentPageCount} items (total so far: ${totalCount2})`);
167
167
  }
168
168
  pageNumber++;
169
169
  } while (isReportFormat ? responseData.row_values && responseData.row_values.length === pageSize : responseData.length === pageSize);
170
170
  if (hasMultiplePages) {
171
- const totalCount = isReportFormat ? reportResult.row_values.length : returnData.length;
172
- console.log(`📊 Pagination complete: ${totalCount} total items\n`);
171
+ const totalCount2 = isReportFormat ? reportResult.row_values.length : returnData.length;
172
+ console.log(`📊 Pagination complete: ${totalCount2} total items\n`);
173
173
  }
174
- if ("addExecutionHints" in this) {
175
- const totalCount = isReportFormat ? reportResult?.row_values?.length || 0 : returnData.length;
176
- totalCount > 0 && this.addExecutionHints({
177
- message: `Retrieved ${totalCount} items${pageNumber > 2 ? ` across ${pageNumber - 1} pages` : ""}`,
178
- location: "outputPane"
179
- });
180
- }
181
- return isReportFormat ? reportResult : returnData;
174
+ const totalCount = isReportFormat ? reportResult?.row_values?.length || 0 : returnData.length;
175
+ return totalCount > 0 && (0, import_hint_collector.hint)(this, "items_retrieved", totalCount),
176
+ isReportFormat ? reportResult : returnData;
182
177
  } else {
183
178
  options.limit && (qs.pageSize = options.limit), Object.keys(qs).length && (requestOptions.qs = qs);
184
179
  try {
@@ -28,6 +28,7 @@ var operations_exports = {};
28
28
  __export(operations_exports, {
29
29
  buildEndpointUrl: () => buildEndpointUrl,
30
30
  extractOperationParams: () => extractOperationParams,
31
+ fetchRecordById: () => fetchRecordById,
31
32
  recordCreate: () => recordCreate,
32
33
  recordDelete: () => recordDelete,
33
34
  recordGet: () => recordGet,
@@ -84,24 +85,29 @@ function buildEndpointUrl(schema, operation, params = {}) {
84
85
  };
85
86
  }
86
87
 
88
+ async function fetchRecordById(context, schema, params, qs) {
89
+ const {method: method, endpoint: endpoint} = buildEndpointUrl(schema, "get", params);
90
+ return import_client.connectWiseApiRequest.call(context, method, endpoint, {}, qs);
91
+ }
92
+
87
93
  async function recordGet(context, schema, params, qs) {
88
- if (params.company && typeof params.company == "object") {
89
- const companyValue = params.company;
90
- if (companyValue.mode === "identifier") {
91
- const identifierField = schema.identifierField || "id", searchCondition = `${identifierField}="${companyValue.value}"`, searchQs = {
94
+ const selectorField = schema.selectorField || "id", selectorValue = params[selectorField];
95
+ if (selectorValue && typeof selectorValue == "object" && "mode" in selectorValue) {
96
+ const locator = selectorValue;
97
+ if (locator.mode === "identifier") {
98
+ const identifierField = schema.identifierField || "id", searchCondition = `${identifierField}="${locator.value}"`, searchQs = {
92
99
  ...qs,
93
100
  conditions: searchCondition
94
101
  }, getAllEndpoint = buildEndpointUrl(schema, "getAll", params), results = await import_client.connectWiseApiRequest.call(context, getAllEndpoint.method, getAllEndpoint.endpoint, {}, searchQs);
95
- if (!results || results.length === 0) throw new Error(`No ${schema.resource} found with ${identifierField}: ${companyValue.value}`);
102
+ if (!results || results.length === 0) throw new Error(`No ${schema.resource} found with ${identifierField}: ${locator.value}`);
96
103
  return results[0];
97
104
  } else {
98
105
  let id;
99
- if (companyValue.mode === "id") id = companyValue.value; else if (companyValue.mode === "list" && companyValue.value) id = typeof companyValue.value == "object" && "value" in companyValue.value ? companyValue.value.value : companyValue.value; else throw new Error(`Please select a ${schema.resource} from the list or enter an ID`);
106
+ if (locator.mode === "id") id = locator.value; else if (locator.mode === "list" && locator.value) id = typeof locator.value == "object" && "value" in locator.value ? locator.value.value : locator.value; else throw new Error(`Please select a ${schema.resource} from the list or enter an ID`);
100
107
  params.id = id;
101
108
  }
102
109
  }
103
- const {method: method, endpoint: endpoint} = buildEndpointUrl(schema, "get", params);
104
- return await import_client.connectWiseApiRequest.call(context, method, endpoint, {}, qs);
110
+ return fetchRecordById(context, schema, params, qs);
105
111
  }
106
112
 
107
113
  async function recordGetAll(context, schema, params, qs, options) {
@@ -136,7 +142,10 @@ async function recordUpdate(context, schema, endpoint, index, qs) {
136
142
  let existingCustomFields = [];
137
143
  if (existingRecord) existingCustomFields = existingRecord.customFields || []; else {
138
144
  const params = extractOperationParams(context, index);
139
- existingRecord = await recordGet(context, schema, params, {}), existingCustomFields = existingRecord.customFields || [];
145
+ existingRecord = await fetchRecordById(context, schema, {
146
+ id: params.id,
147
+ ticketType: params.ticketType
148
+ }, {}), existingCustomFields = existingRecord.customFields || [];
140
149
  }
141
150
  const fieldsArray = customFields && typeof customFields == "object" && "field" in customFields ? customFields.field || [] : Array.isArray(customFields) ? customFields : [], customFieldPatch = [ {
142
151
  op: "replace",
@@ -159,6 +168,7 @@ async function recordDelete(context, endpoint) {
159
168
  0 && (module.exports = {
160
169
  buildEndpointUrl: buildEndpointUrl,
161
170
  extractOperationParams: extractOperationParams,
171
+ fetchRecordById: fetchRecordById,
162
172
  recordCreate: recordCreate,
163
173
  recordDelete: recordDelete,
164
174
  recordGet: recordGet,
@@ -37,14 +37,12 @@ __export(version_check_exports, {
37
37
 
38
38
  module.exports = __toCommonJS(version_check_exports);
39
39
 
40
- var fs = __toESM(require("fs/promises")), path = __toESM(require("path")), os = __toESM(require("os")), import_common = require("../descriptions/common.descriptions");
40
+ var fs = __toESM(require("fs/promises")), path = __toESM(require("path")), os = __toESM(require("os")), import_common = require("../descriptions/common.descriptions"), import_hint_collector = require("../helpers/hint-collector");
41
41
 
42
- const CACHE_FILE_PATH = path.join(os.tmpdir(), "connectwise-n8n-version-cache.json"), CURRENT_VERSION = "0.1.7";
42
+ const CACHE_FILE_PATH = path.join(os.tmpdir(), "connectwise-n8n-version-cache.json"), CURRENT_VERSION = "0.1.9";
43
43
 
44
44
  let inMemoryCache = null, cacheLoadPromise = null, fetchInProgress = !1;
45
45
 
46
- const notificationShown = /* @__PURE__ */ new WeakSet;
47
-
48
46
  async function initializeCache() {
49
47
  try {
50
48
  const fileContent = await fs.readFile(CACHE_FILE_PATH, "utf-8"), cacheEntry = JSON.parse(fileContent);
@@ -112,10 +110,7 @@ function checkForUpdatesAsync() {
112
110
  async function checkAndNotifyUpdate(context) {
113
111
  try {
114
112
  if (!(await context.getCredentials("connectWisePsaApi")).checkForUpdates) return;
115
- checkForUpdatesAsync(), inMemoryCache?.updateAvailable && "addExecutionHints" in context && !notificationShown.has(context) && (context.addExecutionHints({
116
- message: `🎉 ConnectWise PSA Node v${inMemoryCache.latest} is available! <a href="https://${import_common.CHANGELOG_URL}" target="_blank">View changelog</a>`,
117
- location: "outputPane"
118
- }), notificationShown.add(context));
113
+ checkForUpdatesAsync(), inMemoryCache?.updateAvailable && (0, import_hint_collector.hint)(context, "version_update", inMemoryCache.latest);
119
114
  } catch (error) {
120
115
  console.warn("⚠️ Version check failed:", error);
121
116
  }
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@mspcopilot/n8n-nodes-connectwise",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "n8n node to integrate with ConnectWise PSA",
5
5
  "keywords": [
6
6
  "n8n-community-node-package"
7
7
  ],
8
8
  "license": "SUL-1.0",
9
- "homepage": "https://mspcopilot.io/n8n-nodes/connectwise-psa",
9
+ "homepage": "https://mspcopilot.io",
10
10
  "author": {
11
11
  "name": "Dan Buhler (j0dan)",
12
12
  "email": "dan.buhler@mspcopilot.io"
@@ -67,4 +67,4 @@
67
67
  "registry": "https://registry.npmjs.org",
68
68
  "access": "public"
69
69
  }
70
- }
70
+ }