Skip to content

Commit

Permalink
Fixes and refactoring the rest of the analysis
Browse files Browse the repository at this point in the history
  • Loading branch information
mateuscardosodeveloper committed Jan 9, 2024
1 parent 1509f44 commit 647cd6d
Show file tree
Hide file tree
Showing 12 changed files with 338 additions and 481 deletions.
114 changes: 37 additions & 77 deletions src/analysis/alertTrigger.ts → src/analysis/alert-trigger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,12 @@
* KickStarter Analysis
* Alert Trigger
*
* The analysis runs everytime a device uplink matches an alert and must send an email, sms or notification.
*
* How to setup this analysis
* Make sure you have the following enviroment variables:
* - config_token: the value must be a token from a HTTPs device, that stores general information of the application.
* - account_token: the value must be a token from your profile. See how to generate account-token at: https://help.tago.io/portal/en/kb/articles/495-account-token.
* The analysis runs every time a device uplink matches an alert and must send an email, sms or notification.
*/
import { Utils, Services, Account, Analysis } from "@tago-io/sdk";
import { Data } from "@tago-io/sdk/out/common/common.types";
import { UserInfo } from "@tago-io/sdk/out/modules/Account/run.types";
import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types";
import checkAndChargeUsage from "../services/plan/checkAndChargeUsage";
import { Analysis, Resources, Services, Utils } from "@tago-io/sdk";
import { Data, DeviceInfo, TagoContext, UserInfo } from "@tago-io/sdk/lib/types";

import { checkAndChargeUsage } from "../services/plan/check-and-charge-usage";

