Skip to content

Commit

Permalink
Merge pull request #3663 from LiteFarmOrg/LF-4625-readability-refactors
Browse files Browse the repository at this point in the history
LF-4625 Readability Refactors on Ensemble controller code
  • Loading branch information
kathyavini authored Jan 28, 2025
2 parents e4bebb4 + 1172d83 commit 693196f
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 83 deletions.
104 changes: 49 additions & 55 deletions packages/api/src/controllers/sensorController.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ import PartnerReadingTypeModel from '../models/PartnerReadingTypeModel.js';
import { transaction, Model } from 'objection';

import {
createOrganization,
registerOrganizationWebhook,
bulkSensorClaim,
ENSEMBLE_BRAND,
extractEsids,
registerFarmAndClaimSensors,
unclaimSensor,
ENSEMBLE_UNITS_MAPPING,
} from '../util/ensemble.js';
Expand Down Expand Up @@ -119,7 +119,7 @@ const sensorController = {
const { user_id } = req.auth;
try {
const { access_token } = await IntegratingPartnersModel.getAccessAndRefreshTokens(
'Ensemble Scientific',
ENSEMBLE_BRAND,
);

//TODO: LF-4443 - Sensor should not use User language (unrestricted string), accept as body param or farm level detail
Expand Down Expand Up @@ -173,65 +173,59 @@ const sensorController = {
},
);
} else {
const esids = data.reduce((previous, current) => {
if (current.brand === 'Ensemble Scientific' && current.external_id) {
previous.push(current.external_id);
}
return previous;
}, []);
// Extract Ensemble Scientific sensor IDs (esids)
const esids = extractEsids(data);

let success = [];
let already_owned = [];
let does_not_exist = [];
let occupied = [];
if (esids.length > 0) {
const organization = await createOrganization(farm_id, access_token);

// register webhook for sensor readings
await registerOrganizationWebhook(farm_id, organization.organization_uuid, access_token);

// Register sensors with Ensemble
({ success, already_owned, does_not_exist, occupied } = await bulkSensorClaim(
if (esids.length > 0) {
({ success, already_owned, does_not_exist, occupied } = await registerFarmAndClaimSensors(
farm_id,
access_token,
organization.organization_uuid,
esids,
));
}
// register organization

// Filter sensors by those successfully registered and those with errors
const { registeredSensors, errorSensors } = data.reduce(
(prev, curr, idx) => {
if (success?.includes(curr.external_id) || already_owned?.includes(curr.external_id)) {
prev.registeredSensors.push(curr);
} else if (curr.brand !== 'Ensemble Scientific') {
prev.registeredSensors.push(curr);
} else if (does_not_exist?.includes(curr.external_id)) {
prev.errorSensors.push({
row: idx + 2,
column: 'External_ID',
translation_key: sensorErrors.SENSOR_DOES_NOT_EXIST,
variables: { sensorId: curr.external_id },
});
} else if (occupied?.includes(curr.external_id)) {
prev.errorSensors.push({
row: idx + 2,
column: 'External_ID',
translation_key: sensorErrors.SENSOR_ALREADY_OCCUPIED,
variables: { sensorId: curr.external_id },
});
} else {
// we know that it is an ESID but for some reason it was not returned in the expected format from the API
prev.errorSensors.push({
row: idx + 2,
column: 'External_ID',
translation_key: sensorErrors.INTERNAL_ERROR,
variables: { sensorId: curr.external_id },
});
}
return prev;
},
{ registeredSensors: [], errorSensors: [] },
);

const registeredSensors = [];
const errorSensors = [];

// Iterate over each sensor in the data array
data.forEach((sensor, index) => {
if (sensor.brand !== ENSEMBLE_BRAND) {
// All non-ESCI sensors should be considered successfully registered
registeredSensors.push(sensor);
} else if (
success.includes(sensor.external_id) ||
already_owned.includes(sensor.external_id)
) {
registeredSensors.push(sensor);
} else if (does_not_exist.includes(sensor.external_id)) {
errorSensors.push({
row: index + 2,
column: 'External_ID',
translation_key: sensorErrors.SENSOR_DOES_NOT_EXIST,
variables: { sensorId: sensor.external_id },
});
} else if (occupied.includes(sensor.external_id)) {
errorSensors.push({
row: index + 2,
column: 'External_ID',
translation_key: sensorErrors.SENSOR_ALREADY_OCCUPIED,
variables: { sensorId: sensor.external_id },
});
} else {
// We know that it is an ESID but it was not returned in the expected format from the API
errorSensors.push({
row: index + 2,
column: 'External_ID',
translation_key: sensorErrors.INTERNAL_ERROR,
variables: { sensorId: sensor.external_id },
});
}
});

// Save sensors in database
const sensorLocations = [];
Expand Down Expand Up @@ -630,7 +624,7 @@ const sensorController = {

const user_id = req.auth.user_id;
const { access_token } = await IntegratingPartnersModel.getAccessAndRefreshTokens(
'Ensemble Scientific',
ENSEMBLE_BRAND,
);
let unclaimResponse;
if (partner_name != 'No Integrating Partner' && external_id != '') {
Expand Down
76 changes: 48 additions & 28 deletions packages/api/src/util/ensemble.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,26 @@ const ENSEMBLE_UNITS_MAPPING = {
},
};

const ENSEMBLE_BRAND = 'Ensemble Scientific';

// Return Ensemble Scientific IDs (esids) from sensor data
const extractEsids = (data) =>
data
.filter((sensor) => sensor.brand === 'Ensemble Scientific' && sensor.external_id)
.map((sensor) => sensor.external_id);

// Function to encapsulate the logic for claiming sensors
async function registerFarmAndClaimSensors(farm_id, access_token, esids) {
// Register farm as an organization with Ensemble
const organization = await createOrganization(farm_id, access_token);

// Create a webhook for the organization
await registerOrganizationWebhook(farm_id, organization.organization_uuid, access_token);

// Register sensors with Ensemble and return Ensemble API results
return await bulkSensorClaim(access_token, organization.organization_uuid, esids);
}

/**
* Sends a request to the Ensemble API for an organization to claim sensors
* @param {String} accessToken - a JWT token for accessing the Ensemble API
Expand All @@ -76,6 +96,7 @@ async function bulkSensorClaim(accessToken, organizationId, esids) {
data: { esids },
};

// partial or complete failures (at least some esids failed to claim)
const onError = (error) => {
if (error.response?.data && error.response?.status) {
return { ...error.response.data, status: error.response.status };
Expand All @@ -84,6 +105,7 @@ async function bulkSensorClaim(accessToken, organizationId, esids) {
}
};

// full success (all esids successfully claimed)
const onResponse = (response) => {
return {
success: esids,
Expand Down Expand Up @@ -111,27 +133,27 @@ async function registerOrganizationWebhook(farmId, organizationId, accessToken)
.where({ farm_id: farmId, partner_id: 1 })
.first();
if (existingIntegration?.webhook_id) {
return existingIntegration.webhook_id;
} else {
const axiosObject = {
method: 'post',
url: `${ensembleAPI}/organizations/${organizationId}/webhooks/`,
data: {
url: `${baseUrl}/sensor/reading/partner/1/farm/${farmId}`,
authorization_header: authHeader,
frequency: 15,
},
};
const onError = (error) => {
console.log(error);
throw new Error('Failed to register webhook with ESCI');
};
const onResponse = async (response) => {
await FarmExternalIntegrationsModel.updateWebhookId(farmId, response.data.id);
return { ...response.data, status: response.status };
};
return await ensembleAPICall(accessToken, axiosObject, onError, onResponse);
return;
}

const axiosObject = {
method: 'post',
url: `${ensembleAPI}/organizations/${organizationId}/webhooks/`,
data: {
url: `${baseUrl}/sensor/reading/partner/1/farm/${farmId}`,
authorization_header: authHeader,
frequency: 15,
},
};
const onError = (error) => {
console.log(error);
throw new Error('Failed to register webhook with ESCI');
};
const onResponse = async (response) => {
await FarmExternalIntegrationsModel.updateWebhookId(farmId, response.data.id);
return { ...response.data, status: response.status };
};
await ensembleAPICall(accessToken, axiosObject, onError, onResponse);
}

/**
Expand Down Expand Up @@ -250,12 +272,10 @@ function isAuthError(error) {
*/
async function refreshTokens() {
try {
const { refresh_token } = await IntegratingPartners.getAccessAndRefreshTokens(
'Ensemble Scientific',
);
const { refresh_token } = await IntegratingPartners.getAccessAndRefreshTokens(ENSEMBLE_BRAND);
const response = await axios.post(ensembleAPI + '/token/refresh/', { refresh: refresh_token });
await IntegratingPartners.patchAccessAndRefreshTokens(
'Ensemble Scientific',
ENSEMBLE_BRAND,
response.data?.access,
response.data?.access,
);
Expand All @@ -281,7 +301,7 @@ async function authenticateToGetTokens() {
const password = process.env.ENSEMBLE_PASSWORD;
const response = await axios.post(ensembleAPI + '/token/', { username, password });
await IntegratingPartners.patchAccessAndRefreshTokens(
'Ensemble Scientific',
ENSEMBLE_BRAND,
response.data?.access,
response.data?.access,
);
Expand Down Expand Up @@ -330,9 +350,9 @@ async function unclaimSensor(org_id, external_id, access_token) {
}

export {
bulkSensorClaim,
registerOrganizationWebhook,
createOrganization,
ENSEMBLE_BRAND,
extractEsids,
registerFarmAndClaimSensors,
unclaimSensor,
ENSEMBLE_UNITS_MAPPING,
};

0 comments on commit 693196f

Please sign in to comment.