For simplicity, let's say a given web app is using some files that exist in the root directory like,
First set: image1.jpg info1.txt video1.mp4 and
Second set: image2.jpg info2.txt video2.mp4 as well as
Third set: image3.jpg info3.txt video3.mp4
In this case «due to priority reasons» we would like these to be cached set by set in order. That means, the caching of the second group of files will start only after the first set is «hopefully» done caching, then third will start after second is finished etc.
Once we are good to go with,
if ('caches' in window){
// Safe to try
}
we want to use
const myCache1 = await caches.open('my-cache-1');
const myCache2 = await caches.open('my-cache-2');
const myCache3 = await caches.open('my-cache-3');
to create the cache containers (like folders hidden inside the browser),
and then we can
const firstGroup = ['/image1.jpg', '/info1.txt', '/video1.mp4'];
const secondGroup = ['/image2.jpg', '/info2.txt', '/video2.mp4'];
const thirdGroup = ['/image3.jpg', '/info3.txt', '/video3.mp4'];
but now we want to chain myCache1.addAll(firstGroup); myCache2.addAll(secondGroup); myCache3.addAll(thirdGroup);
so that files get downloaded depending on their importance (or whatever logical reason to put things in order).
Note1: The .then().catch().finally() chaining is essential but async/await answers are also welcome.
Note2: Inclusion of error handling in case of flaky internet connection would be nice but not a must.
It seems like all you need to do is call the addAlls in order with async/await, like so:
async function loadFiles () {
const myCache1 = await caches.open('my-cache-1');
const myCache2 = await caches.open('my-cache-2');
const myCache3 = await caches.open('my-cache-3');
const firstGroup = ['/image1.jpg', '/info1.txt', '/video1.mp4'];
const secondGroup = ['/image2.jpg', '/info2.txt', '/video2.mp4'];
const thirdGroup = ['/image3.jpg', '/info3.txt', '/video3.mp4'];
try {
// load group 1 first
await myCache1.addAll(firstGroup);
// then load group 2
await myCache2.addAll(secondGroup);
// then load group 3
await myCache3.addAll(thirdGroup);
} catch(err) {
// error handling goes here
console.error(err);
}
}
loadFiles();
I am not able to locate MatSnackBar element using protractor.
This is how I display snack bar.
const snackBarRef = this.snackBar.open('Book added.', 'Undo', {
duration: 300000
});
This is e2e test.
const snackBar = element(by.tagName('simple-snack-bar'));
browser.wait(ExpectedConditions.visibilityOf(snackBar), 30000);
element(by.tagName('simple-snack-bar')).getText().then(function (val) {
console.log(val);
expect(val).toEqual('Book added');
});
Test code fails with this error.
Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Thanks for your message, #dplavcic.
The reason that my code didn't work is that snackbar was out of angular root. So I have to use browser.driver.findElement() instead of element(), and finally have solved the issue.
Something like:
public getSnackBar(): promise.Promise<string> {
const snackbar = browser.driver.wait(until.elementLocated(By.tagName('simple-snack-bar')), 10000);
return snackbar.getText();
}```
The time out is from the browser.wait of 30 second, so the application cannot find the elemnt with in that time . so increase the time out as :
add below property to your config file:
jasmineNodeOpts: {defaultTimeoutInterval: 40000}
About element not found
make sure elemnt is not inside iframe or shadown , if so , then switch to parent before accessing it
use promise chaining properly or use await instead
it('should find an element by text input model', async function() {
await browser.get('app/index.html#/form');
var username =element(by.model('username'));
await username.clear();
await username.sendKeys('Jane Doe');
var name = element(by.binding('username'));
expect(await name.getText()).toEqual('Jane Doe');
});
I am using TranscriptLoggerMiddleware and CosmosDB to log my chatbot transcripts. We are trying to capture the user state information (user name, account number, account type, etc) as top level attributes in the transcript so that specific customers can easily be queried in the DB (if that information is just in the individual timestamp attributes of the document, they can't be queried).
Ideally I would just add the user state when I'm building the file, but I can't figure any way to access it since the logger is defined in index.js and TranscriptLoggerMiddleware only provides the activity to my function, not the full context. If anyone has a way to get the user state data via TranscriptLoggerMiddleware, let me know, that would solve this issue. Here is the customLogger code. Note that due to the function receiving both the user query and bot response, I couldn't get retrieving and resaving the transcript to work, so I'm overwriting the transcript from a local log object. Not trying to come up with a new approach here but if one would solve the overall issue I'd like to hear it.
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
const { CosmosDbPartitionedStorage } = require('botbuilder-azure');
const path = require('path');
/**
* CustomLogger, takes in an activity and saves it for the duration of the conversation, writing to an emulator compatible transcript file in the transcriptsPath folder.
*/
class CustomLogger {
/**
* Log an activity to the log file.
* #param activity Activity being logged.
*/
// Set up Cosmos Storage
constructor(appInsightsClient) {
this.transcriptStorage = new CosmosDbPartitionedStorage({
cosmosDbEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
containerId: 'bot-transcripts'
});
this.conversationLogger = {};
this.appInsightsClient = appInsightsClient;
this.msDelay = 250;
}
async logActivity(activity) {
if (!activity) {
throw new Error('Activity is required.');
}
// Log only if this is type message
if (activity.type === 'message') {
if (activity.attachments) {
try {
var logTextDb = `${activity.from.name}: ${activity.attachments[0].content.text}`;
} catch (err) {
var logTextDb = `${activity.from.name}: ${activity.text}`;
}
} else {
var logTextDb = `${activity.from.name}: ${activity.text}`;
}
if (activity.conversation) {
var id = activity.conversation.id;
if (id.indexOf('|') !== -1) {
id = activity.conversation.id.replace(/\|.*/, '');
}
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var fileName = `${datestamp}_${id}`;
var timestamp = Math.floor(Date.now()/1);
// CosmosDB logging (JK)
if (!(fileName in this.conversationLogger)) {
this.conversationLogger[fileName] = {};
this.conversationLogger[fileName]['userData'] = {};
this.conversationLogger[fileName]['botName'] = process.env.BOTNAME;
}
this.conversationLogger[fileName][timestamp] = logTextDb;
let updateObj = {
[fileName]:{
...this.conversationLogger[fileName]
}
}
// Add delay to ensure messages logged sequentially
await this.wait(this.msDelay);
try {
let result = await this.transcriptStorage.write(updateObj);
} catch(err) {
console.log(err);
this.appInsightsClient.trackTrace({message: `Logger Error ${err.code} - ${path.basename(__filename)}`,severity: 3,properties: {'botName': process.env.BOTNAME, 'error':err.body}});
}
}
}
}
async wait(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds) {
break;
}
}
}
}
exports.CustomLogger = CustomLogger;
Not being able to get user state in this function, I decided to try a few other approaches. The most promising was creating a separate "updateTranscript" function to grab the transcript, add user state, and save it back. But I think it was catching it only on user request and getting overidden again by local object on bot response. I added a delay to try to combat this, but it still didn't work. On my very first prompt of providing customer number user state data is getting stored on transcript, but at the next activity it is gone and never comes back (even though I can see it is supposedly getting written to DB). Here is that update function.
const { CosmosDbStorage } = require('botbuilder-azure');
var updateTranscript = async (context, userData, appInsightsClient) => {
const transcriptStorage = new CosmosDbStorage({
serviceEndpoint: process.env.COSMOS_SERVICE_ENDPOINT,
authKey: process.env.COSMOS_AUTH_KEY,
databaseId: process.env.DATABASE,
collectionId: 'bot-transcripts',
partitionKey: process.env.BOTNAME
});
var id = context.activity.conversation.id;
if (id.indexOf('|') !== -1) {
id = context.activity.conversation.id.replace(/\|.*/, '');
}
// Get today's date for datestamp
var currentDate = new Date();
var day = currentDate.getDate();
var month = currentDate.getMonth()+1;
var year = currentDate.getFullYear();
var datestamp = year + '-' + month + '-' + day;
var filename = `${datestamp}_${id}`;
var msDelay = 500;
await new Promise(resolve => setTimeout(resolve, msDelay));
var transcript = await transcriptStorage.read([filename]);
transcript[filename]['userData'] = userData
try {
await transcriptStorage.write(transcript);
console.log('User data added to transcript');
} catch(err) {
console.log(err);
appInsightsClient.trackTrace({message: `Log Updater Error ${err.code} - ${path.basename(__filename)}`,severity: 3,properties: {'botName': process.env.BOTNAME, 'error':err.body}});
}
return;
}
module.exports.updateTranscript = updateTranscript
I realize this approach is a bit of a cluster but I've been unable to find anything better. I know the Microsoft COVID-19 bot has a really nice transcript retrieval function, but I haven't been able to get any input from them on how that was accomplished. That aside, I'm quite happy to continue with this implementation if someone can help me figure out how to get that user state into the transcript without being overwritten or running into concurrency issues.
As to why I can't query an account number even via substring() function, here's an example of the documents data object. I have no idea which string to check for a substring, in this case 122809. I don't know what that timestamp could be. If this is stored at the top level (e.g. userData/accountNumber) I know exactly where to look for the value. For further context, I've displayed what I see after the first prompt for account number, where userData is populated. But it gets overidden on subsequent writes and I can't seem to get it back even with a delay in my updateTranscript function.
"document": {
"userData": {},
"botName": "AveryCreek_OEM_CSC_Bot_QA",
"1594745997562": "AveryCreek_OEM_CSC_Bot_QA: Hi! I'm the OEM CSC Support Bot! Before we get started, can you please provide me with your 6-digit Vista number? If you don't have one, just type \"Skip\".",
"1594746003973": "You: 122809",
"1594746004241": "AveryCreek_OEM_CSC_Bot_QA: Thank you. What can I help you with today? \r\nYou can say **Menu** for a list of common commands, **Help** for chatbot tips, or choose one of the frequent actions below. \r\n \r\n I'm still being tested, so please use our [Feedback Form](https://forms.office.com/Pages/ResponsePage.aspx?id=lVxS1ga5GkO5Jum1G6Q8xHnUJxcBMMdAqVUeyOmrhgBUNFI3VEhMU1laV1YwMUdFTkhYVzcwWk9DMiQlQCN0PWcu) to let us know how well I'm doing and how I can be improved!",
"1594746011384": "You: what is my account number?",
"1594746011652": "AveryCreek_OEM_CSC_Bot_QA: Here is the informaiton I have stored: \n \n**Account Number:** 122809 \n\n I will forget everything except your account number after the end of this conversation.",
"1594746011920": "AveryCreek_OEM_CSC_Bot_QA: I can clear your information if you don't want me to store it or if you want to reneter it. Would you like me to clear your information now?",
"1594746016034": "You: no",
"1594746016301": "AveryCreek_OEM_CSC_Bot_QA: OK, I won't clear your information. You can ask again at any time."
},
"document": {
"userData": {
"accountNumber": "122809"
},
"botName": "AveryCreek_OEM_CSC_Bot_QA",
"1594746019952": "AveryCreek_OEM_CSC_Bot_QA: Hi! I'm the OEM CSC Support Bot! What can I help you with today? \r\nYou can say **Menu** for a list of common commands, **Help** for chatbot tips, or choose one of the frequent actions below. \r\n \r\n I'm still being tested, so please use our [Feedback Form](https://forms.office.com/Pages/ResponsePage.aspx?id=lVxS1ga5GkO5Jum1G6Q8xHnUJxcBMMdAqVUeyOmrhgBUNFI3VEhMU1laV1YwMUdFTkhYVzcwWk9DMiQlQCN0PWcu) to let us know how well I'm doing and how I can be improved!"
},
You had said you were encountering concurrency issues even though JavaScript is single-threaded. As strange as that sounds, I think you're right on some level. TranscriptLoggerMiddleware does have its own buffer that it uses to store activities throughout the turn and then it tries to log all of them all at once. It could easily have provided a way to get that whole buffer in your own logger function, but instead it just loops through the buffer so that you still only get to log them each individually. Also, it allows logActivity to return a promise but it never awaits it, so each activity will get logged "simultaneously" (it's not really simultaneous but the code will likely jump between function calls before waiting for them to complete). This is a problem for any operation that isn't atomic, because you'll be modifying state without knowing about its latest modifications.
while (transcript.length > 0) {
try {
const activity: Activity = transcript.shift();
// If the implementation of this.logger.logActivity() is asynchronous, we don't
// await it as to not block processing of activities.
// Because TranscriptLogger.logActivity() returns void or Promise<void>, we capture
// the result and see if it is a Promise.
const logActivityResult = this.logger.logActivity(activity);
// If this.logger.logActivity() returns a Promise, a catch is added in case there
// is no innate error handling in the method. This catch prevents
// UnhandledPromiseRejectionWarnings from being thrown and prints the error to the
// console.
if (logActivityResult instanceof Promise) {
logActivityResult.catch(err => {
this.transcriptLoggerErrorHandler(err);
});
}
} catch (err) {
this.transcriptLoggerErrorHandler(err);
}
}
All in all, I don't think transcript logger middleware is the way to go here. While it may purport to serve your purposes, there are just too many problems with it. I would either write my own middleware or just put the middleware code directly in my bot logic like this:
async onTurn(turnContext) {
const activity = turnContext.activity;
await this.logActivity(turnContext, activity);
turnContext.onSendActivities(async (ctx, activities, next) => {
for (const activity of activities) {
await this.logActivity(ctx, activity);
}
return await next();
});
// Bot code here
// Save state changes
await this.userState.saveChanges(turnContext);
}
async logActivity(turnContext, activity) {
var transcript = await this.transcriptProperty.get(turnContext, []);
transcript.push(activity);
await this.transcriptProperty.set(turnContext, transcript);
console.log('Activities saved: ' + transcript.length);
}
Since your transcript would be stored in your user state, that user state would also have the account number you need and hopefully you'd be able to query for it.
Kyle's answer did help me solve the issue, and I think that will be the most reusable piece for anyone experiencing similar issues. The key takeaway is that, if you're using nodejs, you should not be using TranscriptLoggerMiddleware and instead use Kyle's function in your onTurn handler (repeated here for reference):
// Function provided by Kyle Delaney
async onTurn(turnContext) {
const activity = turnContext.activity;
await this.logActivity(turnContext, activity);
turnContext.onSendActivities(async (ctx, activities, next) => {
for (const activity of activities) {
await this.logActivity(ctx, activity);
}
return await next();
});
// Bot code here
// Save state changes
await this.userState.saveChanges(turnContext);
}
You need to note, though, that his logActivity function is just storing the raw activities to the user state using a custom transcriptProperty. As of yet I haven't found a good method to give business/admin users access to this data in a way that is easily readable and searchable, nor construct some sort of file out output to send to a customer requesting a transcript of their conversation. As such, I continued using my CustomLogger instead. Here is how I accomplished that.
First, you must create the transcriptLogger in the constructor. If you create it inside your turn handler, you will lose the cache/buffer and it will only have the latest activity instead of the full history. May be common sense but this tripped me up briefly. I do this in the constructor via this.transcriptLogger = new CustomerLogger(appInsightsClient);. I also modified my logActivity function to accept the userData (my state object) as a second, optional parameter. I have successfully been able to use that userData object to add the required customer information to the bot transcript. To modify Kyle's function above you just need to replace this.logActivity with your function call, in my case this.transcriptLogger.logActivity(context, userData);.
While there are still some other issues with this approach, it does solve the title question of how to get user state data into the transcript.
I'm working on a discord bot, using discord.js.
I've been working on this command for too long without finding a solution, so I come here to ask for help.
Here is my problem, maybe it's really simple to solve to you, and that would be great :D
I want to create a command that sends a random gif, based on a keyword.
I'm using a node module called giphy-random.
(async () => {
const API_KEY = "hidden";
const gif = await giphyRandom(API_KEY, {
tag: "kawaii"
});
console.log(gif)
}
I would like to be able to get only the value 'url' of the const which is defined by the function (i'm maybe wrong in my words, I'm a beginner) in order to send it in a channel.
You simply want gif.data.url In fact if you change your console.log like this:
console.log(gif.data.url);
You'll see the url printed to the console.
According to the docs, the link is returned in data.url property of the resulting object. So your code should look like:
(async () => {
const API_KEY = "hidden";
const gif = await giphyRandom(API_KEY, {
tag: "kawaii"
});
console.log(gif.data.url)
}
You can simply access field like this:
const API_KEY = "hidden";
const gif = await giphyRandom(API_KEY, {
tag: "kawaii"
});
const {url} = gif.data; // equal to const url = gif.data.url
console.log(gif);
}
I use Testcafe to test a website which is using the jquery plugin Chosen and
I want to make an assertion in my test code depending on a value returned by an external helper function (getSelectedOption).
This function gets a Chosen Selector as a parameter and should return the selected value to the assertion, but the function always returns the first element of the list instead of the chosen one.
When I use the function code in my test, everything works fine.
It seems that the function doesn't have the actual state about the HTML data and can't see that an element is already selected.
This is a snippet from the test code:
await t
.click(await getOptionByText('salutation', 'Frau'))
.expect(await getSelectedOption('gender')).eql('weiblich')
This is a snippet from the external functions:
export const getChosenSelectorFromName = selectName => `#${selectName}_chosen`;
export const getSelectedOption = async selectName => {
const selectedOptionText = await
Selector(getChosenSelectorFromName(selectName))
.find('.chosen-single')
.innerText;
return selectedOptionText.toLowerCase().trim()
};
export const getOptionByText = async (selectName, optionText) => {
const chosenSelectorString = getChosenSelectorFromName(selectName);
await t.click(Selector(chosenSelectorString));
return await Selector(chosenSelectorString)
.find('.chosen-drop')
.find('li')
.withText(optionText);
};
When I use similar code like the getSelectedOption function inside my test, everything works fine:
const genderSelect = Selector('#gender_chosen);
.click(await getOptionByText('salutation', 'Frau'))
.expect(genderSelect.innerText).eql('WEIBLICH')
If you call await Selector(<some value>) then TestCafe immediately retries the data from the web page at the current moment.
You can tell TestCafe to retry data from web page until it becomes equal to the expected value.
To do it, you need to move the DOM manipulation function into ClientFunction:
import { Selector, ClientFunction } from "testcafe";
fixture `Fixture`
.page('https://harvesthq.github.io/chosen/');
const getChosenSelectorFromName = selectName => `#${selectName}_chosen`;
const getSelectedOption = ClientFunction(selector => {
var choosenDiv = document.querySelector(selector);
var singleValueEl = choosenDiv.querySelector('.chosen-single');
return singleValueEl.innerText;
});
test('test', async t => {
await t.expect(getSelectedOption('.chosen-container')).eql('Choose a Country...');
});