Cucumber Puppeteer: error trying to call step function with arguments - cucumber

Below is code where I try to include arguments in a Then statement (last line) when I am calling a step function
I get an error message stating invalid third argument.
So how do I pass parameters to the function?
async function confirmSheetCreated(folderName,sheetName) {
await this.page.waitFor(1500);
let sheetExists=await this.sheetExists(folderName,sheetName)
await this.page.isTrue(sheetExists);
}
When('I click plus button in top menu', { timeout: 5*1000 }, clickAddSheet);
When('I click Sheet option in dropdown', { timeout: 5*1000 }, clickSelectSheet);
When('I fill in New Sheet Lookup dialog', { timeout: 5*1000 },fillNewLookupSheetDialog);
When('I click Create New Sheet button',{ timeout: 5*1000 },clickCreateNewSheet);
Then( 'I confirm new sheet created',{ timeout: 5*1000 }, confirmSheetCreated('Revenue','Test Sheet 1'));

The key is that the parameters are created in the cucumber feature file by putting the values in quotes like this:
I confirm "Test Sheet 1" created in the "Revenue" folder
Then in the step you enter placeholders like this:
Then ('I confirm {string} created in the {string} folder,{timeout:5*1000},confirmSheetCreated);
and finally the function looks like this:
async function confirmSheetCreated(sheetName, folderName) {
await this.page.waitFor(1500);
let sheetExists=await this.sheetExists(folderName,sheetName)
await this.page.isTrue(sheetExists);
}
The first string becames the sheet name, and the second the Folder.

Related

Get active cell type in JupyterLab extension

I'm trying to write a simple extension with commands to navigate cells. The point of these commands is to automatically enter edit mode after movement unless target cell is of text or markdown type. Following this guide I've added command that executes sequence of some pre-built commands. But I'm stuck on creating condition for cell type, where can I get info about current cell?
import {
JupyterFrontEnd,
JupyterFrontEndPlugin,
} from '#jupyterlab/application';
/**
* Initialization data for the commands example.
*/
const extension: JupyterFrontEndPlugin<void> = {
id: 'commands',
autoStart: true,
activate: (app: JupyterFrontEnd) => {
const { commands } = app;
const command = 'my_command';
// Add a command
commands.addCommand(command, {
label: 'Move down & enter edit mode',
caption: 'Move down & enter edit mode',
execute: (args: any) => {
commands.execute('notebook:enter-command-mode');
commands.execute('notebook:move-cursor-down');
// here I need to check if we are on the code cell
commands.execute('notebook:enter-edit-mode');
},
});
},
};
export default extension;

Move data in Waterfall-Dialog. Bot Framework SDK

I'm using Bot Framework SDK with nodejs to implement a disamibuation flow.
I want that if two intents predicted by Luis are close to each other, ask the user from which of them are the one they want. I have done the validator but, I have a problem with the flow.
It is a waterfall Dialog with 3 steps:
FirstStep: Calls Orchestrator and Luis to get intents and entities. It pass the data with return await step.next({...})
Disamiguation Step: Checks if it is necessary to disambiguate, and, in that case, prompts the options. If not, it pass the data like the first step.
Answer step: If it has a disambiguation flag in the data it receives in step.result, it prompts the answer acordingly with the user response. Elsewhere, it uses the data in step.result that comes from the first step.
The problem is that, when it prompts user to say the intent, I lost the data of the FirstStep since I cannot use step.next({...})
¿How can I maintain both the data from the first step and the user answer in the prompt?
Here are the basic code:
async firstStep(step) {
logger.info(`FinalAnswer Dialog: firstStep`);
let model_dispatch = await this.bot.get_intent_dispatch(step.context);
let result = await this.bot.dispatchToTopIntentAsync(step.context, model_dispatch.model)
// model_dispatch = orchestrator_model
// result = {topIntent: String, entities: Array, disamibiguation: Array}
return await step.next({ model_dispatch: model_dispatch, result: result})
}
async disambiguationStep(step) {
logger.info(`FinalAnswer Dialog: disambiguationStep`);
if (step.result.result.disambiguation) {
logger.info("We need to disambiguate")
let disambiguation_options = step.result.result.disambiguation
const message_text = "What do you need";
const data = [
{
"title": "TEXT",
"value": disambiguation_option[0]
},
{
"title": "TEXT",
"value": disambiguation_option[1]
},
]
let buttons = data.map(function (d) {
return {
type: ActionTypes.PostBack,
title: d.title,
value: d.value
}
});
const msg = MessageFactory.suggestedActions(buttons, message_text);
return await step.prompt(TEXT_PROMPT, { prompt: msg });
return step.next(step.result) //not working
}
else {
logger.info("We dont desambiguate")
return step.next(step.result)
}
}
async answerStep(step) {
logger.info(`FinalAnswer Dialog: answerStep`);
let model_dispatch = step.result.model_dispatch
let result = step.result.result
//Show answer
return await step.endDialog();
}
You can use the step dictionary to store your values. The complex dialogs sample on GitHub is excellent for demonstrating this. https://github.com/microsoft/BotBuilder-Samples/blob/main/samples/javascript_nodejs/43.complex-dialog/dialogs/topLevelDialog.js
You can save data in the context with whatever name you want:
step.values['nameProperty'] = {}
This will be accessible within the entire execution context of the waterfall dialog:
const data = step.values['nameProperty'] // {}

How to copy a Google sheet and get data validation to stay intact?

I have a Google spreadsheet template with two sheets, Data Entry and Data Validation. The Data Validation sheet has a number of columns with valid values for matching columns on the Data Entry sheet. Everything works as expected. I need to copy these two sheets to a Sheet Under Test (SUT). I am using the sheets API to copy both sheets. I copy the Data Validation sheet first and then the Data Entry sheet. Here's the code and this appears to work.
const request = {
spreadsheetId :fromSpreadsheetId,
sheetId : fromSheetId,
resource:{
destinationSpreadsheetId: toSpreadsheetId,
},
}
const result = await _sheetService.spreadsheets.sheets.copyTo(request)
On my SUT, both sheets appear and the Data entry sheet has all the expected dropdowns and they all have the proper values. Seems perfect. The problem is when you select an item from any drop down in any column it selects and enters the proper value and then adds a red triangle and the message that an invalid value has been entered. If the column has the rejection setting then the value is removed and the error dialog appears.
The image shows two cells where I have already selected Video Course from the drop down.
If I go in and reselect column where I want validation, use Data→DataValidation… and just hit the Save button that column starts working, so it would appear everything came across correctly but the sheet doesn't think so. Is there any programmatic way to force the above process that I did manually? Is there something else I need to do in the sheets.copyTo method to make this work properly?
EDIT
This project is written in Node.js with a combination of TypeScript and JavaScript. The lower level code for talking to the Sheets API and copying the sheet between spreadsheets can be found in this github file. The method is copySheetFromTo and it's at the bottom of the file.
A sample source sheet with public view permissions
A sample destination sheet with public edit permissions
The integration test that used the above two files to copy the sheets is at the bottom of the file and has 'DEBUGGING TEST' at the beginning of the name (starts at line 209)
You want to copy the sheet including Data Validation.
When the copied sheet is used, an error occurs at the drop down menu of Data Validation.
You want to remove this error.
If my understanding is correct, how about this answer? In this answer, in order to remove the error, I overwrite the Data Validation of the copied sheet as a workaround. Please think of this as just one of several workarounds.
The flow for your situation is as follows.
Flow:
Retrieve all data validations from the sheet of Data Entry in the source Spreadsheet ("DataValidationTest") using the spreadsheet.get method.
Copy the sheets of Data Entry in the source Spreadsheet ("DataValidationTest") to the destination Spreadsheet ("Public Destination Sheet").
After the sheets of Data Entry was copied, rename the sheet name from Copy of Data Entry to Data Entry.
Then, overwrite the retrieved data validations to the sheet of Data Entry using the spreadsheet.batchUpdate method.
In this case, the structure of data validations retrieved by the spreadsheet.get method is almost the same with the structure for the spreadsheet.batchUpdate method. This workaround used this.
Copy the sheets of Data Validation in the source Spreadsheet ("DataValidationTest") to the destination Spreadsheet ("Public Destination Sheet").
Rename the sheet name of Copy of Data Validation to Data Validation.
Sample script:
When you test this script, please set the variables. And I think that sheet of sheet.spreadsheets.get(), sheet.spreadsheets.batchUpdate() and sheet.spreadsheets.sheets.copyTo() is the same with sheetOps of your script.
const srcSpreadsheet = "###"; // Please set this.
const tempDestSheetId = "###"; // Please set this.
const srcDataEntrySheetId = 0; // Please set this.
const srcDataValidationSheetId = 123456789; // Please set this.
let dataValidation = await sheet.spreadsheets.get({
spreadsheetId: srcSpreadsheet,
ranges: ["Data Entry"],
fields: "sheets/data/rowData/values/dataValidation"
});
let data = dataValidation.data.sheets[0].data;
let rows = [];
for (let i = 0; i < data.length; i++) {
if (data[i].rowData) {
rows = data[i].rowData;
break;
}
}
sheet.spreadsheets.sheets.copyTo(
{
spreadsheetId: srcSpreadsheet,
sheetId: srcDataEntrySheetId,
resource: { destinationSpreadsheetId: tempDestSheetId }
},
(err, res) => {
sheet.spreadsheets.batchUpdate(
{
spreadsheetId: tempDestSheetId,
resource: {
requests: [
{
updateSheetProperties: {
fields: "title,sheetId",
properties: { sheetId: res.data.sheetId, title: "Data Entry" }
}
},
{
updateCells: {
rows: rows,
range: { sheetId: res.data.sheetId },
fields: "dataValidation"
}
}
]
}
},
(er, re) => {
if (err) {
console.error(er);
return;
}
console.log(re.data);
}
);
}
);
let result1 = await sheet.spreadsheets.sheets.copyTo({
spreadsheetId: srcSpreadsheet,
sheetId: srcDataValidationSheetId,
resource: { destinationSpreadsheetId: tempDestSheetId }
});
let result2 = await sheet.spreadsheets.batchUpdate({
spreadsheetId: tempDestSheetId,
resource: {
requests: [
{
updateSheetProperties: {
fields: "title,sheetId",
properties: {
sheetId: result1.data.sheetId,
title: "Data Validation"
}
}
}
]
}
});
console.log(result2.data);
Note:
This script supposes that Sheets API has already been able to be used.
This is the simple modified script for showing the flow of workaround. So please modify this for your situation.
As an important point, in order to overwrite the data validations to the copied sheet of Data Entry, it is required to execute it after the copy of the sheet of Data Entry was completely finished. So I modified the script like above.
References:
Method: spreadsheets.batchUpdate
Method: spreadsheets.get
Method: spreadsheets.sheets.copyTo

React Native: Reach-Navigation and Pouch-DB - db.put not done before "refresh" callback is run

Relative newbie; forgive me if my etiquette and form here aren't great. I'm open to feedback.
I have used create-react-native-app to create an application using PouchDB (which I believe ultimately uses AsyncStorage) to store a list of "items" (basically).
Within a TabNavigator (main app) I have a StackNavigator ("List screen") for the relevant portion of the app. It looks to the DB and queries for the items and then I .map() over each returned record to generate custom ListView-like components dynamically. If there are no records, it alternately displays a prompt telling the user so. In either case, there is an "Add Item" TouchableOpacity that takes them to a screen where they an add a new item (for which they are taken to an "Add" screen).
When navigating back from the "Add" screen I'm using a pattern discussed quite a bit here on SO in which I've passed a "refresh" function as a navigation param. Once the user uses a button on the "Add" screen to "save" the changes, it then does a db.post() and adds them item, runs the "refresh" function on the "List screen" and then navigates back like so:
<TouchableOpacity
style={styles.myButton}
onPress={() => {
if (this.state.itemBrand == '') {
Alert.alert(
'Missing Information',
'Please be sure to select a Brand',
[
{text: 'OK', onPress: () =>
console.log('OK pressed on AddItemScreen')},
],
{ cancelable: false }
)
} else {
this.createItem();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsScreen');
}
}
}
>
And all of this works fine. The "refresh" function (passed as onGoBack param) works fine... for this screen. The database is called with the query, the new entry is found and the components for the item renders up like a charm.
Each of the rendered ListItem-like components on the "List screen" contains a react-native-slideout with an "Edit" option. An onPress for these will send the user to an "Item Details" screen, and the selected item's _id from PouchDB is passed as a prop to the "Item Details" screen where loadItem() runs in componentDidMount and does a db.get(id) in the database module. Additional details are shown from a list of "events" property for that _id (which are objects, in an array) which render out into another bunch of ListItem-like components.
The problem arises when either choose to "Add" an event to the list for the item... or Delete it (using another function via [another] slideout for these items. There is a similar backward navigation, called in the same form as above after either of the two functions is called from the "Add Event" screen, this being the "Add" example:
async createEvent() {
var eventData = {
eventName: this.state.eventName.trim(),
eventSponsor: this.state.eventSponsor.trim(),
eventDate: this.state.eventDate,
eventJudge: this.state.eventJudge.trim(),
eventStandings: this.state.eventStandings.trim(),
eventPointsEarned: parseInt(this.state.eventPointsEarned.trim()),
};
var key = this.key;
var rev = this.rev;
await db.createEvent(key, rev, eventData);
}
which calls my "db_ops" module function:
exports.createEvent = function (id, rev, eventData) {
console.log('You called db.createEvent()');
db.get(id)
.then(function(doc) {
var arrWork = doc.events; //assign array of events to working variable
console.log('arrWork is first assigned: ' + arrWork);
arrWork.push(eventData);
console.log('then, arrWork was pushed and became: ' + arrWork);
var arrEvents = arrWork.sort((a,b)=>{
var dateA = new Date(a.eventDate), dateB = new Date(b.eventDate);
return b.eventDate - a.eventDate;
})
doc.events = arrEvents;
return db.put(doc);
})
.then((response) => {
console.log("db.createEvent() response was:\n" +
JSON.stringify(response));
})
.catch(function(err){
console.log("Error in db.createEvent():\n" + err);
});
}
After which the "Add Event" screen's button fires the above in similar sequence to the first, just before navigating back:
this.createEvent();
this.props.navigation.state.params.onGoBack();
this.props.navigation.navigate('ItemsDetails');
The "refresh" function looks like so (also called in componentDidMount):
loadItem() {
console.log('Someone called loadItem() with this.itemID of ' + this.itemID);
var id = this.itemID;
let totalWon = 0;
db.loadItem(id)
.then((item) => {
console.log('[LOAD ITEM] got back data of:\n' + JSON.stringify(item));
this.setState({objItem: item, events: item.events});
if (this.state.events.length != 0) { this.setState({itemLoaded: true});
this.state.events.map(function(event) {
totalWon += parseInt(event.eventPointsEarned);
console.log('totalWon is ' + totalWon + ' with ' +
event.eventPointsEarned + ' having been added.');
});
};
this.setState({totalWon: totalWon});
})
.catch((err) => {
console.log('db.loadItem() error: ' + err);
this.setState({itemLoaded: false});
});
}
I'm at a loss for why the List Screen refreshes when I add an item... but not when I'm doing other async db operations with PouchDB in what I think is similar fashion to modify the object containing the "event" information and then heading back to the Item Details screen.
Am I screwing up with Promise chain someplace? Neglecting behavior of the StackNavigator when navigating deeper?
The only other difference being that I'm manipulating the array in the db function in the non-working case, whereas the others I'm merely creating/posting or deleting/removing the record, etc. before going back to update state on the prior screen.
Edit to add, as per comments, going back to "List screen" and the opening "Item Details" does pull the database data and correctly shows that the update was made.
Further checking I've done also revealed that the console.log in createEvent() to print the response to the db call isn't logging until after some of the other dynamic rendering methods are getting called on the "Item Details" screen. So it seems as though the prior screen is doing the get() that loadItem() calls before the Promise chain in createEvent() is resolving. Whether the larger issue is due to state management is still unclear -- though it would make sense in some respects -- to me as this could be happening regardless of whether I've called my onGoBack() function.
Edit/bump: I’ve tried to put async/await to use in various places in both the db_ops module on the db.get() and the component-side loadItem() which calls it. There’s something in the timing of these that just doesn’t jive and I am just totally stuck here. Aside from trying out redux (which I think is overkill in this particular case), any ideas?
There is nothing to do with PDB or navigation, it's about how you manage outer changes in your depending (already mounted in Navigator since they are in history - it's important to understand - so componentDidMount isn't enough) components. If you don't use global state redux-alike management (as I do) the only way to let know depending component that it should update is passing corresponding props and checking if they were changed.
Like so:
//root.js
refreshEvents = ()=> { //pass it to DeleteView via screenProps
this.setState({time2refreshEvents: +new Date()}) //pass time2refreshEvents to EventList via screenProps
}
//DeleteView.js
//delete button...
onPress={db.deleteThing(thingID).then(()=> this.props.screenProps.refreshEvents())}
//EventList.js
...
constructor(props) {
super(props);
this.state = {
events: [],
noEvents: false,
ready: false,
time2refreshEvents: this.props.screenProps.time2refreshEvents,
}
}
static getDerivedStateFromProps(nextProps, currentState) {
if (nextProps.screenProps.time2refreshEvents !== currentState.time2refreshEvents ) {
return {time2refreshEvents : nextProps.screenProps.time2refreshEvents }
} else {
return null
}
}
componentDidMount() {
this._getEvents()
}
componentDidUpdate(prevProps, prevState) {
if (this.state.time2refreshEvents !== prevState.time2refreshEvents) {
this._getEvents()
}
}
_getEvents = ()=> {
//do stuff querying db and updating your list with actual data
}

Botframework Prompt dialogs until user finishes

I'm creating a chat bot for slack using Microsoft's botbuilder and LUIS.
Is there a way to keep using builder.Prompts.text() to keep asking the user if there are anymore information the user wants to put, like a for or while loop? For example I want to keep on asking the user an undefined number of times if there's a key the user wants to save and only stop when the user types done and then I will have an equal number of builder.Prompts.text() to ask the user for the values to put in each of those keys.
function (session, results, next) {
builder.Prompts.text(session, "Another key to put?");
},
function (session, results, next) {
builder.Prompts.text(session, "Value to put?");
}
It doesn't seem like I can create some sort of loop with an array that saves each key with its value, I'm not sure how to approach this.
Thanks.
What you're looking for is session.replaceDialog(); there is an example labeled 'basics-loops' on the GitHub repo for the SDK. To loop through prompts, one has to create a small dialog with the desired prompts and have the dialog restart automatically via session.replaceDialog() or session.beginDialog().
I've built a chatbot that receives key-value pairs in the scenario you specified above. The code excerpt below is the final step in my 'Loop' dialog.
function (session, results) {
var value = results.response ? results.response : null,
key = session.dialogData.key;
var pairs = session.userData.kVPairs;
var newPair = {};
newPair[key] = value;
if (key && value) {
session.userData.kVPairs.push(newPair);
console.log(pairs[pairs.length - 1]);
}
session.send('latest key-value pair added, { %s : %s }', key, value);
session.replaceDialog('Loop');
}
session.replaceDialog('Loop') is incorporated at the end of this waterfall step and takes the Id of the new dialog. The method can also take optional arguments to pass to the new dialog.
Note: While not applicable here, the difference between replaceDialog and beginDialog/endDialog is semi-obvious, when you use beginDialog, the new dialog is added to the stack. When you end that child dialog, you will be returned to the original/parent dialog. replaceDialog will end the current dialog and begin the new one.
You may use replacedialog to loop the user:
bot.dialog("/getUserKeys", [
function (session, args, next) {
session.dialogData.keys = args && args.keys ? args.keys : [];
builder.Prompts.text(session, "Another key to put?");
},
function (session, results, next) {
if (results.response === "none") {
session.endDialogWithResult({response: { keys: session.DialogData.keys }});
return;
}
session.dialogData.keys[session.dialogData.keys.length] = results.response;
session.replaceDialog("/getUserKeys", { keys: session.DialogData.keys });
}
]);

Resources