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
Markdown
# 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