n8n-nodes-hudu 1.3.0 → 1.3.1
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/README.md +60 -27
- package/dist/nodes/Hudu/optionLoaders/asset_layouts/getAssetLayoutFields.ts +234 -234
- package/dist/nodes/Hudu/optionLoaders/asset_layouts/getCustomFieldsLayoutFields.ts +77 -77
- package/dist/nodes/Hudu/resources/assets/assets.handler.ts +395 -395
- package/dist/nodes/Hudu/utils/debugConfig.ts +132 -132
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,4 +1,28 @@
|
|
|
1
1
|
# n8n-nodes-hudu
|
|
2
|
+
This community node enables seamless integration with Hudu documentation platform in your n8n workflows, allowing you to automate and manage your IT documentation tasks.
|
|
3
|
+
|
|
4
|
+

|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
[](https://buymeacoffee.com/msoukhomlinov)
|
|
8
|
+
|
|
9
|
+
## Recent Changes
|
|
10
|
+
|
|
11
|
+
### [1.3.1] - 2025-04-21
|
|
12
|
+
- Aligned package general content such as README and documentation
|
|
13
|
+
|
|
14
|
+
### [1.3.0] - 2025-03-20
|
|
15
|
+
- Fixed missing company_id field in asset delete, archive, and unarchive operations
|
|
16
|
+
|
|
17
|
+
### [1.2.9] - 2025-01-25
|
|
18
|
+
- Improved Asset operations with enhanced UI, custom asset tags support, and optimised asset creation/update
|
|
19
|
+
- Added streamlined workflow for using asset data between operations
|
|
20
|
+
|
|
21
|
+
### [1.2.8] - 2025-01-21
|
|
22
|
+
- Fixed asset passwords create/update operations with required fields and enhanced validation
|
|
23
|
+
- Improved field validation and error handling for asset password operations
|
|
24
|
+
|
|
25
|
+
> **IMPORTANT**: When updating between versions, make sure to restart your n8n instance after the update. UI changes and new features are only picked up after a restart. The recommended update process is:
|
|
2
26
|
|
|
3
27
|
This n8n community node enables the integration of Hudu within your n8n workflows.
|
|
4
28
|
|
|
@@ -8,19 +32,39 @@ This n8n community node enables the integration of Hudu within your n8n workflow
|
|
|
8
32
|
|
|
9
33
|
This node was built against Hudu v2.34.4. Future versions of Hudu may not be 100% compatible without node updates.
|
|
10
34
|
|
|
35
|
+
[Installation](#installation)
|
|
36
|
+
[Credentials](#credentials)
|
|
37
|
+
[Features](#features)
|
|
38
|
+
[Supported Resources & Operations](#supported-resources--operations)
|
|
39
|
+
[Resources](#resources)
|
|
40
|
+
[Contributing](#contributing)
|
|
41
|
+
[Support](#support)
|
|
42
|
+
[License](#license)
|
|
43
|
+
|
|
11
44
|
## Installation
|
|
12
45
|
|
|
13
46
|
Follow the [installation guide](https://docs.n8n.io/integrations/community-nodes/installation/) in the n8n community nodes documentation.
|
|
14
47
|
|
|
15
|
-
##
|
|
48
|
+
## Credentials
|
|
49
|
+
|
|
50
|
+
To use this node, you need to:
|
|
16
51
|
|
|
17
|
-
|
|
52
|
+
1. Have a Hudu instance
|
|
53
|
+
2. Generate an API key in your Hudu settings
|
|
54
|
+
3. Configure the node with:
|
|
55
|
+
- Base URL (e.g., https://your-hudu-instance.com)
|
|
56
|
+
- API Key
|
|
18
57
|
|
|
19
|
-
|
|
20
|
-
2. Restart n8n instance
|
|
21
|
-
3. Clear browser cache if needed
|
|
58
|
+
## Features
|
|
22
59
|
|
|
23
|
-
|
|
60
|
+
- Comprehensive pagination support for all applicable resources
|
|
61
|
+
- Robust error handling and debugging capabilities
|
|
62
|
+
- Advanced filtering options with both API-side and client-side filtering
|
|
63
|
+
- Support for both single and bulk operations
|
|
64
|
+
- Dynamic loading of related resources (companies, layouts, etc.)
|
|
65
|
+
- Date range filtering with preset options
|
|
66
|
+
- Automatic type conversion and validation
|
|
67
|
+
- Debug logging for troubleshooting
|
|
24
68
|
|
|
25
69
|
## Supported Resources & Operations
|
|
26
70
|
|
|
@@ -170,27 +214,6 @@ Note: Custom field support has some limitations:
|
|
|
170
214
|
- Link to companies
|
|
171
215
|
- Filter by company and status
|
|
172
216
|
|
|
173
|
-
## Credentials
|
|
174
|
-
|
|
175
|
-
To use this node, you need to:
|
|
176
|
-
|
|
177
|
-
1. Have a Hudu instance
|
|
178
|
-
2. Generate an API key in your Hudu settings
|
|
179
|
-
3. Configure the node with:
|
|
180
|
-
- Base URL (e.g., https://your-hudu-instance.com)
|
|
181
|
-
- API Key
|
|
182
|
-
|
|
183
|
-
## Features
|
|
184
|
-
|
|
185
|
-
- Comprehensive pagination support for all applicable resources
|
|
186
|
-
- Robust error handling and debugging capabilities
|
|
187
|
-
- Advanced filtering options with both API-side and client-side filtering
|
|
188
|
-
- Support for both single and bulk operations
|
|
189
|
-
- Dynamic loading of related resources (companies, layouts, etc.)
|
|
190
|
-
- Date range filtering with preset options
|
|
191
|
-
- Automatic type conversion and validation
|
|
192
|
-
- Debug logging for troubleshooting
|
|
193
|
-
|
|
194
217
|
## Resources
|
|
195
218
|
|
|
196
219
|
- [n8n community nodes documentation](https://docs.n8n.io/integrations/community-nodes/)
|
|
@@ -215,3 +238,13 @@ Please ensure your PR:
|
|
|
215
238
|
- Includes tests if applicable
|
|
216
239
|
|
|
217
240
|
For bug reports or feature requests, please use the GitHub issues section.
|
|
241
|
+
|
|
242
|
+
## Support
|
|
243
|
+
|
|
244
|
+
If you find this node helpful and would like to support its development:
|
|
245
|
+
|
|
246
|
+
[](https://buymeacoffee.com/msoukhomlinov)
|
|
247
|
+
|
|
248
|
+
## License
|
|
249
|
+
|
|
250
|
+
[MIT](LICENSE)
|
|
@@ -1,235 +1,235 @@
|
|
|
1
|
-
import type { ILoadOptionsFunctions, IDataObject, ResourceMapperFields, FieldType, INodePropertyOptions } from 'n8n-workflow';
|
|
2
|
-
import { NodeOperationError } from 'n8n-workflow';
|
|
3
|
-
import { handleGetOperation } from '../../utils/operations';
|
|
4
|
-
import type { IAssetLayoutFieldEntity } from '../../resources/asset_layout_fields/asset_layout_fields.types';
|
|
5
|
-
import { ASSET_LAYOUT_FIELD_TYPES } from '../../utils/constants';
|
|
6
|
-
import { debugLog } from '../../utils/debugConfig';
|
|
7
|
-
import { huduApiRequest } from '../../utils/requestUtils';
|
|
8
|
-
import type { IAssetResponse } from '../../resources/assets/assets.types';
|
|
9
|
-
|
|
10
|
-
interface IAssetLayout extends IDataObject {
|
|
11
|
-
active: boolean;
|
|
12
|
-
name: string;
|
|
13
|
-
fields: IAssetLayoutFieldEntity[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface IAssetLayoutResponse extends IDataObject {
|
|
17
|
-
asset_layout: IAssetLayout;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface IMappedField {
|
|
21
|
-
id: string;
|
|
22
|
-
displayName: string;
|
|
23
|
-
required: boolean;
|
|
24
|
-
defaultMatch: boolean;
|
|
25
|
-
type: FieldType | undefined;
|
|
26
|
-
display: boolean;
|
|
27
|
-
canBeUsedToMatch: boolean;
|
|
28
|
-
description: string | undefined;
|
|
29
|
-
options?: INodePropertyOptions[];
|
|
30
|
-
label: string;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async function getFieldTypeAndOptions(
|
|
34
|
-
this: ILoadOptionsFunctions,
|
|
35
|
-
huduType: string,
|
|
36
|
-
field: IAssetLayoutFieldEntity,
|
|
37
|
-
): Promise<{ type: FieldType | undefined; options?: INodePropertyOptions[] }> {
|
|
38
|
-
debugLog('[FieldTypeMapping] Starting type mapping for field:', { huduType, field });
|
|
39
|
-
|
|
40
|
-
// Map Hudu field types to n8n types
|
|
41
|
-
switch (huduType) {
|
|
42
|
-
case ASSET_LAYOUT_FIELD_TYPES.NUMBER:
|
|
43
|
-
return { type: 'number' };
|
|
44
|
-
case ASSET_LAYOUT_FIELD_TYPES.DATE:
|
|
45
|
-
return { type: 'dateTime' };
|
|
46
|
-
case ASSET_LAYOUT_FIELD_TYPES.CHECKBOX:
|
|
47
|
-
return { type: 'boolean' };
|
|
48
|
-
case ASSET_LAYOUT_FIELD_TYPES.LIST_SELECT:
|
|
49
|
-
return { type: 'string' };
|
|
50
|
-
case ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG:
|
|
51
|
-
case ASSET_LAYOUT_FIELD_TYPES.PASSWORD:
|
|
52
|
-
case ASSET_LAYOUT_FIELD_TYPES.TEXT:
|
|
53
|
-
case ASSET_LAYOUT_FIELD_TYPES.RICH_TEXT:
|
|
54
|
-
case ASSET_LAYOUT_FIELD_TYPES.HEADING:
|
|
55
|
-
case ASSET_LAYOUT_FIELD_TYPES.WEBSITE:
|
|
56
|
-
case ASSET_LAYOUT_FIELD_TYPES.EMBED:
|
|
57
|
-
case ASSET_LAYOUT_FIELD_TYPES.EMAIL:
|
|
58
|
-
case ASSET_LAYOUT_FIELD_TYPES.PHONE:
|
|
59
|
-
case ASSET_LAYOUT_FIELD_TYPES.ADDRESS_DATA:
|
|
60
|
-
return { type: 'string' };
|
|
61
|
-
default:
|
|
62
|
-
return { type: undefined };
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
async function getLayoutFields(
|
|
67
|
-
this: ILoadOptionsFunctions,
|
|
68
|
-
includeAssetTags = false,
|
|
69
|
-
): Promise<ResourceMapperFields> {
|
|
70
|
-
debugLog('[ResourceMapping] Starting getLayoutFields');
|
|
71
|
-
|
|
72
|
-
let layoutId: string;
|
|
73
|
-
const operation = this.getNodeParameter('operation', 0) as string;
|
|
74
|
-
|
|
75
|
-
if (operation === 'update') {
|
|
76
|
-
// For update operation, get layout ID from asset details
|
|
77
|
-
const assetId = this.getNodeParameter('id', 0) as string;
|
|
78
|
-
debugLog('[ResourceMapping] Getting layout ID from asset:', assetId);
|
|
79
|
-
|
|
80
|
-
// Use huduApiRequest directly since handleGetAllOperation expects IExecuteFunctions
|
|
81
|
-
const response = await huduApiRequest.call(
|
|
82
|
-
this,
|
|
83
|
-
'GET',
|
|
84
|
-
'/assets',
|
|
85
|
-
{},
|
|
86
|
-
{ id: assetId },
|
|
87
|
-
) as IAssetResponse;
|
|
88
|
-
|
|
89
|
-
if (!response || !response.assets || !response.assets.length) {
|
|
90
|
-
throw new Error(`Asset with ID ${assetId} not found`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const asset = response.assets[0] as { asset_layout_id: number };
|
|
94
|
-
layoutId = asset.asset_layout_id.toString();
|
|
95
|
-
debugLog('[ResourceMapping] Got layout ID from asset:', layoutId);
|
|
96
|
-
} else {
|
|
97
|
-
// For create operation, get layout ID from UI
|
|
98
|
-
layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
|
|
99
|
-
debugLog('[ResourceMapping] Got layout ID from UI:', layoutId);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
debugLog('[ResourceMapping] Context:', {
|
|
103
|
-
node: this.getNode().name,
|
|
104
|
-
layoutId,
|
|
105
|
-
includeAssetTags,
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
if (!layoutId) {
|
|
109
|
-
debugLog('[ResourceMapping] No layout ID available, returning empty fields');
|
|
110
|
-
return {
|
|
111
|
-
fields: [],
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// Fetch the layout details
|
|
116
|
-
debugLog('[ResourceMapping] Fetching layout details for ID:', layoutId);
|
|
117
|
-
const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
|
|
118
|
-
debugLog('[ResourceMapping] Layout response:', layout);
|
|
119
|
-
|
|
120
|
-
// Check if layout exists
|
|
121
|
-
if (!layout || !layout.asset_layout) {
|
|
122
|
-
debugLog('[ResourceMapping] Layout not found or inaccessible');
|
|
123
|
-
throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const layoutData = layout.asset_layout;
|
|
127
|
-
const fields = layoutData.fields || [];
|
|
128
|
-
debugLog('[ResourceMapping] Found fields:', fields);
|
|
129
|
-
|
|
130
|
-
// If no fields are found
|
|
131
|
-
if (fields.length === 0) {
|
|
132
|
-
debugLog('[ResourceMapping] No fields found in layout, returning empty fields');
|
|
133
|
-
return {
|
|
134
|
-
fields: [],
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Map fields to ResourceMapperFields format
|
|
139
|
-
debugLog('[ResourceMapping] Starting field mapping process');
|
|
140
|
-
const mappedFields = await Promise.all(fields
|
|
141
|
-
.filter((field: IAssetLayoutFieldEntity) => {
|
|
142
|
-
const isAssetTag = field.field_type === ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG;
|
|
143
|
-
const include = !field.is_destroyed && (includeAssetTags ? isAssetTag : !isAssetTag);
|
|
144
|
-
debugLog(`[ResourceMapping] Filtering field ${field.label}:`, { include, field });
|
|
145
|
-
return include;
|
|
146
|
-
})
|
|
147
|
-
.map(async (field: IAssetLayoutFieldEntity) => {
|
|
148
|
-
debugLog(`[ResourceMapping] Processing field ${field.label} of type ${field.field_type}`);
|
|
149
|
-
const { type, options } = await getFieldTypeAndOptions.call(this, field.field_type, field);
|
|
150
|
-
debugLog(`[ResourceMapping] Mapped field ${field.label}:`, { field, type, options });
|
|
151
|
-
|
|
152
|
-
const mappedField: IMappedField = {
|
|
153
|
-
id: field.id.toString(),
|
|
154
|
-
displayName: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
|
|
155
|
-
required: field.required || false,
|
|
156
|
-
defaultMatch: false,
|
|
157
|
-
type,
|
|
158
|
-
display: true,
|
|
159
|
-
canBeUsedToMatch: true,
|
|
160
|
-
description: field.hint || undefined,
|
|
161
|
-
label: field.label,
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
if (options) {
|
|
165
|
-
debugLog(`[ResourceMapping] Adding options to field ${field.label}:`, options);
|
|
166
|
-
mappedField.options = options;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
debugLog(`[ResourceMapping] Final mapped field ${field.label}:`, mappedField);
|
|
170
|
-
return mappedField;
|
|
171
|
-
}))
|
|
172
|
-
.then(fields => fields.sort((a, b) => a.displayName.localeCompare(b.displayName)));
|
|
173
|
-
|
|
174
|
-
debugLog('[ResourceMapping] Final mapped fields:', mappedFields);
|
|
175
|
-
return {
|
|
176
|
-
fields: mappedFields,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export async function mapAssetLayoutFieldsForResource(
|
|
181
|
-
this: ILoadOptionsFunctions,
|
|
182
|
-
): Promise<ResourceMapperFields> {
|
|
183
|
-
try {
|
|
184
|
-
const parameterName = this.getNodeParameter('name', 0) as string;
|
|
185
|
-
const includeAssetTags = parameterName === 'tagFieldMappings';
|
|
186
|
-
return getLayoutFields.call(this, includeAssetTags);
|
|
187
|
-
} catch (error) {
|
|
188
|
-
debugLog('[ResourceMapping] Error in getAssetLayoutFields:', error);
|
|
189
|
-
if (error instanceof NodeOperationError) {
|
|
190
|
-
throw error;
|
|
191
|
-
}
|
|
192
|
-
throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export async function getAssetLayoutFields(
|
|
197
|
-
this: ILoadOptionsFunctions,
|
|
198
|
-
): Promise<INodePropertyOptions[]> {
|
|
199
|
-
try {
|
|
200
|
-
const layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
|
|
201
|
-
|
|
202
|
-
debugLog('[FieldLoading] Starting getAssetLayoutFields for layout:', layoutId);
|
|
203
|
-
|
|
204
|
-
if (!layoutId) {
|
|
205
|
-
return [];
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// Fetch the layout details
|
|
209
|
-
const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
|
|
210
|
-
|
|
211
|
-
if (!layout || !layout.asset_layout) {
|
|
212
|
-
throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const layoutData = layout.asset_layout;
|
|
216
|
-
const fields = layoutData.fields || [];
|
|
217
|
-
|
|
218
|
-
// Map fields to INodePropertyOptions format
|
|
219
|
-
return fields
|
|
220
|
-
.filter((field: IAssetLayoutFieldEntity) => !field.is_destroyed)
|
|
221
|
-
.map((field: IAssetLayoutFieldEntity) => ({
|
|
222
|
-
name: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
|
|
223
|
-
value: field.id.toString(),
|
|
224
|
-
description: field.hint || undefined,
|
|
225
|
-
}))
|
|
226
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
227
|
-
|
|
228
|
-
} catch (error) {
|
|
229
|
-
debugLog('[FieldLoading] Error in getAssetLayoutFields:', error);
|
|
230
|
-
if (error instanceof NodeOperationError) {
|
|
231
|
-
throw error;
|
|
232
|
-
}
|
|
233
|
-
throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
|
|
234
|
-
}
|
|
1
|
+
import type { ILoadOptionsFunctions, IDataObject, ResourceMapperFields, FieldType, INodePropertyOptions } from 'n8n-workflow';
|
|
2
|
+
import { NodeOperationError } from 'n8n-workflow';
|
|
3
|
+
import { handleGetOperation } from '../../utils/operations';
|
|
4
|
+
import type { IAssetLayoutFieldEntity } from '../../resources/asset_layout_fields/asset_layout_fields.types';
|
|
5
|
+
import { ASSET_LAYOUT_FIELD_TYPES } from '../../utils/constants';
|
|
6
|
+
import { debugLog } from '../../utils/debugConfig';
|
|
7
|
+
import { huduApiRequest } from '../../utils/requestUtils';
|
|
8
|
+
import type { IAssetResponse } from '../../resources/assets/assets.types';
|
|
9
|
+
|
|
10
|
+
interface IAssetLayout extends IDataObject {
|
|
11
|
+
active: boolean;
|
|
12
|
+
name: string;
|
|
13
|
+
fields: IAssetLayoutFieldEntity[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface IAssetLayoutResponse extends IDataObject {
|
|
17
|
+
asset_layout: IAssetLayout;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface IMappedField {
|
|
21
|
+
id: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
required: boolean;
|
|
24
|
+
defaultMatch: boolean;
|
|
25
|
+
type: FieldType | undefined;
|
|
26
|
+
display: boolean;
|
|
27
|
+
canBeUsedToMatch: boolean;
|
|
28
|
+
description: string | undefined;
|
|
29
|
+
options?: INodePropertyOptions[];
|
|
30
|
+
label: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function getFieldTypeAndOptions(
|
|
34
|
+
this: ILoadOptionsFunctions,
|
|
35
|
+
huduType: string,
|
|
36
|
+
field: IAssetLayoutFieldEntity,
|
|
37
|
+
): Promise<{ type: FieldType | undefined; options?: INodePropertyOptions[] }> {
|
|
38
|
+
debugLog('[FieldTypeMapping] Starting type mapping for field:', { huduType, field });
|
|
39
|
+
|
|
40
|
+
// Map Hudu field types to n8n types
|
|
41
|
+
switch (huduType) {
|
|
42
|
+
case ASSET_LAYOUT_FIELD_TYPES.NUMBER:
|
|
43
|
+
return { type: 'number' };
|
|
44
|
+
case ASSET_LAYOUT_FIELD_TYPES.DATE:
|
|
45
|
+
return { type: 'dateTime' };
|
|
46
|
+
case ASSET_LAYOUT_FIELD_TYPES.CHECKBOX:
|
|
47
|
+
return { type: 'boolean' };
|
|
48
|
+
case ASSET_LAYOUT_FIELD_TYPES.LIST_SELECT:
|
|
49
|
+
return { type: 'string' };
|
|
50
|
+
case ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG:
|
|
51
|
+
case ASSET_LAYOUT_FIELD_TYPES.PASSWORD:
|
|
52
|
+
case ASSET_LAYOUT_FIELD_TYPES.TEXT:
|
|
53
|
+
case ASSET_LAYOUT_FIELD_TYPES.RICH_TEXT:
|
|
54
|
+
case ASSET_LAYOUT_FIELD_TYPES.HEADING:
|
|
55
|
+
case ASSET_LAYOUT_FIELD_TYPES.WEBSITE:
|
|
56
|
+
case ASSET_LAYOUT_FIELD_TYPES.EMBED:
|
|
57
|
+
case ASSET_LAYOUT_FIELD_TYPES.EMAIL:
|
|
58
|
+
case ASSET_LAYOUT_FIELD_TYPES.PHONE:
|
|
59
|
+
case ASSET_LAYOUT_FIELD_TYPES.ADDRESS_DATA:
|
|
60
|
+
return { type: 'string' };
|
|
61
|
+
default:
|
|
62
|
+
return { type: undefined };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function getLayoutFields(
|
|
67
|
+
this: ILoadOptionsFunctions,
|
|
68
|
+
includeAssetTags = false,
|
|
69
|
+
): Promise<ResourceMapperFields> {
|
|
70
|
+
debugLog('[ResourceMapping] Starting getLayoutFields');
|
|
71
|
+
|
|
72
|
+
let layoutId: string;
|
|
73
|
+
const operation = this.getNodeParameter('operation', 0) as string;
|
|
74
|
+
|
|
75
|
+
if (operation === 'update') {
|
|
76
|
+
// For update operation, get layout ID from asset details
|
|
77
|
+
const assetId = this.getNodeParameter('id', 0) as string;
|
|
78
|
+
debugLog('[ResourceMapping] Getting layout ID from asset:', assetId);
|
|
79
|
+
|
|
80
|
+
// Use huduApiRequest directly since handleGetAllOperation expects IExecuteFunctions
|
|
81
|
+
const response = await huduApiRequest.call(
|
|
82
|
+
this,
|
|
83
|
+
'GET',
|
|
84
|
+
'/assets',
|
|
85
|
+
{},
|
|
86
|
+
{ id: assetId },
|
|
87
|
+
) as IAssetResponse;
|
|
88
|
+
|
|
89
|
+
if (!response || !response.assets || !response.assets.length) {
|
|
90
|
+
throw new Error(`Asset with ID ${assetId} not found`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const asset = response.assets[0] as { asset_layout_id: number };
|
|
94
|
+
layoutId = asset.asset_layout_id.toString();
|
|
95
|
+
debugLog('[ResourceMapping] Got layout ID from asset:', layoutId);
|
|
96
|
+
} else {
|
|
97
|
+
// For create operation, get layout ID from UI
|
|
98
|
+
layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
|
|
99
|
+
debugLog('[ResourceMapping] Got layout ID from UI:', layoutId);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
debugLog('[ResourceMapping] Context:', {
|
|
103
|
+
node: this.getNode().name,
|
|
104
|
+
layoutId,
|
|
105
|
+
includeAssetTags,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!layoutId) {
|
|
109
|
+
debugLog('[ResourceMapping] No layout ID available, returning empty fields');
|
|
110
|
+
return {
|
|
111
|
+
fields: [],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Fetch the layout details
|
|
116
|
+
debugLog('[ResourceMapping] Fetching layout details for ID:', layoutId);
|
|
117
|
+
const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
|
|
118
|
+
debugLog('[ResourceMapping] Layout response:', layout);
|
|
119
|
+
|
|
120
|
+
// Check if layout exists
|
|
121
|
+
if (!layout || !layout.asset_layout) {
|
|
122
|
+
debugLog('[ResourceMapping] Layout not found or inaccessible');
|
|
123
|
+
throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const layoutData = layout.asset_layout;
|
|
127
|
+
const fields = layoutData.fields || [];
|
|
128
|
+
debugLog('[ResourceMapping] Found fields:', fields);
|
|
129
|
+
|
|
130
|
+
// If no fields are found
|
|
131
|
+
if (fields.length === 0) {
|
|
132
|
+
debugLog('[ResourceMapping] No fields found in layout, returning empty fields');
|
|
133
|
+
return {
|
|
134
|
+
fields: [],
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Map fields to ResourceMapperFields format
|
|
139
|
+
debugLog('[ResourceMapping] Starting field mapping process');
|
|
140
|
+
const mappedFields = await Promise.all(fields
|
|
141
|
+
.filter((field: IAssetLayoutFieldEntity) => {
|
|
142
|
+
const isAssetTag = field.field_type === ASSET_LAYOUT_FIELD_TYPES.ASSET_TAG;
|
|
143
|
+
const include = !field.is_destroyed && (includeAssetTags ? isAssetTag : !isAssetTag);
|
|
144
|
+
debugLog(`[ResourceMapping] Filtering field ${field.label}:`, { include, field });
|
|
145
|
+
return include;
|
|
146
|
+
})
|
|
147
|
+
.map(async (field: IAssetLayoutFieldEntity) => {
|
|
148
|
+
debugLog(`[ResourceMapping] Processing field ${field.label} of type ${field.field_type}`);
|
|
149
|
+
const { type, options } = await getFieldTypeAndOptions.call(this, field.field_type, field);
|
|
150
|
+
debugLog(`[ResourceMapping] Mapped field ${field.label}:`, { field, type, options });
|
|
151
|
+
|
|
152
|
+
const mappedField: IMappedField = {
|
|
153
|
+
id: field.id.toString(),
|
|
154
|
+
displayName: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
|
|
155
|
+
required: field.required || false,
|
|
156
|
+
defaultMatch: false,
|
|
157
|
+
type,
|
|
158
|
+
display: true,
|
|
159
|
+
canBeUsedToMatch: true,
|
|
160
|
+
description: field.hint || undefined,
|
|
161
|
+
label: field.label,
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
if (options) {
|
|
165
|
+
debugLog(`[ResourceMapping] Adding options to field ${field.label}:`, options);
|
|
166
|
+
mappedField.options = options;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
debugLog(`[ResourceMapping] Final mapped field ${field.label}:`, mappedField);
|
|
170
|
+
return mappedField;
|
|
171
|
+
}))
|
|
172
|
+
.then(fields => fields.sort((a, b) => a.displayName.localeCompare(b.displayName)));
|
|
173
|
+
|
|
174
|
+
debugLog('[ResourceMapping] Final mapped fields:', mappedFields);
|
|
175
|
+
return {
|
|
176
|
+
fields: mappedFields,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function mapAssetLayoutFieldsForResource(
|
|
181
|
+
this: ILoadOptionsFunctions,
|
|
182
|
+
): Promise<ResourceMapperFields> {
|
|
183
|
+
try {
|
|
184
|
+
const parameterName = this.getNodeParameter('name', 0) as string;
|
|
185
|
+
const includeAssetTags = parameterName === 'tagFieldMappings';
|
|
186
|
+
return getLayoutFields.call(this, includeAssetTags);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
debugLog('[ResourceMapping] Error in getAssetLayoutFields:', error);
|
|
189
|
+
if (error instanceof NodeOperationError) {
|
|
190
|
+
throw error;
|
|
191
|
+
}
|
|
192
|
+
throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export async function getAssetLayoutFields(
|
|
197
|
+
this: ILoadOptionsFunctions,
|
|
198
|
+
): Promise<INodePropertyOptions[]> {
|
|
199
|
+
try {
|
|
200
|
+
const layoutId = this.getNodeParameter('asset_layout_id', 0) as string;
|
|
201
|
+
|
|
202
|
+
debugLog('[FieldLoading] Starting getAssetLayoutFields for layout:', layoutId);
|
|
203
|
+
|
|
204
|
+
if (!layoutId) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Fetch the layout details
|
|
209
|
+
const layout = await handleGetOperation.call(this, '/asset_layouts', layoutId) as IAssetLayoutResponse;
|
|
210
|
+
|
|
211
|
+
if (!layout || !layout.asset_layout) {
|
|
212
|
+
throw new NodeOperationError(this.getNode(), 'Asset layout not found or inaccessible');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const layoutData = layout.asset_layout;
|
|
216
|
+
const fields = layoutData.fields || [];
|
|
217
|
+
|
|
218
|
+
// Map fields to INodePropertyOptions format
|
|
219
|
+
return fields
|
|
220
|
+
.filter((field: IAssetLayoutFieldEntity) => !field.is_destroyed)
|
|
221
|
+
.map((field: IAssetLayoutFieldEntity) => ({
|
|
222
|
+
name: `${field.label} (${field.field_type})${!layoutData.active ? ' [Archived Layout]' : ''}`,
|
|
223
|
+
value: field.id.toString(),
|
|
224
|
+
description: field.hint || undefined,
|
|
225
|
+
}))
|
|
226
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
227
|
+
|
|
228
|
+
} catch (error) {
|
|
229
|
+
debugLog('[FieldLoading] Error in getAssetLayoutFields:', error);
|
|
230
|
+
if (error instanceof NodeOperationError) {
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
throw new NodeOperationError(this.getNode(), `Failed to load asset layout fields: ${(error as Error).message}`);
|
|
234
|
+
}
|
|
235
235
|
}
|