UNPKG

n8n-nodes-arubacentral

Version:

n8n community node for Aruba Central API integration with comprehensive monitoring, configuration, and management capabilities

881 lines (773 loc) 33.2 kB
# Aruba Central API Node for n8n: Development & Troubleshooting Guide You're working on an n8n integration node for Aruba Central API. This comprehensive guide will help you continue development, implement new features, and troubleshoot issues with the codebase. ## Project Structure The project follows a domain-driven design with the following structure: ``` . ├── ArubaCentral.node.ts ├── api │ ├── aiops │ │ ├── aiops.operations.ts │ │ ├── aiops.types.ts │ │ ├── connectivity │ │ │ ├── connectivity.common.ts │ │ │ ├── connectivity.descriptions.ts │ │ │ ├── connectivity.methods.ts │ │ │ ├── connectivity.operations.ts │ │ │ ├── getGlobalConnectivity.descriptions.ts │ │ │ ├── getGlobalConnectivity.methods.ts │ │ │ ├── getGroupConnectivity.descriptions.ts │ │ │ ├── getGroupConnectivity.methods.ts │ │ │ ├── getSiteConnectivity.descriptions.ts │ │ │ └── getSiteConnectivity.methods.ts │ │ ├── insight │ │ │ ├── insight.common.ts │ │ │ ├── insight.descriptions.ts │ │ │ ├── insight.operations.ts │ │ │ ├── insights.methods.ts │ │ │ ├── listApInsights.descriptions.ts │ │ │ ├── listApInsights.methods.ts │ │ │ ├── listClientInsights.descriptions.ts │ │ │ ├── listClientInsights.methods.ts │ │ │ ├── listGlobalInsights.descriptions.ts │ │ │ ├── listGlobalInsights.methods.ts │ │ │ ├── listSiteInsights.descriptions.ts │ │ │ └── listSiteInsights.methods.ts │ │ └── insights │ ├── configuration │ │ ├── ap │ │ │ ├── ap.common.ts │ │ │ ├── apCli.methods.ts │ │ │ ├── apConfiguration.types.ts │ │ │ ├── apSettings.methods.ts │ │ │ ├── apSettings.types.ts │ │ │ ├── apSpecialized.methods.ts │ │ │ ├── armConfig.methods.ts │ │ │ ├── dirtyDiff.methods.ts │ │ │ ├── dot11aRadio.methods.ts │ │ │ ├── dot11gRadio.methods.ts │ │ │ ├── getApCli.descriptions.ts │ │ │ ├── getApSettings.descriptions.ts │ │ │ ├── iapVariables.methods.ts │ │ │ ├── replaceApCli.descriptions.ts │ │ │ ├── replaceApSettings.descriptions.ts │ │ │ ├── setApHostname.descriptions.ts │ │ │ ├── setApSsids.descriptions.ts │ │ │ ├── sshCredentials.methods.ts │ │ │ └── systemConfig.methods.ts │ │ ├── configuration.operations.ts │ │ └── devices │ │ ├── devices.common.ts │ │ ├── devices.types.ts │ │ ├── getDeviceGroup.descriptions.ts │ │ ├── group.methods.ts │ │ └── moveDevicesToGroup.descriptions.ts │ ├── descriptions.ts │ ├── firmware │ │ ├── compliance │ │ │ ├── clearFirmwareComplianceVersion.methods.ts │ │ │ ├── compliance.common.ts │ │ │ ├── compliance.methods.ts │ │ │ ├── getFirmwareComplianceVersion.descriptions.ts │ │ │ ├── getFirmwareComplianceVersion.methods.ts │ │ │ ├── setFirmwareComplianceVersion.descriptions.ts │ │ │ └── setFirmwareComplianceVersion.methods.ts │ │ ├── devices │ │ │ ├── devices.common.ts │ │ │ ├── devices.methods.ts │ │ │ ├── getFirmwareDeviceDetails.descriptions.ts │ │ │ ├── getFirmwareDeviceDetails.methods.ts │ │ │ ├── getFirmwareDevicesList.descriptions.ts │ │ │ └── getFirmwareDevicesList.methods.ts │ │ ├── firmware.common.ts │ │ ├── firmware.operations.ts │ │ ├── firmware.types.ts │ │ ├── status │ │ │ ├── getFirmwareStatus.descriptions.ts │ │ │ ├── getFirmwareStatus.methods.ts │ │ │ ├── status.common.ts │ │ │ └── status.methods.ts │ │ ├── upgrades │ │ │ ├── cancelFirmwareUpgrade.descriptions.ts │ │ │ ├── cancelFirmwareUpgrade.methods.ts │ │ │ ├── upgradeFirmware.descriptions.ts │ │ │ ├── upgradeFirmware.methods.ts │ │ │ ├── upgrades.common.ts │ │ │ └── upgrades.methods.ts │ │ └── versions │ │ ├── getFirmwareVersion.descriptions.ts │ │ ├── getFirmwareVersion.methods.ts │ │ ├── getFirmwareVersions.descriptions.ts │ │ ├── getFirmwareVersions.methods.ts │ │ ├── versions.common.ts │ │ └── versions.methods.ts │ ├── inventory │ │ ├── devices │ │ │ ├── addDevice.descriptions.ts │ │ │ ├── addDevice.methods.ts │ │ │ ├── archiveDevices.descriptions.ts │ │ │ ├── archiveDevices.methods.ts │ │ │ ├── deleteDevice.descriptions.ts │ │ │ ├── deleteDevice.methods.ts │ │ │ ├── devices.common.ts │ │ │ ├── devices.methods.ts │ │ │ ├── getArchivedDevices.descriptions.ts │ │ │ ├── getArchivedDevices.methods.ts │ │ │ ├── getDeviceStats.descriptions.ts │ │ │ ├── getDeviceStats.methods.ts │ │ │ ├── getDevices.descriptions.ts │ │ │ ├── getDevices.methods.ts │ │ │ ├── unarchiveDevices.descriptions.ts │ │ │ ├── unarchiveDevices.methods.ts │ │ │ ├── verifyDevice.descriptions.ts │ │ │ └── verifyDevice.methods.ts │ │ ├── inventory.operations.ts │ │ └── inventory.types.ts │ ├── monitoring │ │ ├── ap │ │ │ ├── ap.descriptions.ts │ │ │ ├── ap.methods.ts │ │ │ ├── ap.types.ts │ │ │ └── operations │ │ │ ├── deleteAP.descriptions.ts │ │ │ ├── deleteAP.methods.ts │ │ │ ├── getAPBandwidthUsage.descriptions.ts │ │ │ ├── getAPBandwidthUsage.methods.ts │ │ │ ├── getAPDetails.descriptions.ts │ │ │ ├── getAPDetails.methods.ts │ │ │ ├── getAPRFSummary.descriptions.ts │ │ │ ├── getAPRFSummary.methods.ts │ │ │ ├── getAPs.descriptions.ts │ │ │ ├── getAPs.methods.ts │ │ │ ├── getBSSIDs.descriptions.ts │ │ │ ├── getBSSIDs.methods.ts │ │ │ ├── getTopNAPs.descriptions.ts │ │ │ └── getTopNAPs.methods.ts │ │ ├── client │ │ │ ├── client.descriptions.ts │ │ │ ├── client.methods.ts │ │ │ ├── client.types.ts │ │ │ └── operations │ │ │ ├── getClientBandwidthUsage.descriptions.ts │ │ │ ├── getClientBandwidthUsage.methods.ts │ │ │ ├── getClientDetails.descriptions.ts │ │ │ ├── getClientDetails.methods.ts │ │ │ ├── getClientMobilityTrail.descriptions.ts │ │ │ ├── getClientMobilityTrail.methods.ts │ │ │ ├── getClientsCount.descriptions.ts │ │ │ ├── getClientsCount.methods.ts │ │ │ ├── getTopNClients.descriptions.ts │ │ │ ├── getTopNClients.methods.ts │ │ │ ├── getUnifiedClients.descriptions.ts │ │ │ ├── getUnifiedClients.methods.ts │ │ │ ├── getWiredClientDetails.descriptions.ts │ │ │ ├── getWiredClientDetails.methods.ts │ │ │ ├── getWiredClients.descriptions.ts │ │ │ ├── getWiredClients.methods.ts │ │ │ ├── getWirelessClientDetails.descriptions.ts │ │ │ ├── getWirelessClientDetails.methods.ts │ │ │ ├── getWirelessClients.descriptions.ts │ │ │ └── getWirelessClients.methods.ts │ │ ├── events │ │ │ ├── events.descriptions.ts │ │ │ ├── events.methods.ts │ │ │ ├── events.types.ts │ │ │ └── operations │ │ │ ├── getEvents.descriptions.ts │ │ │ └── getEvents.methods.ts │ │ ├── gateway │ │ │ ├── gateway.descriptions.ts │ │ │ ├── gateway.methods.ts │ │ │ ├── gateway.types.ts │ │ │ └── operations │ │ │ ├── deleteGateway.descriptions.ts │ │ │ ├── deleteGateway.methods.ts │ │ │ ├── getClients.descriptions.ts │ │ │ ├── getClients.methods.ts │ │ │ ├── getDetail.descriptions.ts │ │ │ ├── getDetail.methods.ts │ │ │ ├── getDistribution.descriptions.ts │ │ │ ├── getDistribution.methods.ts │ │ │ ├── getErrors.descriptions.ts │ │ │ ├── getErrors.methods.ts │ │ │ ├── getGateway.descriptions.ts │ │ │ ├── getGateway.methods.ts │ │ │ ├── getGateways.descriptions.ts │ │ │ ├── getGateways.methods.ts │ │ │ ├── getInfo.descriptions.ts │ │ │ ├── getInfo.methods.ts │ │ │ ├── getPools.descriptions.ts │ │ │ ├── getPools.methods.ts │ │ │ ├── getPorts.descriptions.ts │ │ │ ├── getPorts.methods.ts │ │ │ ├── getStats.descriptions.ts │ │ │ ├── getStats.methods.ts │ │ │ ├── getTunnels.descriptions.ts │ │ │ ├── getTunnels.methods.ts │ │ │ ├── getUsage.descriptions.ts │ │ │ └── getUsage.methods.ts │ │ ├── label │ │ │ ├── label.descriptions.ts │ │ │ ├── label.methods.ts │ │ │ ├── label.types.ts │ │ │ └── operations │ │ │ ├── deleteDevices.descriptions.ts │ │ │ ├── deleteDevices.methods.ts │ │ │ ├── deleteLabel.descriptions.ts │ │ │ ├── deleteLabel.methods.ts │ │ │ ├── getCategories.descriptions.ts │ │ │ ├── getCategories.methods.ts │ │ │ ├── getLabel.descriptions.ts │ │ │ ├── getLabel.methods.ts │ │ │ ├── getLabels.descriptions.ts │ │ │ ├── getLabels.methods.ts │ │ │ ├── patchLabel.descriptions.ts │ │ │ ├── patchLabel.methods.ts │ │ │ ├── postDevices.descriptions.ts │ │ │ ├── postDevices.methods.ts │ │ │ ├── postLabel.descriptions.ts │ │ │ └── postLabel.methods.ts │ │ ├── mobilitycontroller │ │ │ ├── mobilitycontroller.descriptions.ts │ │ │ ├── mobilitycontroller.methods.ts │ │ │ ├── mobilitycontroller.types.ts │ │ │ └── operations │ │ │ ├── deleteSerial.descriptions.ts │ │ │ ├── deleteSerial.methods.ts │ │ │ ├── getInfo.descriptions.ts │ │ │ ├── getInfo.methods.ts │ │ │ ├── getMcs.descriptions.ts │ │ │ ├── getMcs.methods.ts │ │ │ ├── getMobility_controllers.descriptions.ts │ │ │ ├── getMobility_controllers.methods.ts │ │ │ ├── getPorts.descriptions.ts │ │ │ ├── getPorts.methods.ts │ │ │ ├── getSerial.descriptions.ts │ │ │ ├── getSerial.methods.ts │ │ │ ├── getUsage.descriptions.ts │ │ │ └── getUsage.methods.ts │ │ ├── monitoring.descriptions.ts │ │ ├── monitoring.methods.ts │ │ ├── monitoring.resources.ts │ │ ├── network │ │ │ ├── network.descriptions.ts │ │ │ ├── network.methods.ts │ │ │ ├── network.types.ts │ │ │ └── operations │ │ │ ├── getBandwidth_usage.descriptions.ts │ │ │ ├── getBandwidth_usage.methods.ts │ │ │ ├── getNetwork_name.descriptions.ts │ │ │ ├── getNetwork_name.methods.ts │ │ │ ├── getNetworks.descriptions.ts │ │ │ └── getNetworks.methods.ts │ │ ├── site │ │ │ ├── operations │ │ │ │ ├── associateSiteToDevice.descriptions.ts │ │ │ │ ├── associateSiteToDevice.methods.ts │ │ │ │ ├── associateSiteToDevices.descriptions.ts │ │ │ │ ├── associateSiteToDevices.methods.ts │ │ │ │ ├── createSite.descriptions.ts │ │ │ │ ├── createSite.methods.ts │ │ │ │ ├── deleteSite.descriptions.ts │ │ │ │ ├── deleteSite.methods.ts │ │ │ │ ├── getSiteDetails.descriptions.ts │ │ │ │ ├── getSiteDetails.methods.ts │ │ │ │ ├── getSites.descriptions.ts │ │ │ │ ├── getSites.methods.ts │ │ │ │ ├── unassociateSiteFromDevice.descriptions.ts │ │ │ │ ├── unassociateSiteFromDevice.methods.ts │ │ │ │ ├── unassociateSiteFromDevices.descriptions.ts │ │ │ │ ├── unassociateSiteFromDevices.methods.ts │ │ │ │ ├── updateSite.descriptions.ts │ │ │ │ └── updateSite.methods.ts │ │ │ ├── site.descriptions.ts │ │ │ ├── site.methods.ts │ │ │ └── site.types.ts │ │ ├── swarm │ │ │ ├── operations │ │ │ │ ├── getSwarm.descriptions.ts │ │ │ │ ├── getSwarm.methods.ts │ │ │ │ ├── getSwarms.descriptions.ts │ │ │ │ ├── getSwarms.methods.ts │ │ │ │ ├── getTopn.descriptions.ts │ │ │ │ └── getTopn.methods.ts │ │ │ ├── swarm.descriptions.ts │ │ │ ├── swarm.methods.ts │ │ │ └── swarm.types.ts │ │ ├── switch │ │ │ ├── operations │ │ │ │ ├── deleteStack.descriptions.ts │ │ │ │ ├── deleteStack.methods.ts │ │ │ │ ├── deleteSwitch.descriptions.ts │ │ │ │ ├── deleteSwitch.methods.ts │ │ │ │ ├── getDetail.descriptions.ts │ │ │ │ ├── getDetail.methods.ts │ │ │ │ ├── getErrors.descriptions.ts │ │ │ │ ├── getErrors.methods.ts │ │ │ │ ├── getInfo.descriptions.ts │ │ │ │ ├── getInfo.methods.ts │ │ │ │ ├── getNeighbors.descriptions.ts │ │ │ │ ├── getNeighbors.methods.ts │ │ │ │ ├── getPorts.descriptions.ts │ │ │ │ ├── getPorts.methods.ts │ │ │ │ ├── getStack.descriptions.ts │ │ │ │ ├── getStack.methods.ts │ │ │ │ ├── getStacks.descriptions.ts │ │ │ │ ├── getStacks.methods.ts │ │ │ │ ├── getSwitch.descriptions.ts │ │ │ │ ├── getSwitch.methods.ts │ │ │ │ ├── getSwitches.descriptions.ts │ │ │ │ ├── getSwitches.methods.ts │ │ │ │ ├── getTopn.descriptions.ts │ │ │ │ ├── getTopn.methods.ts │ │ │ │ ├── getUsage.descriptions.ts │ │ │ │ ├── getUsage.methods.ts │ │ │ │ ├── getVlan.descriptions.ts │ │ │ │ └── getVlan.methods.ts │ │ │ ├── switch.descriptions.ts │ │ │ ├── switch.methods.ts │ │ │ └── switch.types.ts │ │ └── vpn │ │ ├── operations │ │ │ ├── getInfo.descriptions.ts │ │ │ ├── getInfo.methods.ts │ │ │ ├── postUsage.descriptions.ts │ │ │ └── postUsage.methods.ts │ │ ├── vpn.descriptions.ts │ │ ├── vpn.methods.ts │ │ └── vpn.types.ts │ ├── operations.ts │ └── topology │ ├── topology │ │ ├── getApNeighbors.descriptions.ts │ │ ├── getApNeighbors.methods.ts │ │ ├── getDeviceDetails.descriptions.ts │ │ ├── getDeviceDetails.methods.ts │ │ ├── getEdgeDetails.descriptions.ts │ │ ├── getEdgeDetails.methods.ts │ │ ├── getEdgeDetailsV2.descriptions.ts │ │ ├── getEdgeDetailsV2.methods.ts │ │ ├── getSiteTopology.descriptions.ts │ │ ├── getSiteTopology.methods.ts │ │ ├── getSiteVlans.descriptions.ts │ │ ├── getSiteVlans.methods.ts │ │ ├── getTunnelDetails.descriptions.ts │ │ ├── getTunnelDetails.methods.ts │ │ ├── getUnreachableDevices.descriptions.ts │ │ ├── getUnreachableDevices.methods.ts │ │ ├── getUplinkDetails.descriptions.ts │ │ ├── getUplinkDetails.methods.ts │ │ ├── topology.common.ts │ │ ├── topology.methods.ts │ │ └── topology.operations.ts │ ├── topology.operations.ts │ └── topology.types.ts ├── arubaCentral.svg ├── credentials │ └── ArubaCentralOAuth2Api.credentials.ts ├── docs │ └── Instructions.md ├── helpers │ ├── apiRequest.ts │ ├── deduplicate.ts │ ├── errorHandler.ts │ ├── executeOperation.ts │ ├── formatter.ts │ ├── logger.ts │ ├── pagination.ts │ ├── responseFormatter.ts │ └── validation.ts ├── shared │ ├── interfaces.ts │ └── utilities.ts └── tests └── monitoring ├── ap.test.ts └── client.test.ts ``` Each domain directory (e.g., api/monitoring/) follows this structure: ``` . ├── resource/ # e.g., ap, client, switch │ ├── resource.common.ts # Shared utilities for the resource │ ├── resource.descriptions.ts # Resource Description File │ ├── resource.methods.ts # API method implementations │ ├── resource.types.ts # Resource-specific types │ └── operations/ # Individual operation implementations │ ├── operation.methods.ts │ └── operation.descriptions.ts ``` ## Architecture & Design Pattern The node follows a domain-driven design pattern that separates: 1. **Domain Logic**: Each API domain (monitoring, firmware, etc.) has its own directory in `api/` 2. **Resource Implementation**: Each resource within a domain has its own directory with dedicated files 3. **Operation Handlers**: Individual operations are implemented in separate files for clarity 4. **UI Descriptions**: Individual resource definitions are implemented in separate files for clarity 5. **Shared Components**: Common utilities and types are organized in `shared/` and `helpers/` ### Operation Flow 1. User selects Domain → Resource → Operation in n8n UI 2. `ArubaCentral.node.ts` routes to appropriate domain handler 3. Domain handler delegates to resource implementation 4. Resource implementation executes specific operation 5. Response is formatted and returned to n8n ## Adding New API Operations Now, let's define a clear pattern for the entire integration that follows your desired structure: 1. Operation Level Files For each operation like getSites, we need just two files: Operation Method File (operations/getSites.methods.ts): ```typescript import { IExecuteFunctions, INodeExecutionData } from 'n8n-workflow'; import { apiRequest } from '../../../../helpers/apiRequest'; import { logger } from '../../../../helpers/logger'; import { handleApiError } from '../../../../helpers/errorHandler'; /** * Get a list of sites from Aruba Central * * @param this The n8n execution context * @returns Formatted list of sites */ export async function getSites(this: IExecuteFunctions): Promise<INodeExecutionData[]> { try { // Implementation here } catch (error) { return handleApiError.call(this, error, 'Failed to get sites'); } } ``` Operation Description File (operations/getSites.descriptions.ts): ```typescript import { INodeProperties } from 'n8n-workflow'; /** * UI parameters for the getSites operation */ export const getSitesDescription: INodeProperties[] = [ { displayName: 'Return All', name: 'returnAll', type: 'boolean', displayOptions: { show: { resource: ['site'], domain: ['monitoring'], operation: ['getSites'], }, }, default: false, description: 'Whether to return all results or only up to a given limit', }, // More parameters... ]; ``` 2. Resource Level Files For each resource like site, we need three files: Resource Method File (site/site.methods.ts): ```typescript import { IExecuteFunctions } from 'n8n-workflow'; import { getSites } from './operations/getSites.methods'; import { createSite } from './operations/createSite.methods'; // Import other operations /** * All operations available for the Site resource */ export const siteOperations = { getSites, createSite, // Other operations }; /** * Check if a site operation exists */ export function hasSiteOperation(operation: string): boolean { return !!siteOperations[operation as keyof typeof siteOperations]; } ``` Resource Description File (site/site.descriptions.ts): ```typescript import { INodeProperties } from 'n8n-workflow'; import { getSitesDescription } from './operations/getSites.descriptions'; import { createSiteDescription } from './operations/createSite.descriptions'; // Import other descriptions // Define the operation selector export const siteOperationSelector: INodeProperties = { displayName: 'Operation', name: 'operation', type: 'options', // Define options for the dropdown options: [ { name: 'Get Sites', value: 'getSites', description: 'Get a list of sites', action: 'Get sites', }, // Other operations ], default: 'getSites', }; // Build the descriptions array carefully to avoid duplication export const siteDescriptions: INodeProperties[] = [siteOperationSelector]; // Add each operation's descriptions only once function addDescriptionArray(array: INodeProperties[] | undefined) { if (!Array.isArray(array) || array.length === 0) return; array.forEach(item => { const exists = siteDescriptions.some(existing => existing.name === item.name && existing.type === item.type && JSON.stringify(existing.displayOptions) === JSON.stringify(item.displayOptions) ); if (!exists) { siteDescriptions.push(item); } }); } // Add all operation descriptions addDescriptionArray(getSitesDescription); addDescriptionArray(createSiteDescription); // Add other descriptions ``` Resource Type File (site/site.types.ts): ```typescript // Define types for the resource export interface Site { site_id: string; site_name: string; // Other properties } export interface SiteListResponse { sites: Site[]; count: number; } // Other type definitions ``` 3. Domain Level Files For each domain like monitoring, we need three files: Domain Methods File (monitoring/monitoring.methods.ts): ```typescript import { siteOperations } from './site/site.methods'; import { apOperations } from './ap/ap.methods'; import { clientOperations } from './client/client.methods'; /** * All operations in the monitoring domain */ export const monitoringOperations = { site: siteOperations, ap: apOperations, client: clientOperations, }; /** * Check if an operation exists in the monitoring domain */ export function hasOperation(resource: string, operation: string): boolean { return !!monitoringOperations[resource]?.[operation]; } ``` Domain Description File (monitoring/monitoring.descriptions.ts): ```typescript import { INodeProperties } from 'n8n-workflow'; import { siteDescriptions } from './site/site.descriptions'; import { apDescriptions } from './ap/ap.descriptions'; import { clientDescriptions } from './client/client.descriptions'; // Resource selection for the monitoring domain export const monitoringResourceSelector: INodeProperties = { displayName: 'Resource', name: 'resource', type: 'options', options: [ { name: 'Site', value: 'site' }, { name: 'Access Point', value: 'ap' }, { name: 'Client', value: 'client' }, ], default: 'site', }; // Build the descriptions array carefully to avoid duplication export const monitoringDescriptions: INodeProperties[] = [monitoringResourceSelector]; // Add each resource's descriptions function addDescriptionArray(array: INodeProperties[] | undefined) { if (!Array.isArray(array) || array.length === 0) return; array.forEach(item => { const exists = monitoringDescriptions.some(existing => existing.name === item.name && existing.type === item.type && JSON.stringify(existing.displayOptions) === JSON.stringify(item.displayOptions) ); if (!exists) { monitoringDescriptions.push(item); } }); } // Add each resource's descriptions addDescriptionArray(siteDescriptions); addDescriptionArray(apDescriptions); addDescriptionArray(clientDescriptions); ``` 4. Root API Files At the root of the API, we need: Description File (api/descriptions.ts): ```typescript import { INodeProperties } from 'n8n-workflow'; import { monitoringDescriptions } from './monitoring/monitoring.descriptions'; // Import other domains // Domain selection export const domainSelector: INodeProperties = { displayName: 'Domain', name: 'domain', type: 'options', options: [ { name: 'Monitoring', value: 'monitoring' }, // Other domains ], default: 'monitoring', }; // Build the descriptions array carefully to avoid duplication export const allDescriptions: INodeProperties[] = [domainSelector]; // Add each domain's descriptions function addDescriptionArray(array: INodeProperties[] | undefined) { if (!Array.isArray(array) || array.length === 0) return; array.forEach(item => { const exists = allDescriptions.some(existing => existing.name === item.name && existing.type === item.type && JSON.stringify(existing.displayOptions) === JSON.stringify(item.displayOptions) ); if (!exists) { allDescriptions.push(item); } }); } // Add domains addDescriptionArray(monitoringDescriptions); // Add other domains ``` 5. Helper Functions Create a deduplicate.ts helper to reuse our deduplication logic: ```typescript // helpers/deduplicate.ts import { INodeProperties } from 'n8n-workflow'; /** * Adds descriptions to a target array, avoiding duplicates * * @param target The target array to add descriptions to * @param source The source array of descriptions to add */ export function addDescriptions(target: INodeProperties[], source: INodeProperties[] | undefined): void { if (!Array.isArray(source) || source.length === 0) return; source.forEach(item => { const exists = target.some(existing => existing.name === item.name && existing.type === item.type && JSON.stringify(existing.displayOptions) === JSON.stringify(item.displayOptions) ); if (!exists) { target.push(item); } }); } ``` ## Troubleshooting ### Common Issues 1. **Type Errors** - Check interfaces in resource's `.types.ts` file - Verify type imports are correct - Ensure API response matches type definitions 2. **Authentication Issues** - Check credentials implementation in `credentials/` - Verify token refresh logic - Monitor API request headers 3. **Operation Not Found** - Verify operation is exported in resource's `.common.ts` - Check domain operation registration - Confirm UI description is properly registered 4. **Response Formatting Errors** - Check response type matches expected format - Verify formatResponse helper is handling the type - Log raw response for debugging ### Debugging Tips 1. Use domain-specific logging: ```typescript import { logger } from '../../../helpers/logger'; logger.debug('monitoring:ap:getStatus', { params, response }); ``` 2. Test operations in isolation: ```typescript // tests/monitoring/ap/getStatus.test.ts describe('AP Status Operation', () => { it('should retrieve device status', async () => { const result = await getStatus.call(mockExecuteFunctions); expect(result).toBeDefined(); }); }); ``` 3. Validate API responses against types: ```typescript import { StatusResponse } from './ap.types'; const validateResponse = (data: unknown): data is StatusResponse => { // Implementation }; ``` ## OAuth2 Authentication Authentication is handled centrally through: - `credentials/ArubaCentralOAuth2Api.credentials.ts` - Credential definition - `helpers/apiRequest.ts` - Request handling with auth ## Best Practices 1. **Domain Organization** - Keep related operations in same resource directory - Use common.ts for shared resource code - Maintain clear separation between domains 2. **Type Safety** - Define interfaces for all API responses - Use type guards for validation - Keep types synchronized with API spec 3. **Error Handling** - Use domain-specific error types - Implement consistent error formatting - Provide clear error messages 4. **Documentation** - Document complex operations - Keep API references updated - Include examples in descriptions 5. **Testing** - Write tests for each operation - Test error conditions - Validate response formatting ## Development Workflow 1. **Planning** - Identify domain and resource - Review API documentation - Plan type structures 2. **Implementation** - Create operation files - Implement method logic - Add UI descriptions 3. **Integration** - Register with resource - Update domain operations - Test end-to-end flow 4. **Documentation** - Update operation docs - Add usage examples - Document error cases 5. **Testing** - Unit test operation - Integration test with API - Verify UI functionality ## API Documentation References For more details on available endpoints and parameters, refer to: - API documentation in vector store - Domain-specific documentation in `docs/api/` - Operation descriptions in code ## Next Steps Your task is to: {{ task }} Fix Current Issues - Standardize error handling across all operations using the handleApiError helper - Enhance response formatting to handle all possible response structures - Improve parameter validation before making API requests Documentation - Add JSDoc comments to all functions - Create usage examples for common operations - Document error cases and handling Remember to: 1. Follow the domain-driven structure 2. Implement proper error handling 3. Add comprehensive types 4. Include operation documentation 5. Write tests for new functionality