2023-03-09 09:13:15 -08:00
|
|
|
import type {
|
|
|
|
IExecuteFunctions,
|
|
|
|
IHookFunctions,
|
|
|
|
IDataObject,
|
|
|
|
ILoadOptionsFunctions,
|
|
|
|
JsonObject,
|
|
|
|
} from 'n8n-workflow';
|
2023-01-27 03:22:44 -08:00
|
|
|
import { NodeApiError, NodeOperationError } from 'n8n-workflow';
|
2021-09-05 10:24:56 -07:00
|
|
|
|
2023-01-27 03:22:44 -08:00
|
|
|
import type { OptionsWithUri } from 'request';
|
2023-10-27 05:15:02 -07:00
|
|
|
|
2023-06-16 07:26:35 -07:00
|
|
|
import { omit } from 'lodash';
|
2023-01-27 03:22:44 -08:00
|
|
|
import type {
|
2021-09-05 10:24:56 -07:00
|
|
|
AddressFixedCollection,
|
|
|
|
FreshserviceCredentials,
|
|
|
|
LoadedUser,
|
|
|
|
RolesParameter,
|
|
|
|
} from './types';
|
|
|
|
|
|
|
|
export async function freshserviceApiRequest(
|
|
|
|
this: IExecuteFunctions | IHookFunctions | ILoadOptionsFunctions,
|
|
|
|
method: string,
|
|
|
|
endpoint: string,
|
|
|
|
body: IDataObject = {},
|
|
|
|
qs: IDataObject = {},
|
|
|
|
) {
|
2022-08-01 13:47:55 -07:00
|
|
|
const { apiKey, domain } = (await this.getCredentials(
|
|
|
|
'freshserviceApi',
|
|
|
|
)) as FreshserviceCredentials;
|
2021-09-05 10:24:56 -07:00
|
|
|
const encodedApiKey = Buffer.from(`${apiKey}:X`).toString('base64');
|
|
|
|
|
|
|
|
const options: OptionsWithUri = {
|
|
|
|
headers: {
|
|
|
|
Authorization: `Basic ${encodedApiKey}`,
|
|
|
|
},
|
|
|
|
method,
|
|
|
|
body,
|
|
|
|
qs,
|
|
|
|
uri: `https://${domain}.freshservice.com/api/v2${endpoint}`,
|
|
|
|
json: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (!Object.keys(body).length) {
|
|
|
|
delete options.body;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!Object.keys(qs).length) {
|
|
|
|
delete options.qs;
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
2022-12-23 10:09:52 -08:00
|
|
|
return await this.helpers.request(options);
|
2021-09-05 10:24:56 -07:00
|
|
|
} catch (error) {
|
|
|
|
if (error.error.description === 'Validation failed') {
|
|
|
|
const numberOfErrors = error.error.errors.length;
|
|
|
|
const message = 'Please check your parameters';
|
|
|
|
|
|
|
|
if (numberOfErrors === 1) {
|
|
|
|
const [validationError] = error.error.errors;
|
2023-02-27 19:39:43 -08:00
|
|
|
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
2021-09-05 10:24:56 -07:00
|
|
|
message,
|
|
|
|
description: `For ${validationError.field}: ${validationError.message}`,
|
|
|
|
});
|
|
|
|
} else if (numberOfErrors > 1) {
|
2023-02-27 19:39:43 -08:00
|
|
|
throw new NodeApiError(this.getNode(), error as JsonObject, {
|
2021-09-05 10:24:56 -07:00
|
|
|
message,
|
2022-08-01 13:47:55 -07:00
|
|
|
description: "For more information, expand 'details' below and look at 'cause' section",
|
2021-09-05 10:24:56 -07:00
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-27 19:39:43 -08:00
|
|
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
2021-09-05 10:24:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function freshserviceApiRequestAllItems(
|
|
|
|
this: IExecuteFunctions | IHookFunctions,
|
|
|
|
method: string,
|
|
|
|
endpoint: string,
|
|
|
|
body: IDataObject = {},
|
|
|
|
qs: IDataObject = {},
|
|
|
|
) {
|
|
|
|
const returnData: IDataObject[] = [];
|
|
|
|
qs.page = 1;
|
|
|
|
let items;
|
|
|
|
|
|
|
|
do {
|
|
|
|
const responseData = await freshserviceApiRequest.call(this, method, endpoint, body, qs);
|
2023-02-27 19:39:43 -08:00
|
|
|
const key = Object.keys(responseData as IDataObject)[0];
|
2021-09-05 10:24:56 -07:00
|
|
|
items = responseData[key];
|
|
|
|
if (!items.length) return returnData;
|
2023-02-27 19:39:43 -08:00
|
|
|
returnData.push(...(items as IDataObject[]));
|
2021-09-05 10:24:56 -07:00
|
|
|
qs.page++;
|
2022-08-01 13:47:55 -07:00
|
|
|
} while (items.length >= 30);
|
2021-09-05 10:24:56 -07:00
|
|
|
|
|
|
|
return returnData;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function handleListing(
|
|
|
|
this: IExecuteFunctions,
|
|
|
|
method: string,
|
|
|
|
endpoint: string,
|
|
|
|
body: IDataObject = {},
|
|
|
|
qs: IDataObject = {},
|
|
|
|
) {
|
2022-11-18 05:31:38 -08:00
|
|
|
const returnAll = this.getNodeParameter('returnAll', 0);
|
2021-09-05 10:24:56 -07:00
|
|
|
|
|
|
|
if (returnAll) {
|
2024-01-17 07:08:50 -08:00
|
|
|
return await freshserviceApiRequestAllItems.call(this, method, endpoint, body, qs);
|
2021-09-05 10:24:56 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const responseData = await freshserviceApiRequestAllItems.call(this, method, endpoint, body, qs);
|
2022-11-18 06:26:22 -08:00
|
|
|
const limit = this.getNodeParameter('limit', 0);
|
2021-09-05 10:24:56 -07:00
|
|
|
|
|
|
|
return responseData.slice(0, limit);
|
|
|
|
}
|
|
|
|
|
|
|
|
export const toOptions = (loadedResources: LoadedResource[]) => {
|
|
|
|
return loadedResources
|
|
|
|
.map(({ id, name }) => ({ value: id, name }))
|
|
|
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
};
|
|
|
|
|
|
|
|
export const toUserOptions = (loadedUsers: LoadedUser[]) => {
|
|
|
|
return loadedUsers
|
|
|
|
.map(({ id, last_name, first_name }) => {
|
|
|
|
return {
|
|
|
|
value: id,
|
|
|
|
name: last_name ? `${last_name}, ${first_name}` : `${first_name}`,
|
|
|
|
};
|
|
|
|
})
|
|
|
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Ensure at least one role has been specified.
|
|
|
|
*/
|
2022-08-01 13:47:55 -07:00
|
|
|
export function validateAssignmentScopeGroup(this: IExecuteFunctions, roles: RolesParameter) {
|
2021-09-05 10:24:56 -07:00
|
|
|
if (!roles.roleProperties?.length) {
|
2022-08-01 13:47:55 -07:00
|
|
|
throw new NodeOperationError(this.getNode(), 'Please specify a role for the agent to create.');
|
2021-09-05 10:24:56 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-01 13:47:55 -07:00
|
|
|
export function sanitizeAssignmentScopeGroup(this: IExecuteFunctions, roles: RolesParameter) {
|
|
|
|
roles.roleProperties.forEach((roleProperty) => {
|
2021-09-05 10:24:56 -07:00
|
|
|
if (roleProperty.assignment_scope === 'specified_groups' && !roleProperty?.groups?.length) {
|
|
|
|
throw new NodeOperationError(
|
|
|
|
this.getNode(),
|
|
|
|
'Please specify a group for every role of the agent to create.',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove the `groups` param, only needed for scopes other than `specified_groups`
|
|
|
|
if (roleProperty.assignment_scope !== 'specified_groups' && roleProperty.groups) {
|
|
|
|
delete roleProperty.groups;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Adjust a roles fixed collection into the format expected by Freshservice API.
|
|
|
|
*/
|
|
|
|
export function adjustAgentRoles(roles: RolesParameter) {
|
|
|
|
return {
|
|
|
|
roles: roles.roleProperties.map(({ role, assignment_scope, groups }) => {
|
|
|
|
return {
|
|
|
|
role_id: role,
|
|
|
|
assignment_scope,
|
|
|
|
groups,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function formatFilters(filters: IDataObject) {
|
2022-08-01 13:47:55 -07:00
|
|
|
const query = Object.keys(filters)
|
|
|
|
.map((key) => {
|
|
|
|
const value = filters[key];
|
2021-09-05 10:24:56 -07:00
|
|
|
|
2022-08-01 13:47:55 -07:00
|
|
|
if (!isNaN(Number(value))) {
|
|
|
|
return `${key}:${filters[key]}`; // number
|
|
|
|
}
|
2021-09-05 10:24:56 -07:00
|
|
|
|
2022-08-01 13:47:55 -07:00
|
|
|
if (typeof value === 'string' && value.endsWith('Z')) {
|
|
|
|
return `${key}:'${value.split('T')[0]}'`; // date
|
|
|
|
}
|
2021-09-05 10:24:56 -07:00
|
|
|
|
2022-08-01 13:47:55 -07:00
|
|
|
return `${key}:'${filters[key]}'`; // string
|
|
|
|
})
|
|
|
|
.join(' AND ');
|
2021-09-05 10:24:56 -07:00
|
|
|
|
|
|
|
return {
|
|
|
|
query: `"${query}"`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
export function validateUpdateFields(
|
|
|
|
this: IExecuteFunctions,
|
|
|
|
updateFields: IDataObject,
|
|
|
|
resource: string,
|
|
|
|
) {
|
|
|
|
if (!Object.keys(updateFields).length) {
|
|
|
|
const twoWordResources: { [key: string]: string } = {
|
|
|
|
agentGroup: 'agent group',
|
|
|
|
agentRole: 'agent role',
|
|
|
|
assetType: 'asset type',
|
|
|
|
requesterGroup: 'requester group',
|
|
|
|
};
|
|
|
|
|
|
|
|
throw new NodeOperationError(
|
|
|
|
this.getNode(),
|
2022-08-01 13:47:55 -07:00
|
|
|
`Please enter at least one field to update for the ${
|
|
|
|
twoWordResources[resource] ?? resource
|
|
|
|
}.`,
|
2021-09-05 10:24:56 -07:00
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-01 13:47:55 -07:00
|
|
|
export const toArray = (str: string) => str.split(',').map((e) => e.trim());
|
2021-09-05 10:24:56 -07:00
|
|
|
|
|
|
|
export function adjustAddress(fixedCollection: IDataObject & AddressFixedCollection) {
|
|
|
|
if (!fixedCollection.address) return fixedCollection;
|
|
|
|
|
|
|
|
const adjusted = omit(fixedCollection, ['address']);
|
|
|
|
adjusted.address = fixedCollection.address.addressFields;
|
|
|
|
|
|
|
|
return adjusted;
|
|
|
|
}
|