Run Experiment via Feature and get VariationId of Experiment - growthbook

Playing around with growthbook, and having a hard time, figuring out how to use experiments.
created experiment.
created feature, with experiment rule
now my feature has variations, and my experiment have variations.
in JS-Land, i do something like:
const growthbook = new GrowthBook({
apiHost: "http://localhost:3100",
clientKey: "sdk-ZzfdzxSSDuHgmpXG",
enableDevMode: true,
trackingCallback: (experiment, result) => {
console.log("Viewed Experiment", {
experimentId: experiment.key,
variationId: result.variationId
});
}
});
growthbook.setAttributes({
user_id: "ccc",
id: "cccc"
});
await growthbook.loadFeatures({ autoRefresh: true });
if (growthbook.isOn("my-test")) {
console.log("Feature is enabled!");
}
it works, but in fact the variationId in the console.log is actually the variation ID of the Feature itself.
question: how to get the variation id of the experiment, or what is the best way to run a experiment.
not quiet sure, how to trigger experiments, and how they are connected. (i understand, metric tracking and conversion tracking - but that requires to get the variationId)
or is it supposed to be - that variationId's on feature need to be done too on experiment?!

Related

How to update user permission discord js

So, I want to update a permission for some users.
First, I tried to create a text channel and overwrite a user Permission. Then I do a for loop to update the permission, but I dont know how.
Things I've tried to update the permission :
channel.overwritePermissions({
permissionOverwrites: [
{
id: guild.id,
deny: ['VIEW_CHANNEL'],
},
{
id: playerData[i],
allow: ['VIEW_CHANNEL'],
},
],
});
//it said channel not defined?
message.guild.channels.cache.find('🐺werewolves').overwritePermissions({
permissionOverwrites: [
{
id: playerData[i],
allow:['VIEW_CHANNEL'],
}
]
})
//it said : fn is not a function
I've I have seen this solution and read the documentation, but the instruction isn't clear.
PS : The permission update must be a loop because the number of users who get the permission always changing.
Regarding first codeblock
channel is not defined, because it's not defined. You cannot use variables without defining them, that's how JavaScript works. To solve it, you would have to define it using for example const channel = ..., or access it from other variables you have, just like you are trying to use find in your second codeblock - you access it from message, as you're most likely in the message event.
Regarding second codeblock
This is not a proper way to use find, neither in old - removed in your version - way, nor the new way. The old way was to do find(property, value), to which you wouldn't provide a value ('🐺werewolves' would be treated as property you're trying to search by). New way you have to use, allows way more flexibility by requiring to pass it a function, just like in the example for the method. Since what you passed was a string and not a function, internal code throws an error fn is not a function.
For your example above, correct way to use that find would be
message.guild.channels.cache.find(channel => channel.name === '🐺werewolves');
Additionally, note that ideally you shouldn't try to call any methods on that directly, as in case when no channel with that name would be found, your code will throw an error. Snippet below should avoid that possibility.
const channel = message.guild.channels.cache.find(channel => channel.name === '🐺werewolves');
if (channel) channel.overwritePermissions(...)

What is the best practice to avoid utterance conflicts in an Alexa Skill

