diff --git a/src/analysis/alertTrigger.ts b/src/analysis/alert-trigger.ts similarity index 61% rename from src/analysis/alertTrigger.ts rename to src/analysis/alert-trigger.ts index 622d9f3..a0ade30 100644 --- a/src/analysis/alertTrigger.ts +++ b/src/analysis/alert-trigger.ts @@ -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; @@ -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.`, }); @@ -56,7 +48,6 @@ 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 @@ -64,23 +55,14 @@ async function notificationMessages(type: string[], account: Account, context: T * @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", @@ -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.`, }); @@ -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.`, }); @@ -140,7 +119,6 @@ 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 @@ -148,21 +126,12 @@ async function smsMessages(type: string[], account: Account, context: TagoContex * @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); } /** @@ -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]); } @@ -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[]; } @@ -204,14 +172,6 @@ async function analysisAlert(context: TagoContext, scope: Data[]): Promise 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) { @@ -219,7 +179,7 @@ async function analysisAlert(context: TagoContext, scope: Data[]): Promise } // 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"; } @@ -253,16 +213,16 @@ async function analysisAlert(context: TagoContext, scope: Data[]): Promise 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) { @@ -279,11 +239,11 @@ async function analysisAlert(context: TagoContext, scope: Data[]): Promise 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!"); } diff --git a/src/analysis/battery-updater.ts b/src/analysis/battery-updater.ts new file mode 100644 index 0000000..6152db1 --- /dev/null +++ b/src/analysis/battery-updater.ts @@ -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 }; diff --git a/src/analysis/batteryUpdater.ts b/src/analysis/batteryUpdater.ts deleted file mode 100644 index ab69be4..0000000 --- a/src/analysis/batteryUpdater.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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) - * - * How to setup this analysis - * Make sure you have the following enviroment variables: - * - 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. - */ - -import { Utils, Account, Analysis } from "@tago-io/sdk"; -import { Data } from "@tago-io/sdk/out/common/common.types"; -import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types"; -import { fetchDeviceList } from "../lib/fetchDeviceList"; - -async function resolveDevice(context: TagoContext, account: Account, org_id: string, device_id: string) { - if(!account || !org_id || !device_id) { - throw "Missing Router parameter"; - } - const device = await Utils.getDevice(account, device_id); - - const device_params = await account.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 device.getData({ variables: ["bat", "battery_capacity"], qty: 1 }); - - if (dev_battery?.value) { - await account.devices.paramSet(device_id, { ...dev_battery_param, value: String(dev_battery.value) }); - } -} - -async function handler(context: TagoContext, scope: Data[]): Promise { - console.debug("Running Analysis"); - - const environment = Utils.envToJson(context.environment); - if (!environment) { - return; - } else if (!environment.account_token) { - throw "Missing account_token environment var"; - } - - const account = new Account({ token: environment.account_token }); - - const sensorList = await fetchDeviceList(account, [{ key: "device_type", value: "device" }]); - - sensorList.map((device) => - resolveDevice(context, account, device.tags.find((tag) => tag.key === "organization_id")?.value as string, device.tags.find((tag) => tag.key === "device_id")?.value as string) - ); -} - -async function startAnalysis(context: TagoContext, scope: any) { - try { - await handler(context, scope); - 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 }; diff --git a/src/analysis/clear-buckets.ts b/src/analysis/clear-buckets.ts new file mode 100644 index 0000000..fbac945 --- /dev/null +++ b/src/analysis/clear-buckets.ts @@ -0,0 +1,21 @@ +import { Analysis, Resources } from "@tago-io/sdk"; + +import { fetchDeviceList } from "../lib/fetch-device-list"; + +/** + * Function to start the analysis and clear variables from devices of type organization + * @param context + * @param scope + */ +async function startAnalysis() { + const deviceList = await fetchDeviceList({ tags: [{ key: "device_type", value: "organization" }] }); + + for (const device of deviceList) { + const result = await Resources.devices.deleteDeviceData(device.id, { variables: ["device_qty", "plan_usage"], qty: 9999 }); + console.debug(result); + } +} + +if (!process.env.T_TEST) { + Analysis.use(startAnalysis, { token: process.env.T_ANALYSIS_TOKEN }); +} diff --git a/src/analysis/clearBuckets.ts b/src/analysis/clearBuckets.ts deleted file mode 100644 index 59da5f5..0000000 --- a/src/analysis/clearBuckets.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Utils, Account, Device, Analysis } from "@tago-io/sdk"; -import { Data } from "@tago-io/sdk/out/common/common.types"; -import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types"; -import { fetchDeviceList } from "../lib/fetchDeviceList"; - -/** - * Function to start the analysis and clear variables from devices of type organization - * @param context - * @param scope - */ -async function startAnalysis(context: TagoContext, scope: Data[]) { - const environment = Utils.envToJson(context.environment); - if (!environment) { - return; - } - else if (!environment.account_token) { - throw "Missing account_token environment var"; - } - const account = new Account({ token: environment.account_token }); - const deviceList = await fetchDeviceList(account, [{ key: "device_type", value: "organization" }]); - - for (const device of deviceList) { - const dev = await Utils.getDevice(account, device.id); - const result = await dev.deleteData({ variables: ["device_qty", "plan_usage"], qty: 9999 }); - console.debug(result); - } -} - - -if (!process.env.T_TEST) { - Analysis.use(startAnalysis, { token: process.env.T_ANALYSIS_TOKEN }); -} diff --git a/src/analysis/data-retention.ts b/src/analysis/data-retention.ts new file mode 100644 index 0000000..6af0b13 --- /dev/null +++ b/src/analysis/data-retention.ts @@ -0,0 +1,89 @@ +/* + * KickStarter Analysis + * Data Retention Updater + * + * This analysis gets all sensors in the application and make sure bucket data retention is set properly. + */ + +import { Analysis, Resources } from "@tago-io/sdk"; +import { DeviceListItem } from "@tago-io/sdk/lib/types"; + +import { fetchDeviceList } from "../lib/fetch-device-list"; + +/** + * Function that resolves the data retention of the organization + * @param org_id Organization ID to resolve the data retention + */ +async function resolveDataRetentionByOrg(org_id: string) { + const device_list: DeviceListItem[] = await fetchDeviceList({ + tags: [ + { key: "device_type", value: "device" }, + { key: "organization_id", value: org_id }, + ], + }); + + for (const device_obj of device_list) { + // @ts-ignore: Unreachable code error + if (!device_obj.bucket) { + return; + } + + const bucket_variables = await Resources.buckets.listVariables(device_obj.bucket.id); + if (!bucket_variables[0]) { + return; + } + + const bucket_vars = bucket_variables.map((v) => v.variable); + const data_retention_ignore = bucket_vars + .map((r) => { + if (!r.includes("action")) { + return null; + } + return r; + }) + .filter((x) => x); + // @ts-ignore: Unreachable code error + await Resources.buckets.edit(device_obj.bucket.id, { data_retention_ignore }); + } +} + +/** + * Function that updates the data retention of the application + */ +async function updateDataRetention() { + console.debug("Running"); + + const organization_list: DeviceListItem[] = await fetchDeviceList({ tags: [{ key: "device_type", value: "organization" }] }); + + for (const org of organization_list) { + const org_id = org.id; + const org_param_list = await Resources.devices.paramList(org_id); + const plan_data_retention = org_param_list.find((x) => x.key === "plan_data_retention")?.value || ""; + + if (plan_data_retention !== "") { + await resolveDataRetentionByOrg(org_id); + } + } + + console.debug("success"); +} + +/** + * Function that starts the analysis + */ +async function startAnalysis() { + await updateDataRetention() + .then(() => { + console.debug("Script end."); + }) + .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 }; diff --git a/src/analysis/dataRetention.ts b/src/analysis/dataRetention.ts deleted file mode 100644 index ac2bdb7..0000000 --- a/src/analysis/dataRetention.ts +++ /dev/null @@ -1,105 +0,0 @@ -/* - * KickStarter Analysis - * Data Retetion Updater - * - * This analysis gets all sensors in the application and make sure bucket data retention is set properly. - * - * 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. - * You also must have an action of type Schedule, and set this analysis to run each day (recommended). - * - 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. - */ - -import { Utils, Account, Analysis } from "@tago-io/sdk"; -import { DeviceListItem } from "@tago-io/sdk/out/modules/Account/devices.types"; -import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types"; -import { fetchDeviceList } from "../lib/fetchDeviceList"; - -/** - * Function that resolves the data retention of the organization - * @param account Account class - * @param org_id Organization ID to resolve the data retention - * @param plan_data_retention Data retention of the plan - */ -async function resolveDataRetentionByOrg(account: Account, org_id: string, plan_data_retention: string) { - const device_list: DeviceListItem[] = await fetchDeviceList(account, [ - { key: "device_type", value: "device" }, - { key: "organization_id", value: org_id }, - ]); - - device_list.forEach(async (device_obj) => { - // @ts-ignore: Unreachable code error - await account.buckets.edit(device_obj.bucket, { data_retention: plan_data_retention === "0" ? "forever" : `${plan_data_retention} months` }); - - const bucket_variables = await account.buckets.listVariables(device_obj.bucket); - if (!bucket_variables[0]) { - return; - } - - const bucket_vars = bucket_variables.map((v) => v.variable); - const data_retention_ignore = bucket_vars - .map((r) => { - if (!r.includes("action")) { - return null; - } - return r; - }) - .filter((x) => x); - // @ts-ignore: Unreachable code error - await account.buckets.edit(device_obj.bucket, { data_retention_ignore }); - }); -} - -/** - * Function that updates the data retention of the application - * @param context Context is a variable sent by the analysis - */ -async function updateDataRetention(context: TagoContext) { - console.debug("Running"); - const env_vars = Utils.envToJson(context.environment); - if (!env_vars.account_token) { - throw console.debug("Missing account_token in the environment variables"); - } - if (!env_vars.config_token) { - throw console.debug("Missing config_token in the environment variables"); - } - - const account = new Account({ token: env_vars.account_token }); - - const organization_list: DeviceListItem[] = await fetchDeviceList(account, [{ key: "device_type", value: "organization" }]); - - for (const org of organization_list) { - const org_id = org.id; - const org_param_list = await account.devices.paramList(org_id); - const plan_data_retention = org_param_list.find((x) => x.key === "plan_data_retention")?.value || ""; - - if (plan_data_retention !== "") { - await resolveDataRetentionByOrg(account, org_id, plan_data_retention); - } - } - - console.debug("success"); -} - -/** - * Function that starts the analysis - * @param context Context is a variable sent by the analysis - * @param scope Scope is a variable sent by the analysis - */ -async function startAnalysis(context: TagoContext, scope: any) { - await updateDataRetention(context) - .then(() => { - console.debug("Script end."); - }) - .catch((e) => { - console.debug(e); - console.debug(e.message || JSON.stringify(e)); - }); -} - -if (!process.env.T_TEST) { - Analysis.use(startAnalysis, { token: process.env.T_ANALYSIS_TOKEN }); -} - -export { startAnalysis }; diff --git a/src/analysis/monthly-usage-reset.ts b/src/analysis/monthly-usage-reset.ts new file mode 100644 index 0000000..1c78cf1 --- /dev/null +++ b/src/analysis/monthly-usage-reset.ts @@ -0,0 +1,46 @@ +/* + * KickStarter Analysis + * Monthly Usage Reset + * + * This analysis will reset the monthly usage of SMS and Email from all clients. + * + * How it works: + * - The action "[TagoIO] - Monthly plan reset trigger" will trigger this analysis on the first day of each month at 00:00 UTC. + * - Organization's SMS and Email usage will be reset to 0. + */ + +import { Analysis, Resources } from "@tago-io/sdk"; +import { DeviceListItem } from "@tago-io/sdk/lib/types"; + +import { fetchDeviceList } from "../lib/fetch-device-list"; + +/** + * Function that initializes the analysis + * @param context Context is a variable sent by the analysis + * @param scope Scope is a variable sent by the analysis + */ +async function init(): Promise { + console.debug("Monthly usage reset analysis started"); + + const org_list: DeviceListItem[] = await fetchDeviceList({ tags: [{ key: "device_type", value: "organization" }] }); + + for (const org of org_list) { + const org_params = await Resources.devices.paramList(org.id); + + const plan_email_limit_usage = org_params.find((x) => x.key === "plan_email_limit_usage") || { key: "plan_email_limit_usage", value: "0", sent: false }; + const plan_sms_limit_usage = org_params.find((x) => x.key === "plan_sms_limit_usage") || { key: "plan_sms_limit_usage", value: "0", sent: false }; + const plan_notif_limit_usage = org_params.find((x) => x.key === "plan_notif_limit_usage") || { key: "plan_notif_limit_usage", value: "0", sent: false }; + + await Resources.devices.paramSet(org.id, { ...plan_email_limit_usage, value: "0", sent: false }); + await Resources.devices.paramSet(org.id, { ...plan_sms_limit_usage, value: "0", sent: false }); + await Resources.devices.paramSet(org.id, { ...plan_notif_limit_usage, value: "0", sent: false }); + } + + return console.debug("Analysis finished successfuly!"); +} + +if (!process.env.T_TEST) { + Analysis.use(init, { token: process.env.T_ANALYSIS_TOKEN }); +} + +export { init }; diff --git a/src/analysis/monthlyUsageReset.ts b/src/analysis/monthlyUsageReset.ts deleted file mode 100644 index f0afe30..0000000 --- a/src/analysis/monthlyUsageReset.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * KickStarter Analysis - * Monthly Usage Reset - * - * This analysis will reset the monthly usage of SMS and Email from all clients. - * - * How it works: - * - The action "[TagoIO] - Monthly plan reset trigger" will trigger this analysis on the first day of each month at 00:00 UTC. - * - Organization's SMS and Email usage will be reset to 0. - * - * How to setup this analysis - * Make sure you have the following enviroment variables: - * - 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. - */ - -import { Account, Device, Analysis, Utils } from "@tago-io/sdk"; -import { Data } from "@tago-io/sdk/out/common/common.types"; -import { DeviceListItem } from "@tago-io/sdk/out/modules/Account/devices.types"; -import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types"; -import { fetchDeviceList } from "../lib/fetchDeviceList"; - -/** - * Function that initializes the analysis - * @param context Context is a variable sent by the analysis - * @param scope Scope is a variable sent by the analysis - */ -async function init(context: TagoContext, scope: Data[]): Promise { - console.debug("Monthly usage reset analysis started"); - // Convert the environment variables from [{ key, value }] to { key: value }; - const environment = Utils.envToJson(context.environment); - if (!environment) { - throw "Missing environment variables"; - } - - if (!environment.account_token) { - throw "Missing account_token environment var"; - } - // Instance the Account class - const account = new Account({ token: environment.account_token }); - - const org_list: DeviceListItem[] = await fetchDeviceList(account, [{ key: "device_type", value: "organization" }]); - - for (const org of org_list) { - const org_params = await account.devices.paramList(org.id); - - const plan_email_limit_usage = org_params.find((x) => x.key === "plan_email_limit_usage") || { key: "plan_email_limit_usage", value: "0", sent: false }; - const plan_sms_limit_usage = org_params.find((x) => x.key === "plan_sms_limit_usage") || { key: "plan_sms_limit_usage", value: "0", sent: false }; - const plan_notif_limit_usage = org_params.find((x) => x.key === "plan_notif_limit_usage") || { key: "plan_notif_limit_usage", value: "0", sent: false }; - - await account.devices.paramSet(org.id, { ...plan_email_limit_usage, value: "0", sent: false }); - await account.devices.paramSet(org.id, { ...plan_sms_limit_usage, value: "0", sent: false }); - await account.devices.paramSet(org.id, { ...plan_notif_limit_usage, value: "0", sent: false }); - } - - return console.debug("Analysis finished successfuly!"); -} - -if (!process.env.T_TEST) { - Analysis.use(init, { token: process.env.T_ANALYSIS_TOKEN }); -} - -export { init }; diff --git a/src/analysis/sendReport.ts b/src/analysis/send-report.ts similarity index 66% rename from src/analysis/sendReport.ts rename to src/analysis/send-report.ts index 01b209b..5bcdcea 100644 --- a/src/analysis/sendReport.ts +++ b/src/analysis/send-report.ts @@ -7,22 +7,16 @@ * Reports are generated when: * - On the dashboard Report, through Send Now button; * - When setting a scheduled report, an action will trigger this script. - * - * How to setup this analysis - * Make sure you have the following enviroment variables: - * - 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. */ import dayjs from "dayjs"; -import { Account, Analysis, Device, Utils } from "@tago-io/sdk"; -import { ActionInfo } from "@tago-io/sdk/out/modules/Account/actions.types"; -import { UserInfo } from "@tago-io/sdk/out/modules/Account/run.types"; -import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types"; +import { Analysis, Resources } from "@tago-io/sdk"; +import { ActionInfo, TagoContext, UserInfo } from "@tago-io/sdk/lib/types"; -import html_body from "../lib/html_body"; -import sendPDF from "../lib/sendPDF"; -import checkAndChargeUsage from "../services/plan/checkAndChargeUsage"; +import { htmlBody } from "../lib/html-body"; +import { createPDF } from "../lib/send-pdf"; +import { checkAndChargeUsage } from "../services/plan/check-and-charge-usage"; interface SensorData { name: string; @@ -34,18 +28,16 @@ interface SensorData { /** * Function that resolves the report of the organization and send it to the user - * @param account Account instance class * @param context Context is a variable sent by the analysis * @param action_info Action information of the action that triggered the analysis * @param org_id Organization ID to resolve the report * @param via Via is a string that defines how the report was triggered */ -async function resolveReport(account: Account, context: TagoContext, action_info: ActionInfo, org_id: string, via?: string) { - if (!account || !context || !action_info || !org_id) { +async function resolveReport(context: TagoContext, action_info: ActionInfo, org_id: string, via?: string) { + if (!context || !action_info || !org_id) { throw "Missing Router parameter"; } - const org_dev = await Utils.getDevice(account, org_id); - const { name: org_name } = await org_dev.info(); + const { name: org_name } = await Resources.devices.info(org_id); let sensor_id_list: string[] = []; @@ -62,8 +54,7 @@ async function resolveReport(account: Account, context: TagoContext, action_info const site_id_list = action_group_list.split(", "); for (const site_id of site_id_list) { - const site_dev = await Utils.getDevice(account, site_id); - const site_dev_id_list = await site_dev.getData({ variables: "dev_id", qty: 9999 }); + const site_dev_id_list = await Resources.devices.getDeviceData(site_id, { variables: "dev_id", qty: 9999 }); for (const dev_id_data of site_dev_id_list) { const existing_sensor = sensor_id_list.find((id) => id === (dev_id_data?.value as string)); @@ -78,13 +69,14 @@ async function resolveReport(account: Account, context: TagoContext, action_info const report_data: SensorData[] = []; - for (const sensor of sensor_id_list) { - const sensor_dev = await Utils.getDevice(account, sensor).catch((msg) => console.debug(msg)); - if (!sensor_dev) { + for (const id of sensor_id_list) { + const sensor_info = await Resources.devices.info(id); + + if (!sensor_info) { continue; //sensor has been deleted } - const last_input = (await sensor_dev.info()).last_input; - const sensor_data = await sensor_dev.getData({ variables: ["temperature", "humidity", "compressor", "battery", "rssi"], qty: 1 }); + + const sensor_data = await Resources.devices.getDeviceData(id, { variables: ["temperature", "humidity", "compressor", "battery", "rssi"], qty: 1 }); // const status_history = sensor_data.find((x) => x.variable === "status_history"); const temperature = sensor_data.find((x) => x.variable === "temperature"); const humidity = sensor_data.find((x) => x.variable === "humidity"); @@ -96,14 +88,12 @@ async function resolveReport(account: Account, context: TagoContext, action_info const battery = sensor_data.find((x) => x.variable === "battery"); const rssi = sensor_data.find((x) => x.variable === "rssi"); - const { name: sensor_name } = await sensor_dev.info(); - report_data.push({ - name: sensor_name, + name: sensor_info.name, status: status_history, battery: `${(battery?.value as string) ?? "N/A"}${battery?.unit ?? ""}`, rssi: (rssi?.value as string) ?? "N/A", - date: dayjs(String(last_input)).format("YYYY-MM-DD HH:mm:ss"), + date: dayjs(String(sensor_info.last_input)).format("YYYY-MM-DD HH:mm:ss"), }); } @@ -114,12 +104,12 @@ async function resolveReport(account: Account, context: TagoContext, action_info RSSI Last Input`; - let final_html_body = html_body; + let final_html_body = htmlBody; final_html_body = final_html_body.replace("$TABLE_HEADER$", table_header); let report_table = ``; - report_data.forEach((data) => { + for (const data of report_data) { let report_row = ` $NAME$ @@ -135,11 +125,11 @@ async function resolveReport(account: Account, context: TagoContext, action_info report_row = report_row.replace("$DATE$", data.date); report_table = report_table.concat(report_row); - }); + } final_html_body = final_html_body.replace("$REPORT_TABLE$", report_table); - const org_indicators = await org_dev.getData({ variables: ["device_qty"], qty: 1 }); + const org_indicators = await Resources.devices.getDeviceData(org_id, { variables: ["device_qty"], qty: 1 }); const total_qty = org_indicators[0].value || "0"; const active_qty = org_indicators[0]?.metadata?.active_qty || "0"; const inactive_qty = org_indicators[0]?.metadata?.inactive_qty || "0"; @@ -162,7 +152,7 @@ async function resolveReport(account: Account, context: TagoContext, action_info const all_users_label: string[] = []; for (const user_id_from_list of users_id_list) { - const current_user_info = await account.run.userInfo(user_id_from_list).catch((msg) => console.debug(msg)); + const current_user_info = await Resources.run.userInfo(user_id_from_list).catch((error) => console.debug(error)); if (!current_user_info) { //user has been deleted continue; @@ -174,10 +164,10 @@ async function resolveReport(account: Account, context: TagoContext, action_info //check if users are invited and still existing first -> charge from which will be actually being sent. const to_dispatch_qty = users_info_list.length; - const plan_service_status = await checkAndChargeUsage(account, context, org_id, to_dispatch_qty, "email"); + const plan_service_status = await checkAndChargeUsage(context, org_id, to_dispatch_qty, "email"); if (plan_service_status === false) { - return await org_dev.sendData([ + return await Resources.devices.sendDeviceData(org_id, [ { variable: "plan_status", value: `Attempt to send ${to_dispatch_qty} report(s) was not successful. No email service limit available, check your plan status or get in touch with us.`, @@ -188,15 +178,15 @@ async function resolveReport(account: Account, context: TagoContext, action_info let filename: string | undefined; if (users_id_list.length > 0 && plan_service_status) { - filename = await sendPDF(context, final_html_body, users_info_list, org_name, org_id); - } else if (users_id_list.length == 0) { - return await org_dev.sendData([{ variable: "report_sent", value: `Report has not been sent. No user registered.`, metadata: { users: "-" } }]); + filename = await createPDF(context, final_html_body, users_info_list, org_name, org_id); + } else if (users_id_list.length === 0) { + return await Resources.devices.sendDeviceData(org_id, [{ variable: "report_sent", value: `Report has not been sent. No user registered.`, metadata: { users: "-" } }]); } const all_users_string = all_users_label.join(", "); - const url_file = filename ? `https://api.tago.io/file/651e993977b37400096c7860${filename}` : "-"; - await org_dev.sendData([{ variable: "report_sent", value: `Report has been sent. Via: ${via}.`, metadata: { users: all_users_string, url_file } }]); + const url_file = filename ? `https://api.tago.io/file/61b2f46e561da800197a9c43${filename}` : "-"; + await Resources.devices.sendDeviceData(org_id, [{ variable: "report_sent", value: `Report has been sent. Via: ${via}.`, metadata: { users: all_users_string, url_file } }]); } /** @@ -207,25 +197,13 @@ async function resolveReport(account: Account, context: TagoContext, action_info async function startAnalysis(context: TagoContext, scope: any) { console.debug("Running Analysis"); - const environment = Utils.envToJson(context.environment); - if (!environment) { - return; - } else if (!environment.config_token) { - throw "Missing config_token environment var"; - } else if (!environment.account_token) { - throw "Missing account_token environment var"; - } - - const config_dev = new Device({ token: environment.config_token }); - const account = new Account({ token: environment.account_token }); - let org_id: string | undefined = ""; const action_id = context.environment.find((x) => x.key === "_action_id")?.value as string; if (action_id) { //THROUGH ACTION - const action_info = await account.actions.info(action_id); + const action_info = await Resources.actions.info(action_id); const { tags } = action_info; @@ -238,12 +216,12 @@ async function startAnalysis(context: TagoContext, scope: any) { throw "organization_id not found"; } - resolveReport(account, context, action_info, org_id, "Squeduled Action"); + void resolveReport(context, action_info, org_id, "Squeduled Action"); } else if (scope) { //THROUGH BUTTON SEND NOW const action_group = scope[0]?.group; - const [action_registered] = await account.actions.list({ + const [action_registered] = await Resources.actions.list({ page: 1, fields: ["id", "name", "tags"], filter: { @@ -261,7 +239,7 @@ async function startAnalysis(context: TagoContext, scope: any) { throw "organization_id not found"; } - resolveReport(account, context, action_registered, org_id, "Button"); + void resolveReport(context, action_registered, org_id, "Button"); } } diff --git a/src/analysis/statusUpdater.ts b/src/analysis/status-updater.ts similarity index 54% rename from src/analysis/statusUpdater.ts rename to src/analysis/status-updater.ts index 0d5b49b..764f47f 100644 --- a/src/analysis/statusUpdater.ts +++ b/src/analysis/status-updater.ts @@ -8,39 +8,36 @@ * * Status Updater will run when: * - When the scheduled action (Status Updater Trigger) triggers this script. (Default 1 minute) - * - * How to setup this analysis - * Make sure you have the following enviroment variables: - * - 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. */ -import { Utils, Account, Device, Analysis } from "@tago-io/sdk"; -import { Data } from "@tago-io/sdk/out/common/common.types"; -import { DeviceListItem } from "@tago-io/sdk/out/modules/Account/devices.types"; -import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types"; +import async from "async"; import dayjs from "dayjs"; -import async, { queue } from "async"; + +import { Analysis, Resources, Utils } from "@tago-io/sdk"; +import { DeviceInfo, DeviceListItem, TagoContext } from "@tago-io/sdk/lib/types"; + import { parseTagoObject } from "../lib/data.logic"; -import { fetchDeviceList } from "../lib/fetchDeviceList"; -import { checkinTrigger } from "../services/alerts/checkinAlerts"; +import { fetchDeviceList } from "../lib/fetch-device-list"; +import { checkInTrigger } from "../services/alerts/check-in-alerts"; /** * Function that update the organization's plan usage - * @param account Account instance class * @param org Organization device */ -async function resolveOrg(account: Account, org: DeviceListItem) { +async function resolveOrg(org: DeviceListItem) { let total_qty = 0; let active_qty = 0; - let inactivy_qty = 0; + let inactive_qty = 0; const org_id = org.id; - const sensorList = await fetchDeviceList(account, [ - { key: "organization_id", value: org.id }, - { key: "device_type", value: "device" }, - ]); + const sensorList = await fetchDeviceList({ + tags: [ + { key: "organization_id", value: org.id }, + { key: "device_type", value: "device" }, + ], + }); - sensorList.forEach((sensor) => { + for (const sensor of sensorList) { const last_input = dayjs(sensor.last_input); const now = dayjs(); const diff_time = now.diff(last_input, "hours"); @@ -48,13 +45,11 @@ async function resolveOrg(account: Account, org: DeviceListItem) { if (diff_time < 24) { active_qty++; } else { - inactivy_qty++; + inactive_qty++; } - }); - - const org_dev = await Utils.getDevice(account, org_id); + } - const org_params = await account.devices.paramList(org_id); + const org_params = await Resources.devices.paramList(org_id); const plan_email_limit_usage = org_params.find((x) => x.key === "plan_email_limit_usage")?.value || "0"; const plan_sms_limit_usage = org_params.find((x) => x.key === "plan_sms_limit_usage")?.value || "0"; @@ -62,7 +57,7 @@ async function resolveOrg(account: Account, org: DeviceListItem) { const plan_data_retention = org_params.find((x) => x.key === "plan_data_retention")?.value || "0"; const to_tago = { - device_qty: { value: total_qty, metadata: { total_qty: total_qty, active_qty: active_qty, inactive_qty: inactivy_qty } }, + device_qty: { value: total_qty, metadata: { total_qty: total_qty, active_qty: active_qty, inactive_qty: inactive_qty } }, plan_usage: { value: plan_email_limit_usage, metadata: { @@ -73,8 +68,8 @@ async function resolveOrg(account: Account, org: DeviceListItem) { }, }, }; - //CONSIDER INSTEAD OF DELETEING VARIABLES, PLACE A DATA RETENTION RULE AND SHOW THEM IN A HISTORIC GRAPIHC ON THE WIDGET HEADER BUTTON - const old_data = await org_dev.getData({ + //CONSIDER INSTEAD OF DELETING VARIABLES, PLACE A DATA RETENTION RULE AND SHOW THEM IN A HISTORIC GRAPHIC ON THE WIDGET HEADER BUTTON + const old_data = await Resources.devices.getDeviceData(org_id, { variables: ["device_qty", "plan_usage"], query: "last_item", }); @@ -83,11 +78,11 @@ async function resolveOrg(account: Account, org: DeviceListItem) { const new_data = parseTagoObject(to_tago); if (!device_data || !plan_data) { - await org_dev.sendData(new_data); + await Resources.devices.sendDeviceData(org_id, new_data); return; } - await org_dev.editData([ + await Resources.devices.editDeviceData(org_id, [ { ...device_data, ...new_data[0] }, { ...plan_data, ...new_data[1] }, ]); @@ -95,57 +90,53 @@ async function resolveOrg(account: Account, org: DeviceListItem) { /** * Function that update the sensor's params (last checkin and battery) - * @param account Account instance class - * @param device Sensor device + * @param sensor_info - Sensor information */ -const checkLocation = async (account: Account, device: Device) => { - const [location_data] = await device.getData({ variables: "location", qty: 1 }); +const checkLocation = async (sensor_info: DeviceInfo) => { + const [location_data] = await Resources.devices.getDeviceData(sensor_info.id, { variables: "location", qty: 1 }); if (!location_data) { return "No location sent by device"; } - const device_info = await device.info(); - const site_id = device_info.tags.find((x) => x.key === "site_id")?.value; + const site_id = sensor_info.tags.find((x) => x.key === "site_id")?.value; if (!site_id) { return "No site addressed to the sensor"; } - const site_dev = await Utils.getDevice(account, site_id); - const [dev_id] = await site_dev.getData({ variables: "dev_id", groups: device_info.id, qty: 1 }); + const [dev_id] = await Resources.devices.getDeviceData(site_id, { variables: "dev_id", groups: sensor_info.id, qty: 1 }); if ( (dev_id?.location as any).coordinates[0] === (location_data.location as any).coordinates[0] && (dev_id?.location as any).coordinates[1] === (location_data.location as any).coordinates[1] ) { return "Same position"; } - await site_dev.editData({ ...dev_id, location: location_data.location }); + await Resources.devices.editDeviceData(site_id, { ...dev_id, location: location_data.location }); }; /** * Function that update the sensor's params (last checkin and battery) * @param context - * @param account * @param org_id * @param device_id */ -async function resolveDevice(context: TagoContext, account: Account, org_id: string, device_id: string) { +async function resolveDevice(context: TagoContext, org_id: string, device_id: string) { console.debug("Resolving device", device_id); - const device = await Utils.getDevice(account, device_id).catch((msg) => console.debug(msg)); + const sensor_info = await Resources.devices.info(device_id); - if (!device) { + if (!sensor_info) { return Promise.reject("Device not found"); } - checkLocation(account, device).catch((msg) => console.debug(msg)); + checkLocation(sensor_info).catch((error) => console.debug(error)); - const device_info = await account.devices.info(device_id); + const device_info = await Resources.devices.info(device_id); if (!device_info.last_input) { return Promise.reject("Device not found"); } - const checkin_date = dayjs(device_info.last_input as Date); + const checkin_date = dayjs(device_info.last_input); if (!checkin_date) { return "no data"; @@ -157,47 +148,40 @@ async function resolveDevice(context: TagoContext, account: Account, org_id: str diff_hours = "-"; } //checking for NaN - const device_params = await account.devices.paramList(device_id); + const device_params = await Resources.devices.paramList(device_id); const dev_lastcheckin_param = device_params.find((param) => param.key === "dev_lastcheckin") || { key: "dev_lastcheckin", value: String(diff_hours), sent: false }; - await checkinTrigger(account, context, org_id, { device_id, last_input: device_info.last_input }); + await checkInTrigger(context, org_id, { device_id, last_input: device_info.last_input }); - await account.devices.paramSet(device_id, { ...dev_lastcheckin_param, value: String(diff_hours), sent: (diff_hours as number) >= 24 ? true : false }); + await Resources.devices.paramSet(device_id, { ...dev_lastcheckin_param, value: String(diff_hours), sent: (diff_hours as number) >= 24 ? true : false }); console.debug("Device resolved", device_id); } -async function handler(context: TagoContext, scope: Data[]): Promise { +async function handler(context: TagoContext): Promise { console.debug("Running Analysis"); const environment = Utils.envToJson(context.environment); if (!environment) { return; - } else if (!environment.config_token) { - throw "Missing config_token environment var"; - } else if (!environment.account_token) { - throw "Missing account_token environment var"; } - const config_dev = new Device({ token: environment.config_token }); - const account = new Account({ token: environment.account_token }); - - const orgList = await fetchDeviceList(account, [{ key: "device_type", value: "organization" }]); + const orgList = await fetchDeviceList({ tags: [{ key: "device_type", value: "organization" }] }); - orgList.map((org) => resolveOrg(account, org)); + orgList.map((org) => resolveOrg(org)); - const sensorList = await fetchDeviceList(account, [{ key: "device_type", value: "device" }]); + const sensorList = await fetchDeviceList({ tags: [{ key: "device_type", value: "device" }] }); const processSensorQueue = async.queue(async function (sensorItem: DeviceListItem, callback) { const organization_id = sensorItem?.tags?.find((tag) => tag.key === "organization_id")?.value as string; const device_id = sensorItem?.tags?.find((tag) => tag.key === "device_id")?.value as string; - await resolveDevice(context, account, organization_id, device_id).catch((msg) => console.debug(`${msg} - ${sensorItem.id}`)); + await resolveDevice(context, organization_id, device_id).catch((error) => console.debug(`${error} - ${sensorItem.id}`)); callback(); }, 1); //populating the queue for (const sensorItem of sensorList) { - processSensorQueue.push(sensorItem); + void processSensorQueue.push(sensorItem); } // console.debug("Queue populated", processSensorQueue.length()); @@ -205,16 +189,18 @@ async function handler(context: TagoContext, scope: Data[]): Promise { await processSensorQueue.drain(); //throwing possible errors generated while running the queue - processSensorQueue.error((error) => { console.debug(error); process.exit(); }); } -async function startAnalysis(context: TagoContext, scope: any) { +/** + * Start the analysis + */ +async function startAnalysis(context: TagoContext) { try { - await handler(context, scope); + await handler(context); console.debug("Analysis finished"); } catch (error) { console.debug(error); diff --git a/src/analysis/userSignUp.ts b/src/analysis/user-sign-up.ts similarity index 68% rename from src/analysis/userSignUp.ts rename to src/analysis/user-sign-up.ts index 105c3bd..c46d646 100644 --- a/src/analysis/userSignUp.ts +++ b/src/analysis/user-sign-up.ts @@ -14,11 +14,10 @@ * Create an Action of type Resource whenever an user is registered in the application to run this analysis. */ -import { Utils, Account, Device, Analysis } from "@tago-io/sdk"; -import { UserInfo } from "@tago-io/sdk/out/modules/Account/run.types"; -import { TagoContext } from "@tago-io/sdk/out/modules/Analysis/analysis.types"; -import { parseTagoObject } from "../lib/data.logic"; +import { Analysis, Resources, Utils } from "@tago-io/sdk"; +import { TagoContext, UserInfo } from "@tago-io/sdk/lib/types"; +import { parseTagoObject } from "../lib/data.logic"; import { orgAdd } from "../services/organization/register"; /** @@ -38,15 +37,11 @@ async function startAnalysis(context: TagoContext, scope: UserInfo[]): Promise