Monaca Debugger not handling persistent storage correctly - debugging

All,
I might have found a bug in Monaca Debugger.
I am using the following code to get a persistent or temporary storage:
window.requestFileSystem(__storageType, _size,
function(_fileSystem) { // Success
logger.info("initFilesystem: _fileSystem.root: " + _fileSystem.root.toURL());
_deferred.resolve(_fileSystem);
}, function(_error) { // Failure
_deferred.reject(_error);
});
Where the files are correctly managed on both Android and iOS.
However, the path is not correct on Android and persistent storage, where I assume that it is a bug in how Monaca Debugger gives paths to the cordova plugin (note that I have not tried yet to deploy to android and verify without the debugger).
See below for the paths I get for Android and iOS, where I only get root directory on Android for persistent (note that on all examples the files are managed correctly, it is only the path for Android and persistent that is not correct).
Is this a bug, or a feature I need to manage in code?
Persistent file paths:
iOS: _fileSystem.root: file:///var/mobile/Containers/Data/Application/43F2E2CE-5F06-43F0-AE1C-4DD35EFF723A/Documents/
Android: _fileSystem.root: file:///storage/emulated/0/
Temporary file paths:
iOS: _fileSystem.root: file:///var/mobile/Containers/Data/Application/43F2E2CE-5F06-43F0-AE1C-4DD35EFF723A/tmp/
Android: _fileSystem.root: file:///storage/emulated/0/Android/data/mobi.monaca.debugger/cache/
Versions:
iOS version: 8.1.2
iOS Monaca Debugger version: 3.1.1
Android version: 4.2.2
Android Monaca Debugger version: 3.1.1
Update 2015-02-04:
The file object used (simplified code):
function FileIO() {
if (false === (this instanceof FileIO)) { return new FileIO(); } // Enforce that every time a constructor function is called, this function is invoked properly using the new operator.
/** #public #method initFilesystem.
* #desc Initialize filesystem
* #param {boolean} Persistent = true, Temporary = false. Must be boolean. Defaults to true.
* #param {size} Size in bytes requested. Must be number. Defaults to 0 bytes.
* #return Promise (see examples above). */
FileIO.prototype.initFilesystem = function(_persistent, _size) {
/** #private */ var _deferred = new jQuery.Deferred(); // Used for object Promise.
/** #private */ var _err = { code: null };
if (__status != STATUS_INITIALIZED) { // Only perform if not already initialized.
if (typeof _persistent !== 'boolean') { _persistent = true; } // Value must be boolean. Default to true if not set correctly.
if (typeof _size !== 'number') { _size = 0; } // Value must be number. Default to 0 bytes if not set correctly.
(_persistent == true) ? __storageType = window.PERSISTENT : __storageType = window.TEMPORARY;
__status = STATUS_INITIALIZING;
try {
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
window.storageInfo = window.storageInfo || window.webkitStorageInfo;
window.requestFileSystem(__storageType, _size,
function(_fileSystem) { // Success
__fileSystem = _fileSystem;
__status = STATUS_INITIALIZED;
_deferred.resolve(_fileSystem);
}, function(_error) { // Failure
__status = STATUS_NO_FILESYSTEM;
_deferred.reject(_error);
});
} catch (_error) {
__status = STATUS_NO_FILESYSTEM;
_deferred.reject(_error);
}
} else {
_err.code = FILESYSTEM_ALREADY_INITIALIZED;
_deferred.reject(_err);
}
return _deferred.promise();
};
/** #public #method createFile.
* #desc Create file.
* #param {string} (path/)filename. Must be string. Defaults to "file.txt".
* #return Promise (see examples above). */
FileIO.prototype.createFile = function(_fileName) {
/** #private */ var _err = { code: null };
/** #private */ var _deferred = new jQuery.Deferred(); // Used for object Promise.
if (typeof _fileName !== 'string') { _fileName = "file.txt"; } // Default to "file.txt" if not correct.
if (__status != STATUS_NO_FILESYSTEM) { // Object must be initialized.
try {
__fileSystem.root.getFile(_fileName, { create: true, exclusive: true },
function(_fileEntry) { // Success
_deferred.resolve(_fileEntry);
}, function(_error) { // Failure
_deferred.reject(_error);
});
} catch (_error) {
_deferred.reject(_error);
}
} else {
_err.code = FILESYSTEM_NOT_INITIALIZED;
_deferred.reject(_err);
}
return _deferred.promise();
};
/** #public #constructor init.
* #desc Constructor for the class.
* #param N/A.
* #return N/A. */
FileIO.prototype.init = function() {
};
// Construct
this.init();
}
Initializion:
var appFileSystem = new FileIO();
appFileSystem.initFilesystem(true); // Persistent.
appFileSystem.createFile("testfile.txt").done(function(fileEntry) { console.log("createFile: done. fileEntry: "+JSON.stringify(fileEntry)); }).fail(function(error) { console.log("createFile: fail. error.code: "+error.code); });
With the above, the FileIO object is created, filesystem initialized and file created, where the persistent storage is placed in root (tested and verified for Cordova 4 according to latest Monaca update), while non-persistent is placed in right directory.

