mirror of
https://github.com/n8n-io/n8n.git
synced 2025-01-11 12:57:29 -08:00
feat(Oura Node): Update node for v2 api (#11604)
This commit is contained in:
parent
20fd38f351
commit
3348fbb154
|
@ -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 {
|
||||
name = 'ouraApi';
|
||||
|
@ -16,4 +21,20 @@ export class OuraApi implements ICredentialType {
|
|||
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,
|
||||
ILoadOptionsFunctions,
|
||||
JsonObject,
|
||||
IRequestOptions,
|
||||
IHttpRequestOptions,
|
||||
IHttpRequestMethods,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
@ -18,15 +18,11 @@ export async function ouraApiRequest(
|
|||
uri?: string,
|
||||
option: IDataObject = {},
|
||||
) {
|
||||
const credentials = await this.getCredentials('ouraApi');
|
||||
let options: IRequestOptions = {
|
||||
headers: {
|
||||
Authorization: `Bearer ${credentials.accessToken}`,
|
||||
},
|
||||
let options: IHttpRequestOptions = {
|
||||
method,
|
||||
qs,
|
||||
body,
|
||||
uri: uri || `https://api.ouraring.com/v1${resource}`,
|
||||
url: uri ?? `https://api.ouraring.com/v2${resource}`,
|
||||
json: true,
|
||||
};
|
||||
|
||||
|
@ -41,7 +37,7 @@ export async function ouraApiRequest(
|
|||
options = Object.assign({}, options, option);
|
||||
|
||||
try {
|
||||
return await this.helpers.request(options);
|
||||
return await this.helpers.httpRequestWithAuthentication.call(this, 'ouraApi', options);
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
|
|
|
@ -63,94 +63,126 @@ export class Oura implements INodeType {
|
|||
const length = items.length;
|
||||
|
||||
let responseData;
|
||||
const returnData: IDataObject[] = [];
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'profile') {
|
||||
// *********************************************************************
|
||||
// profile
|
||||
// *********************************************************************
|
||||
try {
|
||||
if (resource === 'profile') {
|
||||
// *********************************************************************
|
||||
// profile
|
||||
// *********************************************************************
|
||||
|
||||
// https://cloud.ouraring.com/docs/personal-info
|
||||
// https://cloud.ouraring.com/docs/personal-info
|
||||
|
||||
if (operation === 'get') {
|
||||
// ----------------------------------
|
||||
// profile: get
|
||||
// ----------------------------------
|
||||
if (operation === 'get') {
|
||||
// ----------------------------------
|
||||
// profile: get
|
||||
// ----------------------------------
|
||||
|
||||
responseData = await ouraApiRequest.call(this, 'GET', '/userinfo');
|
||||
}
|
||||
} else if (resource === 'summary') {
|
||||
// *********************************************************************
|
||||
// summary
|
||||
// *********************************************************************
|
||||
|
||||
// https://cloud.ouraring.com/docs/daily-summaries
|
||||
|
||||
const qs: IDataObject = {};
|
||||
|
||||
const { start, end } = this.getNodeParameter('filters', i) as {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
|
||||
if (start) {
|
||||
qs.start = moment(start).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (end) {
|
||||
qs.end = moment(end).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (operation === 'getActivity') {
|
||||
// ----------------------------------
|
||||
// profile: getActivity
|
||||
// ----------------------------------
|
||||
|
||||
responseData = await ouraApiRequest.call(this, 'GET', '/activity', {}, qs);
|
||||
responseData = responseData.activity;
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
responseData = responseData.splice(0, limit);
|
||||
responseData = await ouraApiRequest.call(this, 'GET', '/usercollection/personal_info');
|
||||
}
|
||||
} else if (operation === 'getReadiness') {
|
||||
// ----------------------------------
|
||||
// profile: getReadiness
|
||||
// ----------------------------------
|
||||
} else if (resource === 'summary') {
|
||||
// *********************************************************************
|
||||
// summary
|
||||
// *********************************************************************
|
||||
|
||||
responseData = await ouraApiRequest.call(this, 'GET', '/readiness', {}, qs);
|
||||
responseData = responseData.readiness;
|
||||
// https://cloud.ouraring.com/docs/daily-summaries
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
responseData = responseData.splice(0, limit);
|
||||
const qs: IDataObject = {};
|
||||
|
||||
const { start, end } = this.getNodeParameter('filters', i) as {
|
||||
start: string;
|
||||
end: string;
|
||||
};
|
||||
|
||||
const returnAll = this.getNodeParameter('returnAll', 0);
|
||||
|
||||
if (start) {
|
||||
qs.start_date = moment(start).format('YYYY-MM-DD');
|
||||
}
|
||||
} else if (operation === 'getSleep') {
|
||||
// ----------------------------------
|
||||
// profile: getSleep
|
||||
// ----------------------------------
|
||||
|
||||
responseData = await ouraApiRequest.call(this, 'GET', '/sleep', {}, qs);
|
||||
responseData = responseData.sleep;
|
||||
if (end) {
|
||||
qs.end_date = moment(end).format('YYYY-MM-DD');
|
||||
}
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
responseData = responseData.splice(0, limit);
|
||||
if (operation === 'getActivity') {
|
||||
// ----------------------------------
|
||||
// profile: getActivity
|
||||
// ----------------------------------
|
||||
|
||||
responseData = await ouraApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/usercollection/daily_activity',
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.data;
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
} else if (operation === 'getReadiness') {
|
||||
// ----------------------------------
|
||||
// profile: getReadiness
|
||||
// ----------------------------------
|
||||
|
||||
responseData = await ouraApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/usercollection/daily_readiness',
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.data;
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
} else if (operation === 'getSleep') {
|
||||
// ----------------------------------
|
||||
// profile: getSleep
|
||||
// ----------------------------------
|
||||
|
||||
responseData = await ouraApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
'/usercollection/daily_sleep',
|
||||
{},
|
||||
qs,
|
||||
);
|
||||
responseData = responseData.data;
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', 0);
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Array.isArray(responseData)
|
||||
? returnData.push(...(responseData as IDataObject[]))
|
||||
: returnData.push(responseData as IDataObject);
|
||||
}
|
||||
|
||||
return [this.helpers.returnJsonArray(returnData)];
|
||||
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": []
|
||||
}
|
Loading…
Reference in a new issue