mirror of
https://github.com/Abdulazizzn/n8n-enterprise-unlocked.git
synced 2025-12-17 18:12:04 +00:00
feat(Oura Node): Update node for v2 api (#11604)
This commit is contained in:
@@ -1,4 +1,9 @@
|
|||||||
import type { ICredentialType, INodeProperties } from 'n8n-workflow';
|
import type {
|
||||||
|
IAuthenticateGeneric,
|
||||||
|
ICredentialTestRequest,
|
||||||
|
ICredentialType,
|
||||||
|
INodeProperties,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
|
||||||
export class OuraApi implements ICredentialType {
|
export class OuraApi implements ICredentialType {
|
||||||
name = 'ouraApi';
|
name = 'ouraApi';
|
||||||
@@ -16,4 +21,20 @@ export class OuraApi implements ICredentialType {
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
authenticate: IAuthenticateGeneric = {
|
||||||
|
type: 'generic',
|
||||||
|
properties: {
|
||||||
|
headers: {
|
||||||
|
Authorization: '=Bearer {{$credentials.accessToken}}',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test: ICredentialTestRequest = {
|
||||||
|
request: {
|
||||||
|
baseURL: 'https://api.ouraring.com',
|
||||||
|
url: '/v2/usercollection/personal_info',
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type {
|
|||||||
IHookFunctions,
|
IHookFunctions,
|
||||||
ILoadOptionsFunctions,
|
ILoadOptionsFunctions,
|
||||||
JsonObject,
|
JsonObject,
|
||||||
IRequestOptions,
|
IHttpRequestOptions,
|
||||||
IHttpRequestMethods,
|
IHttpRequestMethods,
|
||||||
} from 'n8n-workflow';
|
} from 'n8n-workflow';
|
||||||
import { NodeApiError } from 'n8n-workflow';
|
import { NodeApiError } from 'n8n-workflow';
|
||||||
@@ -18,15 +18,11 @@ export async function ouraApiRequest(
|
|||||||
uri?: string,
|
uri?: string,
|
||||||
option: IDataObject = {},
|
option: IDataObject = {},
|
||||||
) {
|
) {
|
||||||
const credentials = await this.getCredentials('ouraApi');
|
let options: IHttpRequestOptions = {
|
||||||
let options: IRequestOptions = {
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${credentials.accessToken}`,
|
|
||||||
},
|
|
||||||
method,
|
method,
|
||||||
qs,
|
qs,
|
||||||
body,
|
body,
|
||||||
uri: uri || `https://api.ouraring.com/v1${resource}`,
|
url: uri ?? `https://api.ouraring.com/v2${resource}`,
|
||||||
json: true,
|
json: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -41,7 +37,7 @@ export async function ouraApiRequest(
|
|||||||
options = Object.assign({}, options, option);
|
options = Object.assign({}, options, option);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.helpers.request(options);
|
return await this.helpers.httpRequestWithAuthentication.call(this, 'ouraApi', options);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,12 +63,13 @@ export class Oura implements INodeType {
|
|||||||
const length = items.length;
|
const length = items.length;
|
||||||
|
|
||||||
let responseData;
|
let responseData;
|
||||||
const returnData: IDataObject[] = [];
|
const returnData: INodeExecutionData[] = [];
|
||||||
|
|
||||||
const resource = this.getNodeParameter('resource', 0);
|
const resource = this.getNodeParameter('resource', 0);
|
||||||
const operation = this.getNodeParameter('operation', 0);
|
const operation = this.getNodeParameter('operation', 0);
|
||||||
|
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
|
try {
|
||||||
if (resource === 'profile') {
|
if (resource === 'profile') {
|
||||||
// *********************************************************************
|
// *********************************************************************
|
||||||
// profile
|
// profile
|
||||||
@@ -81,7 +82,7 @@ export class Oura implements INodeType {
|
|||||||
// profile: get
|
// profile: get
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
responseData = await ouraApiRequest.call(this, 'GET', '/userinfo');
|
responseData = await ouraApiRequest.call(this, 'GET', '/usercollection/personal_info');
|
||||||
}
|
}
|
||||||
} else if (resource === 'summary') {
|
} else if (resource === 'summary') {
|
||||||
// *********************************************************************
|
// *********************************************************************
|
||||||
@@ -100,11 +101,11 @@ export class Oura implements INodeType {
|
|||||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||||
|
|
||||||
if (start) {
|
if (start) {
|
||||||
qs.start = moment(start).format('YYYY-MM-DD');
|
qs.start_date = moment(start).format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (end) {
|
if (end) {
|
||||||
qs.end = moment(end).format('YYYY-MM-DD');
|
qs.end_date = moment(end).format('YYYY-MM-DD');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (operation === 'getActivity') {
|
if (operation === 'getActivity') {
|
||||||
@@ -112,8 +113,14 @@ export class Oura implements INodeType {
|
|||||||
// profile: getActivity
|
// profile: getActivity
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
responseData = await ouraApiRequest.call(this, 'GET', '/activity', {}, qs);
|
responseData = await ouraApiRequest.call(
|
||||||
responseData = responseData.activity;
|
this,
|
||||||
|
'GET',
|
||||||
|
'/usercollection/daily_activity',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.data;
|
||||||
|
|
||||||
if (!returnAll) {
|
if (!returnAll) {
|
||||||
const limit = this.getNodeParameter('limit', 0);
|
const limit = this.getNodeParameter('limit', 0);
|
||||||
@@ -124,8 +131,14 @@ export class Oura implements INodeType {
|
|||||||
// profile: getReadiness
|
// profile: getReadiness
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
responseData = await ouraApiRequest.call(this, 'GET', '/readiness', {}, qs);
|
responseData = await ouraApiRequest.call(
|
||||||
responseData = responseData.readiness;
|
this,
|
||||||
|
'GET',
|
||||||
|
'/usercollection/daily_readiness',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.data;
|
||||||
|
|
||||||
if (!returnAll) {
|
if (!returnAll) {
|
||||||
const limit = this.getNodeParameter('limit', 0);
|
const limit = this.getNodeParameter('limit', 0);
|
||||||
@@ -136,8 +149,14 @@ export class Oura implements INodeType {
|
|||||||
// profile: getSleep
|
// profile: getSleep
|
||||||
// ----------------------------------
|
// ----------------------------------
|
||||||
|
|
||||||
responseData = await ouraApiRequest.call(this, 'GET', '/sleep', {}, qs);
|
responseData = await ouraApiRequest.call(
|
||||||
responseData = responseData.sleep;
|
this,
|
||||||
|
'GET',
|
||||||
|
'/usercollection/daily_sleep',
|
||||||
|
{},
|
||||||
|
qs,
|
||||||
|
);
|
||||||
|
responseData = responseData.data;
|
||||||
|
|
||||||
if (!returnAll) {
|
if (!returnAll) {
|
||||||
const limit = this.getNodeParameter('limit', 0);
|
const limit = this.getNodeParameter('limit', 0);
|
||||||
@@ -146,11 +165,24 @@ export class Oura implements INodeType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Array.isArray(responseData)
|
const executionData = this.helpers.constructExecutionMetaData(
|
||||||
? returnData.push(...(responseData as IDataObject[]))
|
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||||
: returnData.push(responseData as IDataObject);
|
{ itemData: { item: i } },
|
||||||
}
|
);
|
||||||
|
|
||||||
return [this.helpers.returnJsonArray(returnData)];
|
returnData.push(...executionData);
|
||||||
|
} catch (error) {
|
||||||
|
if (this.continueOnFail()) {
|
||||||
|
const executionErrorData = this.helpers.constructExecutionMetaData(
|
||||||
|
this.helpers.returnJsonArray({ error: error.message }),
|
||||||
|
{ itemData: { item: i } },
|
||||||
|
);
|
||||||
|
returnData.push(...executionErrorData);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [returnData];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
8
packages/nodes-base/nodes/Oura/test/apiResponses.ts
Normal file
8
packages/nodes-base/nodes/Oura/test/apiResponses.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export const profileResponse = {
|
||||||
|
id: 'some-id',
|
||||||
|
age: 30,
|
||||||
|
weight: 168,
|
||||||
|
height: 80,
|
||||||
|
biological_sex: 'male',
|
||||||
|
email: 'nathan@n8n.io',
|
||||||
|
};
|
||||||
76
packages/nodes-base/nodes/Oura/test/oura.node.test.ts
Normal file
76
packages/nodes-base/nodes/Oura/test/oura.node.test.ts
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
import type {
|
||||||
|
IExecuteFunctions,
|
||||||
|
IHookFunctions,
|
||||||
|
ILoadOptionsFunctions,
|
||||||
|
IHttpRequestMethods,
|
||||||
|
INode,
|
||||||
|
} from 'n8n-workflow';
|
||||||
|
import nock from 'nock';
|
||||||
|
|
||||||
|
import { setup, equalityTest, workflowToTests, getWorkflowFilenames } from '@test/nodes/Helpers';
|
||||||
|
|
||||||
|
import { profileResponse } from './apiResponses';
|
||||||
|
import { ouraApiRequest } from '../GenericFunctions';
|
||||||
|
|
||||||
|
const node: INode = {
|
||||||
|
id: '2cdb46cf-b561-4537-a982-b8d26dd7718b',
|
||||||
|
name: 'Oura',
|
||||||
|
type: 'n8n-nodes-base.oura',
|
||||||
|
typeVersion: 1,
|
||||||
|
position: [0, 0],
|
||||||
|
parameters: {
|
||||||
|
resource: 'profile',
|
||||||
|
operation: 'get',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockThis = {
|
||||||
|
helpers: {
|
||||||
|
httpRequestWithAuthentication: jest
|
||||||
|
.fn()
|
||||||
|
.mockResolvedValue({ statusCode: 200, data: profileResponse }),
|
||||||
|
},
|
||||||
|
getNode() {
|
||||||
|
return node;
|
||||||
|
},
|
||||||
|
getNodeParameter: jest.fn(),
|
||||||
|
} as unknown as IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions;
|
||||||
|
|
||||||
|
describe('Oura', () => {
|
||||||
|
describe('ouraApiRequest', () => {
|
||||||
|
it('should make an authenticated API request to Oura', async () => {
|
||||||
|
const method: IHttpRequestMethods = 'GET';
|
||||||
|
const resource = '/usercollection/personal_info';
|
||||||
|
|
||||||
|
await ouraApiRequest.call(mockThis, method, resource);
|
||||||
|
|
||||||
|
expect(mockThis.helpers.httpRequestWithAuthentication).toHaveBeenCalledWith('ouraApi', {
|
||||||
|
method: 'GET',
|
||||||
|
url: 'https://api.ouraring.com/v2/usercollection/personal_info',
|
||||||
|
json: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('Run Oura workflow', () => {
|
||||||
|
const workflows = getWorkflowFilenames(__dirname);
|
||||||
|
const tests = workflowToTests(workflows);
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
nock.disableNetConnect();
|
||||||
|
|
||||||
|
nock('https://api.ouraring.com/v2')
|
||||||
|
.get('/usercollection/personal_info')
|
||||||
|
.reply(200, profileResponse);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
nock.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const nodeTypes = setup(tests);
|
||||||
|
|
||||||
|
for (const testData of tests) {
|
||||||
|
test(testData.description, async () => await equalityTest(testData, nodeTypes));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
86
packages/nodes-base/nodes/Oura/test/oura_test_workflow.json
Normal file
86
packages/nodes-base/nodes/Oura/test/oura_test_workflow.json
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
{
|
||||||
|
"name": "Oura Test Workflow",
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "c1e3b825-a9a8-4def-986b-9108d9441992",
|
||||||
|
"name": "When clicking ‘Test workflow’",
|
||||||
|
"type": "n8n-nodes-base.manualTrigger",
|
||||||
|
"position": [720, 400],
|
||||||
|
"typeVersion": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {
|
||||||
|
"resource": "profile"
|
||||||
|
},
|
||||||
|
"id": "7969bf78-9343-4f81-8f79-dc415a60e168",
|
||||||
|
"name": "Oura",
|
||||||
|
"type": "n8n-nodes-base.oura",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [940, 400],
|
||||||
|
"credentials": {
|
||||||
|
"ouraApi": {
|
||||||
|
"id": "r083EOdhFatkVvFy",
|
||||||
|
"name": "Oura account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters": {},
|
||||||
|
"id": "9b97fa0e-51a6-41d3-8a7d-cff0531e5527",
|
||||||
|
"name": "No Operation, do nothing",
|
||||||
|
"type": "n8n-nodes-base.noOp",
|
||||||
|
"typeVersion": 1,
|
||||||
|
"position": [1140, 400]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"pinData": {
|
||||||
|
"No Operation, do nothing": [
|
||||||
|
{
|
||||||
|
"json": {
|
||||||
|
"id": "some-id",
|
||||||
|
"age": 30,
|
||||||
|
"weight": 168,
|
||||||
|
"height": 80,
|
||||||
|
"biological_sex": "male",
|
||||||
|
"email": "nathan@n8n.io"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"connections": {
|
||||||
|
"When clicking ‘Test workflow’": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "Oura",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"Oura": {
|
||||||
|
"main": [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"node": "No Operation, do nothing",
|
||||||
|
"type": "main",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"active": false,
|
||||||
|
"settings": {
|
||||||
|
"executionOrder": "v1"
|
||||||
|
},
|
||||||
|
"versionId": "bd108f46-f6fc-4c22-8655-ade2f51c4b33",
|
||||||
|
"meta": {
|
||||||
|
"templateCredsSetupCompleted": true,
|
||||||
|
"instanceId": "0fa937d34dcabeff4bd6480d3b42cc95edf3bc20e6810819086ef1ce2623639d"
|
||||||
|
},
|
||||||
|
"id": "SrUileWU90mQeo02",
|
||||||
|
"tags": []
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user