Related

Exception: argument too large: value Google Script

I'm trying to scrape a website & put the value in cache so I don't hit the daily limit of UrlFetchApp
Here is the script I did:
/**
* Scrape URL, return whatever you choose with jquery-style selectors.
Dependency: cheeriogs, see https://github.com/fgborges/cheeriogs
*
* #param {url} valid start-url
* #return result (array values)
*
* #customfunction
*/
function scrapercache(url) {
var result = [];
var description;
var options = {
'muteHttpExceptions': true,
'followRedirects': false,
};
var cache = CacheService.getScriptCache();
var properties = PropertiesService.getScriptProperties();
try {
let res = cache.get(url);
if (!res) {
// trim url to prevent (rare) errors
url.toString().trim();
var r = UrlFetchApp.fetch(url, options);
var c = r.getResponseCode();
// check for meta refresh if 200 ok
if (c == 200) {
var html = r.getContentText();
cache.put(url, "cached", 21600);
properties.setProperty(url, html);
var $ = Cheerio.load(html); // make sure this lib is added to your project!
// meta description
if ($('meta[name=description]').attr("content")) {
description = $('meta[name=description]').attr("content").trim();
}
}
result.push([description]);
}
}
catch (error) {
result.push(error.toString());
}
finally {
return result;
}
}
but when I call the function like that:
=scrapercache("https://www.gurufocus.com/term/total_freecashflow/nyse:ABBV/Free-Cash-Flow")
I get the error message:
Exception: argument too large: value
Anyone can help me please?
Thank you :)
Gabriel
As written in the official documentation,
The maximum length of a key is 250 characters. The maximum amount of data that can be stored per key is 100KB.
If the size of the data put in cache exceeds any of the above limitations, the error
Exception: argument too large
is shown. In your case, value exceeds 100KB. Solution would be to cache only necessary data or don't cache at all depending on your specific needs.

AWS Lambda logging through Serilog UDP sink and logstash silently fails

