@mspcopilot/n8n-nodes-connectwise 0.1.8 → 0.2.0

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.
@@ -117,7 +117,7 @@ class ConnectWisePsaApi {
117
117
  name: "checkForUpdates",
118
118
  type: "boolean",
119
119
  default: !0,
120
- hint: "Current version: 0.1.8 (community edition)",
120
+ hint: "Current version: 0.2.0 (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 | 📦 v0.1.8 | 🏗️ Build: ${"community".toUpperCase()} (${"prod".toUpperCase()} mode)`);
77
+ console.log(`🔌 ConnectWise PSA Node Loaded | 📦 v0.2.0 | 🏗️ 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 = 36e5;
39
+ const DOCURL_PREFIX = "mspcopilot.io/docs/n8n-nodes-connectwise", 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
  });
@@ -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: data => `🎉 ConnectWise PSA Node v${data.version} is available! <a href="https://${import_common.CHANGELOG_URL}" target="_blank">View changelog</a>` + (data.message ? `<br>${data.message}` : "")
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
@@ -49,6 +49,26 @@ const customFields = [ {
49
49
  value: "getAll",
50
50
  description: "Get many via custom endpoint (GET)",
51
51
  action: "Custom Get"
52
+ }, {
53
+ name: "POST (Create)",
54
+ value: "create",
55
+ description: "Create via custom endpoint (POST)",
56
+ action: "Custom Create"
57
+ }, {
58
+ name: "DELETE",
59
+ value: "delete",
60
+ description: "Delete via custom endpoint (DELETE)",
61
+ action: "Custom Delete"
62
+ }, {
63
+ name: "PUT (Replace)",
64
+ value: "replace",
65
+ description: "Replace via custom endpoint (PUT)",
66
+ action: "Custom Replace"
67
+ }, {
68
+ name: "PATCH (Update)",
69
+ value: "update",
70
+ description: "Update via custom endpoint (PATCH)",
71
+ action: "Custom Update"
52
72
  } ],
53
73
  default: "getAll"
54
74
  }, {
@@ -62,7 +82,7 @@ const customFields = [ {
62
82
  }
63
83
  },
64
84
  default: "",
65
- hint: `View list of endpoints in the <a href="https://${import_common2.DOCURL_PREFIX}/endpoints">documentation</a>`,
85
+ hint: `View list of endpoints in the <a href="https://${import_common2.DOCURL_PREFIX}/connectwise-api">documentation</a>`,
66
86
  placeholder: "e.g., /company/companies/:id or https://api.connectwise.com/...",
67
87
  description: "The API endpoint. Can be a full URL or a partial path. Include any parameters like the ID directly in the URL."
68
88
  }, {
@@ -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
  }
@@ -160,6 +160,10 @@ const ticketFields = [ {
160
160
  }
161
161
  },
162
162
  modes: [ {
163
+ displayName: "ID",
164
+ name: "id",
165
+ type: "string"
166
+ }, {
163
167
  displayName: "From List",
164
168
  name: "list",
165
169
  type: "list",
@@ -256,7 +260,12 @@ const ticketFields = [ {
256
260
  },
257
261
  displayOptions: {
258
262
  show: {
259
- operation: [ "create", "update" ]
263
+ operation: [ "create", "update" ],
264
+ board: [ {
265
+ _cnd: {
266
+ exists: !0
267
+ }
268
+ } ]
260
269
  }
261
270
  },
262
271
  x_detectField: "auto"
@@ -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"
@@ -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.8";
42
+ const CACHE_FILE_PATH = path.join(os.tmpdir(), "connectwise-n8n-version-cache.json"), CURRENT_VERSION = "0.2.0";
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,10 @@ 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", {
114
+ version: inMemoryCache.latest,
115
+ message: inMemoryCache.message
116
+ });
119
117
  } catch (error) {
120
118
  console.warn("⚠️ Version check failed:", error);
121
119
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mspcopilot/n8n-nodes-connectwise",
3
- "version": "0.1.8",
3
+ "version": "0.2.0",
4
4
  "description": "n8n node to integrate with ConnectWise PSA",
5
5
  "keywords": [
6
6
  "n8n-community-node-package"
@@ -67,4 +67,4 @@
67
67
  "registry": "https://registry.npmjs.org",
68
68
  "access": "public"
69
69
  }
70
- }
70
+ }