In the screenshot below, I have got an utterance conflict, which is obvious because I am using similar patterns of samples in both the utterances.
My question is, the skill I am developing requires similar kind of patterns in multiple utterances and I cannot force users to say something like “Yes I want to continue”, or “I want to store…”, something like this.
In such a scenario what is the best practice to avoid utterance conflicts and that too having the multiple similar patterns?
I can use a single utterance and based on what a user says, I can decide what to do.
Here is an example of what I have in my mind:
User says something against {note}
In the skill I check this:
if(this$inputs.note.value === "no") {
// auto route to stop intent
} else if(this$inputs.note.value === "yes") {
// stays inside the same intent
} else {
// does the database stuff and saves the value.
// then asks the user whether he wants to continue
}
The above loop continues until the user says “no”.
But is this the right way to do it? If not, what is the best practice?
Please suggest.
The issue is really that for those two intents you have slots with no context around them. I'm also assuming you're using these slots as catch-all slots meaning you want to capture everything the person says.
From experience: this is very difficult/annoying to implement and will not result in a good user experience.
For the HaveMoreNotesIntent what you want to do is have a separate YesIntent and NoIntent and then route the user to the correct function/intent based on the intent history (aka context). You'll have to just enable this in your config file.
YesIntent() {
console.log(this.$user.$context.prev[0].request.intent);
// Check if last intent was either of the following
if (
['TutorialState.TutorialStartIntent', 'TutorialLearnIntent'].includes(
this.$user.$context.prev[0].request.intent
)
) {
return this.toStateIntent('TutorialState', 'TutorialTrainIntent');
} else {
return this.toStateIntent('TutorialState', 'TutorialLearnIntent');
}
}
OR if you are inside a state you can have yes and no intents inside that state that will only work in that state.
ISPBuyState: {
async _buySpecificPack() {
console.log('_buySpecificPack');
this.$speech.addText(
'Right now I have a "sports expansion pack". Would you like to hear more about it?'
);
return this.ask(this.$speech);
},
async YesIntent() {
console.log('ISPBuyState.YesIntent');
this.$session.$data.productReferenceName = 'sports';
return this.toStatelessIntent('buy_intent');
},
async NoIntent() {
console.log('ISPBuyState.NoIntent');
return this.toStatelessIntent('LAUNCH');
},
async CancelIntent() {
console.log('ISPBuyState.CancelIntent()');
return this.toStatelessIntent('LAUNCH');
}
}
I hope this helps!

Loop with break or continue based on user input

I loosely know how to use a loop. I've used simple ones and understand how it works. However, I have a situation where I think a loop would be useful, but I'm not quite certain how to get the desired result. I am self-taught in javascript and I've tried poking around some other posts, but I don't recall seeing anything that helped in my case. This may very well be one of my gaps in understanding.
What I'd like this to do: This code is going to be taking the top card off a deck of cards for a TCG. Each card has a "type". I want the person to input a command into Discord like "!flipcard 10". I would like the bot to make a loop to flip cards off the top of the deck until 10 is reached OR a certain card type is flipped over. The card "types" I am working with are "action", "evo", "flip" and "hero". I only want the bot to react differently to the "Flip" card type. I have gotten working code to where I can do this if the user inputs !flipcard but does it one at a time. The maximum amount of cards that can ever be flipped is 34 cards or so, but this is less likely to happen than lower numbers.
I just wrote the code up from what I think I need to do, but I get stuck with not knowing exactly where to go next. So it isn't exactly functioning code yet. I do get an "illegal break statement" error currently, so I can't progress much more.
for (i = 0; i < damageToTake; i++) {
cardRemoved = deckAarray.shift()
//Bunch of stuff edited out for Embed mentioned below
if (cardType == "Flip"){
message.channel.send(flipEmbed)
const filter = m => m.author.id === message.author.id
author.send(`You revealed a Flip card! Do you wish to use it? Please reply with 'yes' or 'no'.`).then((newmsg) => {
newmsg.channel.awaitMessages(filter, {
max: 1,
}).then((collected) => {
reply = collected.first();
if (reply.content == 'yes'){
console.log("Yes reply.")
break;
} else if (reply.content == 'no'){
console.log("No reply")
}
})
})
} else if (cardType != "Flip"){
message.channel.send(nonflipEmbed)
continue;
}
}
})
}
Like I mentioned, the code isn't working to a point I can try it and get a problem because I am getting an "illegal break statement" error.
I think I am on the right track, but I'm uncertain how to make it work exactly how I'm hoping.
So, to conclude...
I would like this code to to the following:
1)User inputs command and specifies how many cards to flip over.
2)Loop should flip cards (displaying the correct embed based on card type)
3)When a "flip" card type is revealed, I would like the code to pause, essentially, to ask the author whether or not they would like to use the Flip card that was revealed. If yes, stop the loop. If no, continue the loop from where it left off until the desired number are flipped OR if another Flip card type is revealed.
Thanks for the help in advance!
What you are trying to do is impossible without code modification. The reason for this is that for loops are synchronous, while promises are not. This means that .then() will only be reached after the loop completes.
One way around this is to wrap everything inside an async function and run your loop in there. Then, use try-catch clauses to break out of the loop when needed. Consider the toy example below:
( async () => {
for (i = 0; i < 100; i++) {
console.log('i:', i);
try {
const val = await randomPromise();
console.log('should resolve and break!');
console.log(val);
break;
} catch (e) {
console.log('moving on...');
}
}
})();
function randomPromise() {
return new Promise( (resolve, reject) => {
const val = Math.random();
if (val > 0.9) {
resolve(val);
} else {
reject(0);
}
})
}
You've changed contexts -- as in you have two functions that know nothing about each other's control flow (e.g.: if/then, for loops, while loops, etc). They cannot influence each other in the way you're wanting. What you're doing is the equivalent of this pseudo code:
function printAllNumbers() {
for(currentNumber : allNumbers) {
printNumber(currentNumber);
}
}
function printNumber(num) {
print(num);
break; // This won't work because it has no clue about for/loop in other func
}
printNumber is scoped so that it only knows what its own function (those 2 lines) are doing. It has no clue about the for/each in the printAllNumbers call, hence the error you're seeing.
And what I said above is only partially true -- you're actually maybe 2 functions deep at this point because you've called .then() and then after your .newMessage() call another .then(). You're reading the code sequentially, but you should try and look at it as branching off paths in this way:
for {
call async functions and (.then) call these other functions
}
...some time passes
<branched off path> The other functions within .then(functions) get called
I recommend reading up on how asynchronous functions work, and also how promises work to better understand in what order your code will execute.
To do what you want, you'll need to write separate code to handle these scenarios:
1) Receiving the number of cards from user and just flat out looping through and printing those cards to chat
2) Receiving further input based on the user (will need to keep track of user) and the current state of their cards that have been flipped -- probably want to number the cards and have them send a command like USE 1 or DISCARD ALL
The code won't pause in the middle of your loop to wait for input, these each have to be different pieces of code.