We have a .NET Core 2.1 AWS Lambda that I'm trying to hook into our existing logging system.
I'm trying to log through Serilog using a UDP sink to our logstash instance for ingestion into our ElasticSearch logging database that is hosted on a private VPC. Running locally through a console logs fine, both to the console itself and through UDP into Elastic. However, when it runs as a lambda, it only logs to the console (i.e CloudWatch), and doesn't output anything indicating that anything is wrong. Possibly because UDP is stateless?
NuGet packages and versions:
Serilog 2.7.1
Serilog.Sinks.Udp 5.0.1
Here is the logging code we're using:
public static void Configure(string udpHost, int udpPort, string environment)
{
var udpFormatter = new JsonFormatter(renderMessage: true);
var loggerConfig = new LoggerConfiguration()
.Enrich.FromLogContext()
.MinimumLevel.Information()
.Enrich.WithProperty("applicationName", Assembly.GetExecutingAssembly().GetName().Name)
.Enrich.WithProperty("applicationVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString())
.Enrich.WithProperty("tags", environment);
loggerConfig
.WriteTo.Console(outputTemplate: "[{Level:u}]: {Message}{N---ewLine}{Exception}")
.WriteTo.Udp(udpHost, udpPort, udpFormatter);
var logger = loggerConfig.CreateLogger();
Serilog.Log.Logger = logger;
Serilog.Debugging.SelfLog.Enable(Console.Error);
}
// this is output in the console from the lambda, but doesn't appear in the Database from the lambda
// when run locally, appears in both
Serilog.Log.Logger.Information("Hello from Serilog!");
...
// at end of lambda
Serilog.Log.CloseAndFlush();
And here is our UDP input on logstash:
udp {
port => 5000
tags => [ 'systest', 'serilog-nested' ]
codec => json
}
Does anyone know how I might go about resolving this? Or even just seeing what specifically is wrong so that I can start to find a solution.
Things tried so far include:
Pinging logstash from the lambda - impossible, lambda doesn't have ICMP
Various things to try and get the UDP sink to output errors, as seen above, various attempts at that. Even putting in a completely fake address yields no error though
Adding the lambda to a VPC where I know logging is possible from
Sleeping around at the end of the lambda. SO that the logs have time to go through before the lambda exits
Checking the logstash logs to see if anything looks odd. It doesn't really. And the fact that local runs get through fine makes me think it's not that.
Using UDP directly. It doesn't seem to reach the server. I'm not sure if that's connectivity issues or just UDP itself from a lambda.
Lots of cursing and swearing
In line with my comment above you can create a log subscription and stream to ES like so, I'm aware that this is NodeJS so it's not quite the right answer but you might be able to figure it out from here:
/* eslint-disable */
// Eslint disabled as this is adapted AWS code.
const zlib = require('zlib')
const { Client } = require('#elastic/elasticsearch')
const elasticsearch = new Client({ ES_CLUSTER_DETAILS })
/**
* This is an example function to stream CloudWatch logs to ElasticSearch.
* #param event
* #param context
* #param callback
*/
export default (event, context, callback) => {
context.callbackWaitsForEmptyEventLoop = true
const payload = new Buffer(event.awslogs.data, 'base64')
zlib.gunzip(payload, (err, result) => {
if (err) {
return callback(null, err)
}
const logObject = JSON.parse(result.toString('utf8'))
const elasticsearchBulkData = transform(logObject)
const params = { body: [] }
params.body.push(elasticsearchBulkData)
esClient.bulk(params, (err, resp) => {
if (err) {
callback(null, 'success')
return
}
})
callback(null, 'success')
})
}
function transform(payload) {
if (payload.messageType === 'CONTROL_MESSAGE') {
return null
}
let bulkRequestBody = ''
payload.logEvents.forEach((logEvent) => {
const timestamp = new Date(1 * logEvent.timestamp)
// index name format: cwl-YYYY.MM.DD
const indexName = [
`cwl-${process.env.NODE_ENV}-${timestamp.getUTCFullYear()}`, // year
(`0${timestamp.getUTCMonth() + 1}`).slice(-2), // month
(`0${timestamp.getUTCDate()}`).slice(-2), // day
].join('.')
const source = buildSource(logEvent.message, logEvent.extractedFields)
source['#id'] = logEvent.id
source['#timestamp'] = new Date(1 * logEvent.timestamp).toISOString()
source['#message'] = logEvent.message
source['#owner'] = payload.owner
source['#log_group'] = payload.logGroup
source['#log_stream'] = payload.logStream
const action = { index: {} }
action.index._index = indexName
action.index._type = 'lambdaLogs'
action.index._id = logEvent.id
bulkRequestBody += `${[
JSON.stringify(action),
JSON.stringify(source),
].join('\n')}\n`
})
return bulkRequestBody
}
function buildSource(message, extractedFields) {
if (extractedFields) {
const source = {}
for (const key in extractedFields) {
if (extractedFields.hasOwnProperty(key) && extractedFields[key]) {
const value = extractedFields[key]
if (isNumeric(value)) {
source[key] = 1 * value
continue
}
const jsonSubString = extractJson(value)
if (jsonSubString !== null) {
source[`$${key}`] = JSON.parse(jsonSubString)
}
source[key] = value
}
}
return source
}
const jsonSubString = extractJson(message)
if (jsonSubString !== null) {
return JSON.parse(jsonSubString)
}
return {}
}
function extractJson(message) {
const jsonStart = message.indexOf('{')
if (jsonStart < 0) return null
const jsonSubString = message.substring(jsonStart)
return isValidJson(jsonSubString) ? jsonSubString : null
}
function isValidJson(message) {
try {
JSON.parse(message)
} catch (e) { return false }
return true
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n)
}
One of my colleagues helped me get most of the way there, and then I managed to figure out the last bit.
I updated Serilog.Sinks.Udp to 6.0.0
I updated the UDP setup code to use the AddressFamily.InterNetwork specifier, which I don't believe was available in 5.0.1.
I removed enriching our log messages with "tags", since I believe it being present on the UDP endpoint somehow caused some kind of clash and I've seen it stop logging without a trace before.
And voila!
Here's the new logging setup code:
loggerConfig
.WriteTo.Udp(udpHost, udpPort, AddressFamily.InterNetwork, udpFormatter)
.WriteTo.Console(outputTemplate: "[{Level:u}]: {Message}{NewLine}{Exception}");