interface IMessageDetail {
device_name: string;
Expand All @@ -25,27 +19,25 @@ interface IMessageDetail {
/**
* Notification messages to be sent
* @param type Type of message to be sent
* @param account Account instanced class
* @param context Context is a variable sent by the analysis
* @param org_id Organization ID of the device that triggered the alert
* @param to_dispatch_qty Number of messages to be sent
* @param users_info Array of users to receive the message
* @param message Message to be sent
*/
async function notificationMessages(type: string[], account: Account, context: TagoContext, org_id: string, to_dispatch_qty: number, users_info: UserInfo[], message: string) {
async function notificationMessages(type: string[], context: TagoContext, org_id: string, to_dispatch_qty: number, users_info: UserInfo[], message: string) {
if (type.includes("notification_run")) {
const has_service_limit = await checkAndChargeUsage(account, context, org_id, to_dispatch_qty, "notification_run");
const has_service_limit = await checkAndChargeUsage(context, org_id, to_dispatch_qty, "notification_run");

if (has_service_limit) {
users_info.forEach((user) => {
account.run.notificationCreate(user.id, {
for (const user of users_info) {
void Resources.run.notificationCreate(user.id, {
message,
title: "Alert Trigger",
});
});
}
} else {
const org_dev = await Utils.getDevice(account, org_id);
await org_dev.sendData({
await Resources.devices.sendDeviceData(org_id, {
variable: "plan_status",
value: `Attempt to send ${to_dispatch_qty} alert(s) was not successful. No notification service limit available, check your service usage at "Info" to learn more about your plan status.`,
});
Expand All @@ -56,31 +48,21 @@ async function notificationMessages(type: string[], account: Account, context: T
/**
* Email messages to be sent
* @param type Type of message to be sent
* @param account Account instanced class
* @param context Context is a variable sent by the analysis
* @param org_id Organization ID of the device that triggered the alert
* @param to_dispatch_qty Number of messages to be sent
* @param users_info Array of users to receive the message
* @param device_info Device information
* @param message Message to be sent
*/
async function emailMessages(
type: string[],
account: Account,
context: TagoContext,
org_id: string,
to_dispatch_qty: number,
users_info: UserInfo[],
device_info: any,
message: string
) {
async function emailMessages(type: string[], context: TagoContext, org_id: string, to_dispatch_qty: number, users_info: UserInfo[], device_info: any, message: string) {
if (type.includes("email")) {
const has_service_limit = await checkAndChargeUsage(account, context, org_id, to_dispatch_qty, "email");
const has_service_limit = await checkAndChargeUsage(context, org_id, to_dispatch_qty, "email");

if (has_service_limit) {
const email = new Services({ token: context.token }).email;

email.send({
void email.send({
to: users_info.map((x) => x.email).join(","),
template: {
name: "email_alert",
Expand All @@ -91,8 +73,7 @@ async function emailMessages(
},
});
} else {
const org_dev = await Utils.getDevice(account, org_id);
await org_dev.sendData({
await Resources.devices.sendDeviceData(org_id, {
variable: "plan_status",
value: `Attempt to send ${to_dispatch_qty} alert(s) was not successful. No email service limit available, check your service usage at "Info" to learn more about your plan status.`,
});
Expand All @@ -103,33 +84,31 @@ async function emailMessages(
/**
* Sms messages to be sent
* @param type Type of message to be sent
* @param account Account instanced class
* @param context Context is a variable sent by the analysis
* @param org_id Organization ID of the device that triggered the alert
* @param to_dispatch_qty Number of messages to be sent
* @param users_info Array of users to receive the message
* @param message Message to be sent
*/
async function smsMessages(type: string[], account: Account, context: TagoContext, org_id: string, to_dispatch_qty: number, users_info: UserInfo[], message: string) {
async function smsMessages(type: string[], context: TagoContext, org_id: string, to_dispatch_qty: number, users_info: UserInfo[], message: string) {
if (type.includes("sms")) {
const has_service_limit = await checkAndChargeUsage(account, context, org_id, to_dispatch_qty, "sms");
const has_service_limit = await checkAndChargeUsage(context, org_id, to_dispatch_qty, "sms");

if (has_service_limit) {
users_info.forEach((user) => {
for (const user of users_info) {
const smsService = new Services({ token: context.token }).sms;
if (!user.phone) {
throw "user.phone not found";
}
smsService
void smsService
.send({
message,
to: user.phone,
})
.then((msg) => console.debug(msg));
});
}
} else {
const org_dev = await Utils.getDevice(account, org_id);
await org_dev.sendData({
await Resources.devices.sendDeviceData(org_id, {
variable: "plan_status",
value: `Attempt to send ${to_dispatch_qty} alert(s) was not successful. No SMS service limit available, check your service usage at "Info" to learn more about your plan status.`,
});
Expand All @@ -140,29 +119,19 @@ async function smsMessages(type: string[], account: Account, context: TagoContex
/**
* Function that starts the analysis and handles the alert trigger and message dispatch
* @param type Type of message to be sent
* @param account Account instanced class
* @param context Context is a variable sent by the analysis
* @param org_id Organization ID of the device that triggered the alert
* @param to_dispatch_qty Number of messages to be sent
* @param users_info Array of users to receive the message
* @param message Message to be sent
* @param device_info Device information
*/
async function dispachMessages(
type: string[],
account: Account,
context: TagoContext,
org_id: string,
to_dispatch_qty: number,
users_info: UserInfo[],
message: string,
device_info
) {
await notificationMessages(type, account, context, org_id, to_dispatch_qty, users_info, message);

await emailMessages(type, account, context, org_id, to_dispatch_qty, users_info, device_info, message);

await smsMessages(type, account, context, org_id, to_dispatch_qty, users_info, message);
async function dispatchMessages(type: string[], context: TagoContext, org_id: string, to_dispatch_qty: number, users_info: UserInfo[], message: string, device_info: DeviceInfo) {
await notificationMessages(type, context, org_id, to_dispatch_qty, users_info, message);

await emailMessages(type, context, org_id, to_dispatch_qty, users_info, device_info, message);

await smsMessages(type, context, org_id, to_dispatch_qty, users_info, message);
}

/**
Expand All @@ -172,7 +141,7 @@ async function dispachMessages(
*/
function replaceMessage(message: string, replace_details: IMessageDetail) {
for (const key of Object.keys(replace_details)) {
message = message.replace(new RegExp(`#${key}#`, "g"), (replace_details as any)[key]);
message = message.replaceAll(new RegExp(`#${key}#`, "g"), (replace_details as any)[key]);
console.debug((replace_details as any)[key]);
}

Expand All @@ -181,11 +150,10 @@ function replaceMessage(message: string, replace_details: IMessageDetail) {

/**
* Function that get the users information
* @param account Account instanced class
* @param send_to Array of users to receive the message
*/
async function getUsers(account: Account, send_to: string[]) {
const func_list = send_to.map((user_id) => account.run.userInfo(user_id).catch(() => null));
async function getUsers(send_to: string[]) {
const func_list = send_to.map((user_id) => Resources.run.userInfo(user_id).catch(() => null));

return (await Promise.all(func_list)).filter((x) => x) as UserInfo[];
}
Expand All @@ -204,22 +172,14 @@ async function analysisAlert(context: TagoContext, scope: Data[]): Promise<void>
console.debug(JSON.stringify(scope));
// Get the environment variables.
const environment_variables = Utils.envToJson(context.environment);
if (!environment_variables.account_token) {
return console.debug('Missing "account_token" environment variable');
} else if (environment_variables.account_token.length !== 36) {
return console.debug('Invalid "account_token" in the environment variable');
}

// Instance the Account class
const account = new Account({ token: environment_variables.account_token });

const action_id = environment_variables._action_id;
if (!action_id) {
return console.debug("This analysis must be triggered by an action.");
}

// Get action details
const action_info = await account.actions.info(action_id);
const action_info = await Resources.actions.info(action_id);
if (!action_info.tags) {
throw "action_info.tags not found";
}
Expand Down Expand Up @@ -253,16 +213,16 @@ async function analysisAlert(context: TagoContext, scope: Data[]): Promise<void>
if (!org_id) {
throw "org_id not found";
}
const org_dev = await Utils.getDevice(account, org_id);
const [message_var] = await org_dev.getData({ variables: ["action_list_message", "action_group_message"], groups: alert_id, qty: 1 });
const [message_var] = await Resources.devices.getDeviceData(org_id, { variables: ["action_list_message", "action_group_message"], groups: alert_id, qty: 1 });

const trigger_variable = scope.find((x) => x.variable === (action_info.trigger[0] as any).variable);
// @ts-ignore
const trigger_variable = scope.find((x) => x.variable === (action_info?.trigger[0] as any)?.variable) ?? null;
if (!trigger_variable?.value) {
throw "trigger_variable.value not found";
}

const device_id = scope[0].device;
const device_info = await account.devices.info(device_id);
const device_info = await Resources.devices.info(device_id);

const sensor_type = device_info?.tags?.find((tag) => tag.key === "sensor")?.value;
if (!sensor_type) {
Expand All @@ -279,11 +239,11 @@ async function analysisAlert(context: TagoContext, scope: Data[]): Promise<void>

const message = replaceMessage(message_var.value as string, replace_details);

const users_info = await getUsers(account, send_to);
const users_info = await getUsers(send_to);

const to_dispatch_qty = users_info.length;

await dispachMessages(type, account, context, org_id, to_dispatch_qty, users_info, message, device_info);
await dispatchMessages(type, context, org_id, to_dispatch_qty, users_info, message, device_info);

return console.debug("Analysis Finished!");
}
Expand Down
52 changes: 52 additions & 0 deletions src/analysis/battery-updater.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* KickStarter Analysis
* Battery Updater
*
* This analysis is responsible to
* update sensor's last checkin parameter.
*
* Battery Updater will run when:
* - When the scheduled action (Battery Updater Trigger) triggers this script. (Default 1 day)
*/

import { Analysis, Resources } from "@tago-io/sdk";

import { fetchDeviceList } from "../lib/fetch-device-list";

async function resolveDevice(org_id: string, device_id: string) {
if (!org_id || !device_id) {
throw "Missing Router parameter";
}

const device_params = await Resources.devices.paramList(device_id);
const dev_battery_param = device_params.find((param) => param.key === "dev_battery") || { key: "dev_battery", value: "N/A", sent: false };

const [dev_battery] = await Resources.devices.getDeviceData(device_id, { variables: ["bat", "battery_capacity"], qty: 1 });

if (dev_battery?.value) {
await Resources.devices.paramSet(device_id, { ...dev_battery_param, value: String(dev_battery.value) });
}
}

async function startAnalysis() {
console.debug("Running Analysis");

try {
const sensorList = await fetchDeviceList({ tags: [{ key: "device_type", value: "device" }] });

sensorList.map((device) =>
resolveDevice(device.tags.find((tag) => tag.key === "organization_id")?.value as string, device.tags.find((tag) => tag.key === "device_id")?.value as string)
);

console.debug("Analysis finished");
} catch (error) {
console.debug(error);
console.debug(error.message || JSON.stringify(error));
}
}

if (!process.env.T_TEST) {
Analysis.use(startAnalysis, { token: process.env.T_ANALYSIS_TOKEN });
}

export { startAnalysis };
70 changes: 0 additions & 70 deletions src/analysis/batteryUpdater.ts

This file was deleted.

Loading

0 comments on commit 647cd6d

Please sign in to comment.