fix(basic.gblib): Allow MERGE keyword in storage #380.

This commit is contained in:
Rodrigo Rodriguez 2023-10-21 12:09:37 -03:00
parent 1dd7b2aa99
commit 1575bf2661
4 changed files with 73 additions and 284 deletions

View file

@ -98,7 +98,7 @@
"botlib": "3.0.11", "botlib": "3.0.11",
"c3-chart-maker": "0.2.8", "c3-chart-maker": "0.2.8",
"cd": "0.3.3", "cd": "0.3.3",
"chatgpt": "^2.4.2", "chatgpt": "2.4.2",
"chrome-remote-interface": "0.31.3", "chrome-remote-interface": "0.31.3",
"cli-progress": "3.11.2", "cli-progress": "3.11.2",
"cli-spinner": "0.2.10", "cli-spinner": "0.2.10",

View file

@ -373,6 +373,7 @@ export class GBVMService extends GBService {
// Imports npm packages for this .gbdialog conversational application. // Imports npm packages for this .gbdialog conversational application.
require('isomorphic-fetch'); require('isomorphic-fetch');
const http = require('node:http');
const createRpcClient = require("@push-rpc/core").createRpcClient; const createRpcClient = require("@push-rpc/core").createRpcClient;
const createHttpClient = require("@push-rpc/http").createHttpClient; const createHttpClient = require("@push-rpc/http").createHttpClient;
@ -454,15 +455,16 @@ export class GBVMService extends GBService {
const optsRPC = {callTimeout: this.callTimeout}; const optsRPC = {callTimeout: this.callTimeout};
let url; let url;
const agent = http.Agent({ keepAlive: true });
url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/dk'; url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/dk';
const dk = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; const dk = (await createRpcClient(0, () => createHttpClient(url, {agent: agent}), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/sys'; url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/sys';
const sys = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; const sys = (await createRpcClient(0, () => createHttpClient(url, {agent: agent}), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/wa'; url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/wa';
const wa = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; const wa = (await createRpcClient(0, () => createHttpClient(url, {agent: agent}), optsRPC)).remote;
url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/img'; url = 'http://localhost:${GBVMService.API_PORT}/api/v3/${min.botId}/img';
const img = (await createRpcClient(0, () => createHttpClient(url), optsRPC)).remote; const img = (await createRpcClient(0, () => createHttpClient(url, {agent: agent}), optsRPC)).remote;
${code} ${code}

View file

@ -351,11 +351,11 @@ export class KeywordsExpressions {
__url = __data.links?.next?.uri; __url = __data.links?.next?.uri;
__seekToken = __data.links?.self?.headers["MS-ContinuationToken"] __seekToken = __data.links?.self?.headers["MS-ContinuationToken"]
__totalCount = __data["totalCount"]; __totalCount = __data["totalCount"] ? __data["totalCount"] : __data.length;
while (__next) while (__next)
{ {
let ${$1} = __data.items[__index]; let ${$1} = __data?.items ? __data?.items[__index] : __data[__index];
`; `;
} }
]; ];
@ -370,21 +370,21 @@ export class KeywordsExpressions {
if (__index >= __totalCount) { if (__index >= __totalCount) {
// Check if HTTP call limit has reached. // Checks if HTTP call limit has reached.
if (__calls < __totalCalls) { if (__calls < __totalCalls) {
// Perform GET request using the constructed URL // Performs GET request using the constructed URL
__data = await sys.get ({pid: pid, file: __url, addressOrHeaders: headers, httpUsername, httpPs}); __data = await sys.get ({pid: pid, file: __url, addressOrHeaders: headers, httpUsername, httpPs});
// Update current variable handlers. // Updates current variable handlers.
__url = __data.links?.next?.uri; __url = __data.links?.next?.uri;
__seekToken = __data.links?.self?.headers["MS-ContinuationToken"] __seekToken = __data.links?.self?.headers["MS-ContinuationToken"]
__totalCount = __data["totalCount"]; __totalCount = __data["totalCount"];
index = 0; __index = 0;
__calls++; __calls++;
} else { } else {
@ -392,9 +392,8 @@ export class KeywordsExpressions {
next = false; next = false;
} }
index = index + 1;
} }
__index = __index + 1;
}`; }`;
} }
]; ];

View file

@ -656,27 +656,31 @@ export class SystemKeywords {
*/ */
public async saveToStorage({ pid, table, fieldsValues, fieldsNames }): Promise<any> { public async saveToStorage({ pid, table, fieldsValues, fieldsNames }): Promise<any> {
GBLog.info(`BASIC: Saving '${table}' (SAVE). Values: ${fieldsValues.join(',')}.`); GBLog.info(`BASIC: Saving to storage '${table}' (SAVE).`);
const minBoot = GBServer.globals.minBoot as any; const minBoot = GBServer.globals.minBoot as any;
const definition = minBoot.core.sequelize.models[table]; const definition = minBoot.core.sequelize.models[table];
let data = {}; let out = [];
let index = 0; let data = {}, data2 = {};
data = this.flattenJSON(fieldsValues,{}, '') // Flattern JSON to a table.
fieldsNames.forEach(field => { data = this.flattenJSON(fieldsValues, {}, '')
field = field.charAt(0).toUpperCase() + field.slice(1);
data[field] = fieldsValues[index++]; // Uppercases fields.
Object.keys(data).forEach(field => {
const field2 = field.charAt(0).toUpperCase() + field.slice(1);
data2[field2] = data[field];
}); });
return await definition.create(data); return await definition.create(data2);
} }
/** /**
* Saves the content of several variables to a new row in a tabular file. * Saves the content of several variables to a new row in a tabular file.
* *
* @exaple SAVE "customers.xlsx", name, email, phone, address, city, state, country * @example SAVE "customers.xlsx", name, email, phone, address, city, state, country
* *
*/ */
public async save({ pid, file, args }): Promise<any> { public async save({ pid, file, args }): Promise<any> {
@ -1506,12 +1510,12 @@ export class SystemKeywords {
return GBAdminService.getRndPassword(); return GBAdminService.getRndPassword();
} }
private flattenJSON(obj, res, extraKey, hierarchy=false) { private flattenJSON(obj, res, extraKey, hierarchy = false) {
for (let key in obj) { for (let key in obj) {
if (typeof obj[key] !== 'object') { if (typeof obj[key] !== 'object') {
res[extraKey + key] = obj[key]; res[extraKey + key] = obj[key];
} else { } else {
this.flattenJSON(obj[key], res, hierarchy?`${extraKey}${key}.`:''); this.flattenJSON(obj[key], res, hierarchy ? `${extraKey}${key}.` : '');
}; };
}; };
return res; return res;
@ -1555,246 +1559,6 @@ export class SystemKeywords {
options['qs'] = qs; options['qs'] = qs;
} }
let r1 = {
"totalCount": 2,
"items": [
{
"partnerId": "00083575-bbd0-54de-b2ad-0f5b0e927d71",
"partnerName": "MTBC",
"customerId": "",
"customerName": "",
"customerDomainName": "",
"invoiceNumber": "",
"productId": "",
"skuId": "",
"availabilityId": "",
"skuName": "VM-Series Next-Generation Firewall (Bundle 2 PAYG)",
"productName": "VM-Series Next Generation Firewall",
"publisherName": "Test Alto Networks, Inc.",
"publisherId": "",
"subscriptionId": "12345678-04d9-421c-baf8-e3b8dd62ddba",
"subscriptionDescription": "Pay-As-You-Go",
"chargeStartDate": "2019-01-01T00:00:00Z",
"chargeEndDate": "2019-02-01T00:00:00Z",
"usageDate": "2019-01-01T00:00:00Z",
"meterType": "1 Compute Hour - 4core",
"meterCategory": "Virtual Machine Licenses",
"meterId": "4core",
"meterSubCategory": "VM-Series Next Generation Firewall",
"meterName": "VM-Series Next Generation Firewall - VM-Series Next-Generation Firewall (Bundle 2 PAYG) - 4 Core Hours",
"meterRegion": "",
"unitOfMeasure": "1 Hour",
"resourceLocation": "EASTUS",
"consumedService": "Microsoft.Compute",
"resourceGroup": "ECH-PAN-RG",
"resourceUri": "/subscriptions/12345678-04d9-421c-baf8-e3b8dd62ddba/resourceGroups/ECH-PAN-RG/providers/Microsoft.Compute/virtualMachines/echpanfw",
"tags": "",
"additionalInfo": "{ \"ImageType\": null, \"ServiceType\": \"Standard_D3_v2\", \"VMName\": null, \"VMProperties\": null, \"UsageType\": \"ComputeHR_SW\"}",
"serviceInfo1": "",
"serviceInfo2": "",
"customerCountry": "",
"mpnId": "1234567",
"resellerMpnId": "",
"chargeType": "",
"unitPrice": 1.2799888920023,
"quantity": 24.0,
"unitType": "",
"billingPreTaxTotal": 30.7197334080551,
"billingCurrency": "USD",
"pricingPreTaxTotal": 30.7197334080551,
"pricingCurrency": "USD",
"entitlementId": "3f47bcf1-965d-40a1-a2bc-3d5db3653250",
"entitlementDescription": "Partner Subscription",
"pcToBCExchangeRate": 1,
"pcToBCExchangeRateDate": "2019-08-01T00:00:00Z",
"effectiveUnitPrice": 0,
"rateOfPartnerEarnedCredit": 0,
"rateOfCredit": 0,
"creditType": "Credit Not Applied",
"invoiceLineItemType": "usage_line_items",
"billingProvider": "marketplace",
"benefitOrderId": "5ea053d6-4a0d-46ef-bc82-15065b475d01",
"benefitId": "28ddab06-2c5b-479e-88bb-7b7bfda4e7fd",
"benefitType": "SavingsPlan",
"attributes": {
"objectType": "DailyRatedUsageLineItem"
}
},
{
"partnerId": "00083575-bbd0-54de-b2ad-0f5b0e927d71",
"partnerName": "MTBC",
"customerId": "",
"customerName": "",
"customerDomainName": "",
"invoiceNumber": "",
"productId": "",
"skuId": "",
"availabilityId": "",
"skuName": "VM-Series Next-Generation Firewall (Bundle 2 PAYG)",
"productName": "VM-Series Next Generation Firewall",
"publisherName": "Test Alto Networks, Inc.",
"publisherId": "",
"subscriptionId": "12345678-04d9-421c-baf8-e3b8dd62ddba",
"subscriptionDescription": "Pay-As-You-Go",
"chargeStartDate": "2019-01-01T00:00:00Z",
"chargeEndDate": "2019-02-01T00:00:00Z",
"usageDate": "2019-01-02T00:00:00Z",
"meterType": "1 Compute Hour - 4core",
"meterCategory": "Virtual Machine Licenses",
"meterId": "4core",
"meterSubCategory": "VM-Series Next Generation Firewall",
"meterName": "VM-Series Next Generation Firewall - VM-Series Next-Generation Firewall (Bundle 2 PAYG) - 4 Core Hours",
"meterRegion": "",
"unitOfMeasure": "1 Hour",
"resourceLocation": "EASTUS",
"consumedService": "Microsoft.Compute",
"resourceGroup": "ECH-PAN-RG",
"resourceUri": "/subscriptions/12345678-04d9-421c-baf8-e3b8dd62ddba/resourceGroups/ECH-PAN-RG/providers/Microsoft.Compute/virtualMachines/echpanfw",
"tags": "",
"additionalInfo": "{ \"ImageType\": null, \"ServiceType\": \"Standard_D3_v2\", \"VMName\": null, \"VMProperties\": null, \"UsageType\": \"ComputeHR_SW\"}",
"serviceInfo1": "",
"serviceInfo2": "",
"customerCountry": "",
"mpnId": "1234567",
"resellerMpnId": "",
"chargeType": "",
"unitPrice": 1.2799888920023,
"quantity": 24.0,
"unitType": "",
"billingPreTaxTotal": 30.7197334080551,
"billingCurrency": "USD",
"pricingPreTaxTotal": 30.7197334080551,
"pricingCurrency": "USD",
"entitlementId": "31cdf47f-b249-4edd-9319-637862d12345",
"entitlementDescription": "Partner Subscription",
"pcToBCExchangeRate": 1,
"pcToBCExchangeRateDate": "2019-08-01T00:00:00Z",
"effectiveUnitPrice": 0,
"rateOfPartnerEarnedCredit": 0,
"rateOfCredit": 1,
"creditType": "Azure Credit Applied",
"invoiceLineItemTypce": "usage_line_items",
"billingProvider": "marketplace",
"benefitOrderId": "",
"benefitId": "",
"benefitType": "Charge",
"attributes": {
"objectType": "DailyRatedUsageLineItem"
}
}
],
"links": {
"self": {
"uri": "/invoices/unbilled/lineitems?provider=onetime&invoicelineitemtype=usagelineitems&currencycode=usd&period=previous&size=2000",
"method": "GET",
"headers": []
},
"next": {
"uri": "/invoices/unbilled/lineitems?provider=onetime&invoicelineitemtype=usagelineitems&currencycode=usd&period=previous&size=2000&seekOperation=Next",
"method": "GET",
"headers": [
{
"key": "MS-ContinuationToken",
"value": "AQAAAA=="
}
]
}
},
"attributes": {
"objectType": "Collection"
}
};
let r2 =
{
"totalCount": 1,
"items": [
{
"partnerId": "00083575-bbd0-54de-b2ad-0f5b0e927d71",
"partnerName": "MTBC",
"customerId": "",
"customerName": "",
"customerDomainName": "",
"invoiceNumber": "",
"productId": "",
"skuId": "",
"availabilityId": "",
"skuName": "VM-Series Next-Generation Firewall (Bundle 2 PAYG)",
"productName": "VM-Series Next Generation Firewall",
"publisherName": "Test Alto Networks, Inc.",
"publisherId": "",
"subscriptionId": "12345678-04d9-421c-baf8-e3b8dd62ddba",
"subscriptionDescription": "Pay-As-You-Go",
"chargeStartDate": "2019-01-01T00:00:00Z",
"chargeEndDate": "2019-02-01T00:00:00Z",
"usageDate": "2019-01-02T00:00:00Z",
"meterType": "1 Compute Hour - 4core",
"meterCategory": "Virtual Machine Licenses",
"meterId": "4core",
"meterSubCategory": "VM-Series Next Generation Firewall",
"meterName": "VM-Series Next Generation Firewall - VM-Series Next-Generation Firewall (Bundle 2 PAYG) - 4 Core Hours",
"meterRegion": "",
"unitOfMeasure": "1 Hour",
"resourceLocation": "EASTUS",
"consumedService": "Microsoft.Compute",
"resourceGroup": "ECH-PAN-RG",
"resourceUri": "/subscriptions/12345678-04d9-421c-baf8-e3b8dd62ddba/resourceGroups/ECH-PAN-RG/providers/Microsoft.Compute/virtualMachines/echpanfw",
"tags": "",
"additionalInfo": "{ \"ImageType\": null, \"ServiceType\": \"Standard_D3_v2\", \"VMName\": null, \"VMProperties\": null, \"UsageType\": \"ComputeHR_SW\"}",
"serviceInfo1": "",
"serviceInfo2": "",
"customerCountry": "",
"mpnId": "1234567",
"resellerMpnId": "",
"chargeType": "",
"unitPrice": 1.2799888920023,
"quantity": 24.0,
"unitType": "",
"billingPreTaxTotal": 30.7197334080551,
"billingCurrency": "USD",
"pricingPreTaxTotal": 30.7197334080551,
"pricingCurrency": "USD",
"entitlementId": "31cdf47f-b249-4edd-9319-637862d8c0b4",
"entitlementDescription": "Partner Subscription",
"pcToBCExchangeRate": 1,
"pcToBCExchangeRateDate": "2019-08-01T00:00:00Z",
"effectiveUnitPrice": 0,
"rateOfPartnerEarnedCredit": 0.15,
"rateOfCredit": 0.15,
"creditType": "Partner Earned Credit Applied",
"invoiceLineItemType": "usage_line_items",
"billingProvider": "marketplace",
"benefitOrderId": "",
"benefitId": "",
"benefitType": "Charge",
"attributes": {
"objectType": "DailyRatedUsageLineItem"
}
}
],
"links": {
"self": {
"uri": "/invoices/unbilled/lineitems?provider=onetime&invoicelineitemtype=usagelineitems&currencycode=usd&period=previous&size=2000",
"method": "GET",
"headers": []
}
},
"attributes": {
"objectType": "Collection"
}
};
// let result;
// if (!SystemKeywords.aa) {
// SystemKeywords.aa = 1;
// return r1;
// } else {
// SystemKeywords.aa = null;
// return r2;
// }
const result = await fetch(url, options); const result = await fetch(url, options);
if (result.status === 2000) { if (result.status === 2000) {
@ -2126,7 +1890,9 @@ export class SystemKeywords {
throw new Error(`TABLE ${file} not found. Check TABLE keywords.`); throw new Error(`TABLE ${file} not found. Check TABLE keywords.`);
} }
rows = await t.findAll({}); rows = await t.findAll({});
header = rows['dataNames']; if (rows.length > 0) {
header = Object.keys(rows[0].dataValues)
}
} else { } else {
const botId = min.instance.botId; const botId = min.instance.botId;
@ -2151,18 +1917,15 @@ export class SystemKeywords {
rows = results.text; rows = results.text;
} }
// As BASIC uses arrays starting with 1 (one) as index,
// a ghost element is added at 0 (zero) position.
let table = []; let table = [];
table.push({ gbarray: '0' }); let foundIndex = 0;
let foundIndex = 1;
// Fills the row variable. // Fills the row variable.
for (; foundIndex < rows.length; foundIndex++) { for (; foundIndex < rows.length; foundIndex++) {
let row = {}; let row = {};
const xlRow = rows[foundIndex]; const xlRow = rows[foundIndex];
row = xlRow.dataValues ? xlRow.dataValues : xlRow;
for (let colIndex = 0; colIndex < xlRow.length; colIndex++) { for (let colIndex = 0; colIndex < xlRow.length; colIndex++) {
const propertyName = header[colIndex]; const propertyName = header[colIndex];
let value = xlRow[colIndex]; let value = xlRow[colIndex];
@ -2196,11 +1959,14 @@ export class SystemKeywords {
// Scans all sheet lines and compare keys. // Scans all sheet lines and compare keys.
const row = data[i]; let row = data[i];
let found; let found;
let key1Value; let key1Value;
if (key1Index) { if (key1Index) {
key1 = key1.charAt(0).toLowerCase() + key1.slice(1);
key1Value = row[key1]; key1Value = row[key1];
const foundRow = key1Index[key1Value]; const foundRow = key1Index[key1Value];
if (foundRow) { if (foundRow) {
@ -2209,22 +1975,27 @@ export class SystemKeywords {
} }
if (found) { if (found) {
row = this.flattenJSON(row, {}, '')
let keys = Object.keys(row); let keys = Object.keys(row);
for (let j = 0; j < keys.length; j++) { for (let j = 0; j < header.length; j++) {
const columnName = header[j]; const columnName = header[j];
const value = row[keys[j]]; const columnNameLower = columnName.charAt(0).toLowerCase() + columnName.slice(1);
const value = row[columnNameLower];
if (storage) { if (value !== found[columnName]) {
const obj = { id: keys[j], columnName: value }; if (storage) {
await t.update(obj, { where: { key1: key1Value } });
} else { const obj = { columnName: value };
await t.update(obj, { where: { key1: key1Value } });
const cell = `${this.numberToLetters(j)}${i + 1}`; } else {
const address = `${cell}:${cell}`;
if (value !== found[columnName]) { const cell = `${this.numberToLetters(j)}${i + 1}`;
const address = `${cell}:${cell}`;
await this.set({ pid, handle: null, file, address, value }); await this.set({ pid, handle: null, file, address, value });
merges++; merges++;
@ -2233,10 +2004,27 @@ export class SystemKeywords {
} }
} else { } else {
// Check if is a tree or flat object.
const hasSubObject = (products) => {
for (var key in products) {
if (!products.hasOwnProperty(key)) continue;
if (typeof products[key] === "object") return true;
}
return false;
}
let fieldsValues = []; let fieldsValues = [];
let fieldsNames = Object.keys(row); const fieldsNames = Object.keys(row);
for (let j = 0; j < fieldsNames.length; j++) {
fieldsValues.push(row[fieldsNames[j]]); if (hasSubObject(row)) {
fieldsValues = row;
}
else {
for (let j = 0; j < fieldsNames.length; j++) {
fieldsValues.push(row[fieldsNames[j]]);
}
} }
if (storage) { if (storage) {
@ -2250,7 +2038,7 @@ export class SystemKeywords {
} }
} }
if (table.length === 1) { if (table.length === 0) {
GBLog.info(`BASIC: MERGE ran but updated zero rows.`); GBLog.info(`BASIC: MERGE ran but updated zero rows.`);
return null; return null;
} else { } else {