Hiding _all_ remote methods in loopback model

I'm using loopback 3.
I have this lines of codes in my model's js (survey.js):
let enabledRemoteMethods = []
Survey.sharedClass.methods().forEach(function(method) {
console.log(method.name, method.isStatic)
let matchingEnabledRemoteMethod = _.find(enabledRemoteMethods, {name: method.name});
if (!matchingEnabledRemoteMethod) {
Survey.disableRemoteMethodByName(method.name, method.isStatic);
}
});
It works.... almost. I could still see in the explorer the REST endpoint for "PATCH /surveys/{id}". My expectation is: there shouldn't be any REST endpoints listed in the explorer.
Then I examined the URL corresponding to that operation, it is:
http://localhost:3000/explorer/#!/Survey/Survey_prototype_patchAttributes
Which, according to the documentation, means that patchAttributes is a static method.
Then I cross checked with the output in the console... there it says: pathAttributes is not static.
Incosistency!
I even have tried adding this line:
Survey.disableRemoteMethodByName("patchAttributes", true);
Also
Survey.disableRemoteMethodByName("patchAttributes", false);
No luck.
Can someone confirm if it's a bug in loopback 3 (I don't know about loopback 2, haven't checked)? If it's a bug I wouldn't have to spend time on it and just wait until it gets fixed. But if it's not a bug, can someone point out what's missing in my code?
Thanks!
UPDATE: figured out how
With this line I'm able to get rid of it:
Survey.disableRemoteMethodByName("prototype.patchAttributes", true);
The second parameter doesn't seem to matter (you can put false as well). Not sure why though (I suppose it should've accepted true only).
This is my current solution:
let disabledPrototypesRemoteMethods = ['patchAttributes']
let enabledRemoteMethods = [
"create", "findById", "replaceById", "deleteById",
"replaceOrCreateQuestion"
]
Survey.sharedClass.methods().forEach(function(method) {
if (enabledRemoteMethods.indexOf(method.name) == -1) {
Survey.disableRemoteMethodByName(method.name);
}
if (disabledPrototypesRemoteMethods.indexOf(method.name) > -1) {
Survey.disableRemoteMethodByName("prototype." + method.name);
}
});
Still, one small detail: this thing still shows up (I suppose it provides the POST alternative for the normal PUT for the replaceById operation..., but I don't want it; I want to force user of my API to go with the PUT only):
http://localhost:3000/explorer/#!/Survey/Survey_replaceById_post_surveys_id_replace
I tried adding this line:
Survey.disableRemoteMethodByName("replaceById_post_surveys_id_replace");
No luck.
Anyway... hope this useful for others; loopback doc is kind of sketchy.
You can get the prototype methods as well by looking at the stringName property. That way you can include the prototypes in your list.
The stringName includes the sharedClass name in the value, so you will need to parse that out.
module.exports = BusinessProfileContacted => {
const enabledRemoteMethods = ["create", "findById", "replaceById", "deleteById", "replaceOrCreateQuestion", "prototype.replaceAttributes"];
Survey.sharedClass.methods().forEach(method => {
const methodName = method.stringName.replace(/.*?(?=\.)/, '').substr(1);
if (enabledRemoteMethods.indexOf(methodName) === -1) {
Survey.disableRemoteMethodByName(methodName);
}
});
};

How to read/write a document in parallel execution with mongoDB/mongoose

I'm using MongoDB with NodeJS. Therefore I use mongoose.
I'm developing a multi player real time game. So I receive many requests from many players sometimes at the very same time.
I can simplify it by saying that I have a house collection, that looks like this:
{
"_id" : 1,
"items": [item1, item2, item3]
}
I have a static function, called after each request is received:
house.statics.addItem = function(id, item, callback){
var HouseModel = this;
HouseModel.findById(id, function(err, house){
if (err) throw err;
//make some calculations such as:
if (house.items.length < 4){
HouseModel.findByIdAndUpdate(id, {$push: {items: item}}, cb);
}
});
}
In this example, I coded so that the house document can never have more than 4 items. But what happens is that when I receive several request at the very same time, this function is executed twice by both requests and since it is asynchronous, they both push a new item to the items field and then my house has 5 items.
I am doing something wrong? How can I avoid that behavior in the future?
yes, you need better locking on the houseModel, to indicate that an addItem
is in progress.
The problem is that multiple requests can call findById and see the same
house.items.length, then each determine based on that (outdated) snapshot
that it is ok to add one more item. The nodejs boundary of atomicity is the
callback; between an async call and its callback, other requests can run.
One easy fix is to track not just the number of items in the house but the
number of intended addItems as well. On entry into addItem, bump the "want
to add more" count, and test that.
One possible approach since the release of Mongoose 4.10.8 is writing a plugin which makes save() fail if the document has been modified since you loaded it. A partial example is referenced in #4004:
#vkarpov15 said:
8b4870c should give you the general direction of how one would write a plugin for this
Since Mongoose 4.10.8, plugins now have access to this.$where. For documents which have been loaded from the database (i.e., are not this.isNew), the plugin can add conditions which will be evaluated by MongoDB during the update which can prevent the update from actually happening. Also, if a schema’s saveErrorIfNotFound option is enabled, the save() will return an error instead of succeeding if the document failed to save.
By writing such a plugin and changing some property (such as a version number) on every update to the document, you can implement “optimistic concurrency” (as #4004 is titled). I.e., you can write code that roughly does findOne(), do some modification logic, save(), if (ex) retry(). If all you care about is a document remaining self-consistent and ensuring that Mongoose’s validators run and your document is not highly contentious, this lets you write code that is simple (no need to use something which bypasses Mongoose’s validators like .update()) without sacrificing safety (i.e., you can reject save()s if the document was modified in the meantime and avoid overwriting committed changes).
Sorry, I do not have a code example yet nor do I know if there is a package on npm which implements this pattern as a plugin yet.
I am also building a multiplayer game and ran into the same issue. I believe I have solved it my implementing a queue-like structure:
class NpcSaveQueue {
constructor() {
this.queue = new Map();
this.runQueue();
}
addToQueue(unitId, obj) {
if (!this.queue.has(unitId)) {
this.queue.set(String(unitId), obj);
} else {
this.queue.set(String(unitId), {
...this.queue.get(unitId),
...obj,
})
}
}
emptyUnitQueue(unitId) {
this.queue.delete(unitId);
}
async executeUnitQueue(unitId) {
await NPC.findByIdAndUpdate(unitId, this.queue.get(unitId));
this.emptyUnitQueue(unitId);
}
runQueue() {
setInterval(() => {
this.queue.forEach((value, key) => {
this.executeUnitQueue(key);
})
}, 1000)
}
}
Then when I want to update an NPC, instead of interacting with Mongoose directly, I run:
npcSaveQueue.addToQueue(unit._id, {
"location.x": newLocation.x,
"location.y": newLocation.y,
});
That way, every second, the SaveQueue just executes all code for every NPC that requires updating.
This function never executes twice, because update operation is atomic on a level of single document.
More info in official manual: http://docs.mongodb.org/manual/core/write-operations-atomicity/#atomicity-and-transactions

Resources