BotBuilder type error :Cannot read property 'listen' of undefined

I'm following the official quick start of Microsoft Bot Builder(SDK v3) for NodeJs: Create a bot with the Bot Builder SDK for Node.js
1- I made a new project with
npm init
2- then
npm install --save botbuilder#3.13.1
3- then I created a new file "app.js"
var builder = require('botbuilder');
var connector = new builder.ConsoleConnector().listen();
var bot = new builder.UniversalBot(connector, function (session) {
session.send("You said: %s", session.message.text);
});
But when I run node app.js the following error is thrown:
var connector=builder.ConsoleConnector().listen(); TypeError:
Cannot read property 'listen' of undefined
You haven't assigned a storage option to your bot. The simplest option (for development only) is to use in memory storage. You're code should look like this:
var builder = require('botbuilder');
// Bot Storage: Here we register the state storage for your bot.
// Default store: volatile in-memory store - Only for prototyping!
var inMemoryStorage = new builder.MemoryBotStorage();
var connector = new builder.ConsoleConnector().listen();
var bot = new builder.UniversalBot(connector, function(session) {
session.send("You said: %s", session.message.text);
}).set('storage', inMemoryStorage); // Register in memory storage
That being said, please be aware that the v3 SDK is going to be DEPRECATED in the near future. It is advised that you start your development using the v4 Node SDK, instead. To get started, you can reference the docs here and review sample code here.
In short, in v4, you will utilize three files: index.js, bot.js, and consoleAdapter.js.
The index.js file essentially builds the server, api's, etc.
const path = require('path');
const {
ConsoleAdapter
} = require('./consoleAdapter');
// load environment variables from .env file.
const ENV_FILE = path.join(__dirname, '.env');
require('dotenv').config({
path: ENV_FILE
});
// Create the bot adapter, which is responsible for sending and receiving messages.
// We are using the ConsoleAdapter, which enables a bot you can chat with from within your terminal window.
const adapter = new ConsoleAdapter();
// Import our bot class.
const {
EchoBot
} = require('./bot');
const bot = new EchoBot();
// A call to adapter.listen tells the adapter to start listening for incoming messages and events, known as "activities."
// Activities are received as TurnContext objects by the handler function.
adapter.listen(async(context) => {
bot.onTurn(context);
});
// Emit a startup message with some instructions.
console.log('> Console EchoBot is online. I will repeat any message you send me!');
console.log('> Say "quit" to end.');
console.log(''); // Leave a blank line after instructions.
The bot.js file, generally, handles your bot's on[ActivityType] actions (e.g. onMessage()). In more complex bots, dialogs are extrapolated into their own files.
class EchoBot {
async onTurn(context) {
// Check to see if this activity is an incoming message.
// (It could theoretically be another type of activity.)
if(context.activity.type === 'message' && context.activity.text) {
// Check to see if the user sent a simple "quit" message.
if(context.activity.text.toLowerCase() === 'quit') {
// Send a reply.
context.sendActivity(`Bye!`);
process.exit();
} else {
// Echo the message text back to the user.
return context.sendActivity(`I heard you say "${ context.activity.text }"`);
}
}
}
}
module.exports.EchoBot = EchoBot;
Lastly, the consoleAdapter.js file is tasked with capturing the console activity and translating that to the bot.
'use strict';
var __importStar = (this && this.__importStar) || function(mod) {
if(mod && mod.__esModule) return mod;
var result = {};
if(mod != null)
for(var k in mod)
if(Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result['default'] = mod;
return result;
};
Object.defineProperty(exports, '__esModule', {
value: true
});
const botbuilderCore = require('botbuilder-core');
const readline = __importStar(require('readline'));
const console = require('console');
/**
* Lets a user communicate with a bot from a console window.
*
*/
class ConsoleAdapter extends botbuilderCore.BotAdapter {
/**
* Creates a new ConsoleAdapter instance.
* #param reference (Optional) reference used to customize the address information of activities sent from the adapter.
*/
constructor(reference) {
super();
this.nextId = 0;
this.reference = Object.assign({
channelId: 'console',
user: {
id: 'user',
name: 'User1'
},
bot: {
id: 'bot',
name: 'Bot'
},
conversation: {
id: 'convo1',
name: '',
isGroup: false
},
serviceUrl: ''
}, reference);
}
/**
* Begins listening to console input. A function will be returned that can be used to stop the
* bot listening and therefore end the process.
*
* #param logic Function which will be called each time a message is input by the user.
*/
listen(logic) {
const rl = this.createInterface({
input: process.stdin,
output: process.stdout,
terminal: false
});
rl.on('line', (line) => {
// Initialize activity
const activity = botbuilderCore.TurnContext.applyConversationReference({
type: botbuilderCore.ActivityTypes.Message,
id: (this.nextId++).toString(),
timestamp: new Date(),
text: line
}, this.reference, true);
// Create context and run middleware pipe
const context = new botbuilderCore.TurnContext(this, activity);
this.runMiddleware(context, logic)
.catch((err) => {
this.printError(err.toString());
});
});
return() => {
rl.close();
};
}
/**
* Lets a bot proactively message the user.
*
* #param reference A `ConversationReference` saved during a previous message from a user. This can be calculated for any incoming activity using `TurnContext.getConversationReference(context.activity)`.
* #param logic A function handler that will be called to perform the bots logic after the the adapters middleware has been run.
*/
continueConversation(reference, logic) {
// Create context and run middleware pipe
const activity = botbuilderCore.TurnContext.applyConversationReference({}, reference, true);
const context = new botbuilderCore.TurnContext(this, activity);
return this.runMiddleware(context, logic)
.catch((err) => {
this.printError(err.toString());
});
}
/**
* Logs a set of activities to the console.
*
* #param context Context for the current turn of conversation with the user.
* #param activities List of activities to send.
*/
sendActivities(context, activities) {
const that = this;
// tslint:disable-next-line:promise-must-complete
return new Promise((resolve, reject) => {
const responses = [];
function next(i) {
if(i < activities.length) {
responses.push({});
const a = activities[i];
switch(a.type) {
case 'delay':
setTimeout(() => next(i + 1), a.value);
break;
case botbuilderCore.ActivityTypes.Message:
if(a.attachments && a.attachments.length > 0) {
const append = a.attachments.length === 1 ?
`(1 attachment)` : `(${ a.attachments.length } attachments)`;
that.print(`${ a.text } ${ append }`);
} else {
that.print(a.text || '');
}
next(i + 1);
break;
default:
that.print(`[${ a.type }]`);
next(i + 1);
break;
}
} else {
resolve(responses);
}
}
next(0);
});
}
/**
* Not supported for the ConsoleAdapter. Calling this method or `TurnContext.updateActivity()`
* will result an error being returned.
*/
updateActivity(context, activity) {
return Promise.reject(new Error(`ConsoleAdapter.updateActivity(): not supported.`));
}
/**
* Not supported for the ConsoleAdapter. Calling this method or `TurnContext.deleteActivity()`
* will result an error being returned.
*/
deleteActivity(context, reference) {
return Promise.reject(new Error(`ConsoleAdapter.deleteActivity(): not supported.`));
}
/**
* Allows for mocking of the console interface in unit tests.
* #param options Console interface options.
*/
createInterface(options) {
return readline.createInterface(options);
}
/**
* Logs text to the console.
* #param line Text to print.
*/
print(line) {
console.log(line);
}
/**
* Logs an error to the console.
* #param line Error text to print.
*/
printError(line) {
console.error(line);
}
}
exports.ConsoleAdapter = ConsoleAdapter;
The above code is taken from the 01.console-echo sample of the Botbuilder-Samples repo. I removed some inline commentary. Please refer to the project for the complete code/files and associated remarks.
Hope of help!

How to create repeating form section in ServiceNow

I'm new to ServiceNow and going to start create a new form. One of the requirment is to create a repeating form section (below image is the design of the component).
May I know any default component in ServiceNow or we need to create a custom widget for this?
There is no OOB way to do this.
The way that I've solved the problem in the past, was as follows:
Create a single variable or set of variables representing the data you want to capture
Create a UI Macro/button "Add"
When clicked, that button should trigger a Script which will add the data from the fields into a JSON object which is then used to populate an HTML element with some friendly-looking representation of the data.
Here are some swatches of code I saved to do that, but keep in mind that I had to do this like a year ago so it's not super fresh.
"Add" button:
<?xml version="1.0" encoding="utf-8" ?>
<!--
UI Page
Name: http_addServerButton_loadBalancer
Category: General
Direct: false
Note that this will change slightly in name, and in the argument being passed, depending on whether the button is for the http, https, or TCP
sections.
-->
<j:jelly trim="false"
xmlns:j="jelly:core"
xmlns:g="glide"
xmlns:j2="null"
xmlns:g2="null" >
<script language="javascript"
src="addServerButtonClicked.jsdbx" />
<button name="add"
onclick="addServerButtonClicked('http')" >Add
</button >
</j:jelly >
UI Script that handled the add actions:
/*
UI Script
Name: addServerButtonClicked
Category: General
Direct: false
*/
/***********BEGIN***********/
// todo: Create onSubmit validation. Make sure users don't submit a form with existing server name/port data filled out but not added to the JSON.
// todo: Change add new button name to "add server".
/**
* This function is called when the "Add Server" button is clicked
*/
function addServerButtonClicked(protocol) {
g_form.hideFieldMsg('server_name_' + protocol, true);
g_form.hideFieldMsg('server_port_' + protocol, true);
//todo: validate the server AND port are filled out for the specific protocol selected, using "'server_name_' + protocol" and "'server_port_' + protocol"
//todo: If not BOTH populated, then add field mesage and tell the user that they are a bad user and should feel bad about their life choices.
var isFormValid = validateForm(protocol);
if (isFormValid) {
var fieldName = 'server_name_' + protocol;
g_form.getReference(fieldName, function(gr) {
//cheap way to combine relevant objects from differing scope without writing a gigantic anonymous inline function.
buildDataObject(gr, protocol);
});
} else {
alert('form invalid'); // todo: remove
// todo: throw some error or something
}
}
/**
* This function is called whenever a new server is added to the request. It parses the existing JSON data into an object, and then goes about adding on to it.
* #param serverGR {GlideRecord} - The GlideRecord object returned from the asynchronous nonHireATSCandidatesEncodedQuery that's run when add button is clicked. This param is auto-populated.
*/
function buildDataObject(serverGR, protocol) {
if (!serverGR) {
console.error('No valid server was found.');
}
// Grab the value of the JSON Data field
var existingJsonData = g_form.getValue('json_data');
// If the JSON Data field already contains some existing JSON data, use that as the starting
// point for our object. Otherwise (if this is the first entry), declare a new object.
var dataObject = existingJsonData ? JSON.parse(existingJsonData) : {
http: {},
https: {},
tcp: {}
};
//todo: write and call a function to get the protocol header info crap and append it to the object.
// Set the "serverName" property to either the server's name, or (if no name exists for this server),
// its' IP address of (if no name OR IP address exists for this server), its' sys_id.
var requestedFor = g_form.getValue('requested_for');
var serverSysId = serverGR.getValue('sys_id');
var serverIP = serverGR.getValue('ip_address');
var serverName = serverGR.getValue('name') ? serverGR.getValue('name') : serverGR.getValue('ip_address') ? serverGR.getValue('ip_address') : serverSysId = serverGR.getValue('sys_id');
var serverPort;
var tcpIPPort;
var lbMethod;
var persistence;
var monitorRequest;
var monitorResponse;
//Okay, yeah, so I could've used one line to set each of these vars, and used syntax like "'server_port_' + protocol.toLowerCase()".
//But instead of that, I did it this way. Why? Because I thought for a minute at 2AM that this would help future-me, in the event that
//we ever had stupid variable names to compete with. Sooo... behold, the pointless switch-case block.
switch (protocol.toLowerCase()) {
case 'http':
serverPort = g_form.getValue('server_port_http');
tcpIPPort = g_form.getValue('tcp_ip_http');
lbMethod = g_form.getValue('lb_method_http');
persistence = g_form.getValue('persistence_http');
monitorRequest = g_form.getValue('monitor_request_http');
monitorResponse = g_form.getValue('monitor_response_http');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_http', '');
g_form.setValue('server_name_http', '');
break;
case 'https':
serverPort = g_form.getValue('server_port_https');
tcpIPPort = g_form.getValue('tcp_ip_https');
lbMethod = g_form.getValue('lb_method_https');
persistence = g_form.getValue('persistence_https');
monitorRequest = g_form.getValue('monitor_request_https');
monitorResponse = g_form.getValue('monitor_response_https');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_https', '');
g_form.setValue('server_name_https', '');
break;
case 'tcp':
serverPort = g_form.getValue('server_port_tcp');
tcpIPPort = g_form.getValue('tcp_ip_tcp');
lbMethod = g_form.getValue('lb_method_tcp');
persistence = g_form.getValue('persistence_tcp');
monitorRequest = g_form.getValue('monitor_request_tcp');
monitorResponse = g_form.getValue('monitor_response_tcp');
// Clear the data from these two fields so it's clear that they need to be re-populated.
g_form.setValue('server_port_tcp', '');
g_form.setValue('server_name_tcp', '');
break;
}
if (!serverIP || !serverSysId || !serverPort || !serverName) {
// return; //Halt execution, since we don't have some of the data we need.
// todo: re-enable the above line after testing. Need to figure out how to handle errors, and what constitutes an error.
console.error('Not able to get one of these important values: [IP, Sys ID, Port, Server]')
}
// Populate the data object.
// Using bracket-notation here, in order to use a variable name as the object property name.
dataObject[protocol][serverSysId] = {};
dataObject[protocol][serverSysId].name = serverName;
dataObject[protocol][serverSysId].port = serverPort;
dataObject[protocol][serverSysId].sysid = serverSysId;
dataObject[protocol][serverSysId].ip = serverIP;
console.log(dataObject);
var dataSummary = '';
/*
This bit's pretty complex.
For each "prot" (protocol) in the outermost object,
check if the object corresponding to that protocol is truthy (not empty).
If it isn't empty, insert a header (H3) for that protocol/section.
Then, for each "prop" (server element) in that protocol object, print out some details about it.
*/
for (var prot in dataObject) {
if (!isObjEmpty(dataObject[prot]) && dataObject.hasOwnProperty(prot) && prot !== 'requestor' && prot !== 'generalInfo') {
dataSummary += '<h3>' + prot.toUpperCase() + '</h3>';
for (var prop in dataObject[prot]) {
if (dataObject[prot].hasOwnProperty(prop) && prop !== 'protocolDetails') {
dataSummary += '<b>Server name</b>: ' + dataObject[prot][prop].name + '<br />Server Sys ID: ' + dataObject[prot][prop].sysid + '<br />IP: ' + dataObject[prot][prop].ip + '<br />Port: ' + dataObject[prot][prop].port + '<br /><br />';
}
}
}
}
dataObject = populateObjectMeta(dataObject);
g_form.setValue('request_summary', dataSummary);
g_form.setValue('json_data', JSON.stringify(dataObject));
g_form.setVisible('request_summary', true);
}
function populateObjectMeta(dataObject) {
var i;
//Get boolean values for the three check-boxes representing whether the user wants HTTP, HTTPS, or TCP.
var httpSelected = isThisTrueOrWhat(g_form.getValue('http_select'));
var httpsSelected = isThisTrueOrWhat(g_form.getValue('https_select'));
var tcpSelected = isThisTrueOrWhat(g_form.getValue('tcp_select')); //todo: use these to populate more metadata in the relevant object
//Populate requestor details
dataObject.requestor = {};
dataObject.requestor.name = g_form.getReference('requested_by').getValue('name');
dataObject.requestor.sysID = g_form.getValue('requested_by');
dataObject.requestor.email = g_form.getValue('email');
//Populate general load balancing details
dataObject.generalInfo = {};
dataObject.generalInfo.dnsHost = g_form.getValue('dns_host');
dataObject.generalInfo.domainName = g_form.getValue('domain_name');
dataObject.generalInfo.application = isThisTrueOrWhat(g_form.getValue('application_not_found')) ? g_form.getValue('application_name_text') : g_form.getReference('application_name_ref').getValue('name');
dataObject.generalInfo.lifeCycle = g_form.getValue('lifecycle');
dataObject.generalInfo.site = g_form.getReference('site').getValue('name');
//Populate protocol details for HTTP, HTTPS, and TCP.
var selectedProtocols = getSelectedProtocols();
console.log(selectedProtocols);
for (i = 0; i < selectedProtocols.length; i++) {
console.log('Adding data to dataObj with selected protocol ' + [selectedProtocols[i]]);
dataObject[selectedProtocols[i]].protocolDetails = {};
dataObject[selectedProtocols[i]].protocolDetails.tcpIPPort = g_form.getValue('tcp_ip_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.lbMethod = g_form.getValue('lb_method_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.persistence = g_form.getValue('persistence_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.monitorRequest = g_form.getValue('monitor_request_' + selectedProtocols[i]);
dataObject[selectedProtocols[i]].protocolDetails.monitorResponse = g_form.getValue('monitor_response_' + selectedProtocols[i]);
}
return dataObject;
}
function getSelectedProtocols() {
var selectedProtocols = [];
var httpSelected = isThisTrueOrWhat(g_form.getValue('http_select'));
var httpsSelected = isThisTrueOrWhat(g_form.getValue('https_select'));
var tcpSelected = isThisTrueOrWhat(g_form.getValue('tcp_select'));
if (httpSelected) {
selectedProtocols.push('http');
}
if (httpsSelected) {
selectedProtocols.push('https');
}
if (tcpSelected) {
selectedProtocols.push('tcp');
}
return selectedProtocols;
}
function validateForm(protocol) {
var port;
switch (protocol.toLowerCase()) {
case 'http':
port = g_form.getValue('server_port_http');
break;
case 'https':
port = g_form.getValue('server_port_https');
break;
case 'tcp':
port = g_form.getValue('server_port_tcp');
break;
}
//todo: validate port, and a bunch of other stuff.
return true;
}
/**
* Adds one object to another, nesting the child object into the parent.
* this is to get around javascript's immutable handling of objects.
* #param name {String} - The name of the property of the parent object, in which to nest the child object. <br />For example, if the name parameter is set to "pickles" then "parent.pickles" will return the child object.
* #param child {Object} - The object that should be nested within the parent object.
* #param [parent={}] {Object} - The parent object in which to nest the child object. If the parent object is not specified, then the child object is simple nested into an otherwise empty object, which is then returned.
* #returns {Object} - A new object consisting of the parent (or an otherwise empty object) with the child object nested within it.
* #example
* //sets myNewObject to a copy of originalObject, which now also contains the original (yet un-linked) version of itself as a child, under the property name "original"
* var myNewObject = addObjToObj("original", originalObj, originalObj);
*/
function addObjToObj(name, child, parent) {
if (!parent) {
parent = {};
}
parent[name] = child;
return parent;
}
function isObjEmpty(o) {
for (var p in o) {
if (o.hasOwnProperty(p)) {
return false;
}
}
return true;
}
function isThisTrueOrWhat(b) {
return ((typeof b == 'string') ? (b.toLowerCase() == 'true') : (b == true)); //all this just to properly return a bool in JS. THERE'S GOT TO BE A BETTER WAY!
}
Just create a UI page with the functionality that you want to have.
Ex- clicking on new button opens up the repeated form view.
Get the inputs from the filled form as list of objects, send them over to the client-callable script include(GlideAjax) that handles creation.

Restrictions in file types in FineUploader 3.7.0

I am using the option "allowedExtensions" without any problem but there is a situation where I have to permit any type of extension but two.
Is there a simple way to do that? I didn't find an option like 'restrictedExtensions' to do that in the code.
Thanks
From the docs:
The validate and validateBatch events are thrown/called before the default Fine Uploader validators (defined in the options) execute.
Also, if your validation event handler returns false, then Fine Uploader will register that file as invalid and not submit it.
Here's some code you could try in your validate event handler. It has not been tested yet so YMMV.
var notAllowedExts = ['pptx', 'xlsx', 'docx'];
/* ... */
onValidate: function (fileOrBlobData) {
var valid = true;
var fileName = fileOrBlobData.name || '';
qq.each(notAllowedExts, function(idx, notAllowedExt) {
var extRegex = new RegExp('\\.' + notAllowedExt + "$", 'i');
if (fileName.match(extRegex) != null) {
valid = false;
return false;
}
});
return valid;
}
/* ... */

Resources