I am new to TinCan, I am building a course where I need to set the completion status to completed and also the time needs to be recorded. I am using an LMS and also scormcloud for testing.
Currently, the completed status is working but it's not capturing the time spent.
This is the code:
var tincan = new TinCan({url: location.href});
tincan.sendStatement(new TinCan.Statement({
verb: 'completed',
result: {
success: true
}
}));
To include duration in that statement you should use the result.duration property. This is an ISO 8601 duration representing the attempt duration (rather than session duration). I actually have a blog on duration due out in the next month or so that I hope you'll find helpful.
I'd also recommend using a full verb object and using result.completion instead of result.success for a completion statement.
So your final code will be:
var tincan = new TinCan({url: location.href});
tincan.sendStatement(new TinCan.Statement({
verb: {
id: "http://adlnet.gov/expapi/verbs/completed",
display:{
"en-US": "completed"
}
},
result: {
completion: true,
duration: "PT21.896S"
}
}));
Note also that it be may best not to rely on the activity definition from the query string, but I appreciate you may be keeping the code sample short for the sake of the question.
Related
First question on StackOverflow, long time reader first time poster or whatever people say.
I'm developing a Discord bot in my free time using Discord.js, and I'm using Sequelize to interface with a local SQLite database. I can insert data into it just fine-- however, I can't seem to delete any of the records I add. Relevant piece of code is below, which I believe to be self-contradictory:
const query3 = await Towers.findAll({
attributes: ['channelID']
});
console.log(JSON.stringify(query3)); //returns the one Tower
console.log(query3[0].channelID === channel); //returns true(!)
const query2 = await Towers.findAll({
attributes: ['channelID'],
where: {channelID: channel}
});
console.log(JSON.stringify(query2)); //returns empty
//DELETE FROM Towers WHERE channelID = channel;
const query = await Towers.destroy({
where: {channelID: channel}
});
console.log(query); //returns 0, expected behavior given query2 returns empty
I'm attempting to delete a record from a table named Towers by passing a channel ID to it, which is expected to be unique. However, when I make any query on the database with a WHERE clause, the query returns an empty set-- even when, in this example, I sanity-checked and verified that the value I'm attempting to remove is present in the table. This occurs for both findAll() and findOne() as long as a WHERE clause is present.
(For posterity, I've double and triple checked that channelID was spelled correctly and with the correct capitalization in all instances.)
I'm happy to provide any more information if needed!
EDIT: As requested, the model definition...
const Towers = sequelize.define('Towers', {
serverID: {
type: Sequelize.INTEGER,
allowNull: false,
},
channelID: {
type: Sequelize.INTEGER,
unique: true,
allowNull: false,
},
pattern: Sequelize.STRING,
height: Sequelize.INTEGER,
delay: Sequelize.BOOLEAN,
});
channel in the snippet in the original post is defined as parseInt(interaction.options.getChannel('channel').id).
To anyone who happens to have the same issue I did, the answer is a doozy.
I wanted to store Discord server and channel ID's as integers, even though they're returned to you as strings when calling the API. As it turns out, Discord snowflakes are higher than float64 precision, which JS uses. When parsing the strings into integers to insert them into my table, the value changed from the intended number, and I was creating erroneous records.
In my case (with the actual numbers obfuscated) interaction.options.getChannel('channel').id returned "837512533934092340", while parseInt(interaction.options.getChannel('channel').id returned 837512533934092300. The number I was adding to the table was somehow 40 less!
I'm not sure if this could be fixed by using BigInt, but since it's going into a different structure anyway, I just shrugged and changed the serverId and channelId types to Sequelize.STRING in the model definition and removed the parseInt calls. Works like a charm now.
Good opportunity to shake my fist at JS though.
I have a collection of requests in POSTMAN. I wanted to add a pause between 2 requests but I couldn't find a way to do so from reading their docs. Any idea?
UPDATE I only wanted to put a pause after one request instead of after every request in the collection.
In case someone is still looking for this - You can add delay after/before 1 of many test in a collection you can use:
setTimeout(function(){}, [number]);
where 'number' is the milliseconds. If it's added at 'Tests' it will be executed after request is send. If it's added at Pre-request Scripts it will be executed before request is send.
Using javascript's busy wait is a good hack but it makes your CPU hot and the app unresponsive. I figured out this solution using postman-echo.
Assuming you want to add a long delay between Request_A and Request_B.
First, in Request_A's test script set up an env var to mark the start.
environment.delayTimerStart = new Date();
Then, create a GET request in the creation (here called 'Delay 10s'). It makes a GET on https://postman-echo.com/delay/10 (It returns after 10s)
In its test script, add
var curDate = new Date();
if (curDate - environment.delayTimerStart < delay_time_in_sec*1000) {
postman.setNextRequest('Delay 10s');
} else {
postman.setNextRequest("Request_B");
}
In this way you can add a delay of any length.
Note: 10 sec is the maxium value that postman-echo accepts. If you just need a short delay, simply GET https://postman-echo.com/delay/[1~10].
Try something like this-
if(jsonBody.responseCode=="SUCCESS"){
setTimeout(function(){
console.log("Sleeping for 3 seconds before next request.");
}, 3000);
postman.setNextRequest("nextAPI");
}
I know two possible ways to do this
Method I
Run your request as a collection. (https://www.getpostman.com/docs/collections)
Use Newman (Postman's collection runner from command line) to run your collection with --delay flag. The delay input value is in milliseconds.
Method II
This is a nice hack which I found here https://github.com/postmanlabs/postman-app-support/issues/1038. You can add a delay function to your test script in Postman.
Just simple example, I'm sure you will be understand.
setTimeout(() => {}, 15000);
15000 it's a value in miliseconds
looking at the current documentation if you are using Postman Runner
Delay
This is the interval (in ms) between each request in your collection run.
https://www.getpostman.com/docs/postman/collection_runs/starting_a_collection_run
and if you are using newman
--delay-request [number] Specify a delay (in ms) between requests [number]
https://www.getpostman.com/docs/postman/collection_runs/command_line_integration_with_newman
If you have the standalone Postman App (supports ES7) and you are intending to test only on Postman, not on newman(which doesn't support ES7), then you can have something like this in the Pre-Request script of the Request you want to delay:
function foo() {
return (new Promise((resolve, reject) => {
setTimeout(() => {
resolve("done!"); // passing argument is optional, can just use resolve()
}, 10000) // specify the delay time in ms here..
}))
}
async function waitForMe() {
await foo().then((val) => {
console.log(val); // not required, you can just do an await without then
})
}
waitForMe();
I prefer to use an online service Postman Echo's delay endpoint (docs are here). Simply create a request that uses this service and call it between the two other requests you wish to add a delay between.
If you want to check the status of something before continuing, you can use postman.setNextRequest() in the Tests of a request to loop until something has been completed. Simply do the following:
1) Create a collection structured as:
Delay For 10 Seconds
Status Check
Continue Processing
2) In the Status Check request Tests:
if(responseBody.has("Success")) //or any other success condition
{
postman.setNextRequest('Continue Processing');
tests["Success found"] = true;
}
else
{
postman.setNextRequest('Delay For 10 Seconds');
tests["No success found"] = true;
}
You can use JavaScript setTimeout method. This will make sure that your method will be executed after given delay.
setTimeout(checkStatusCode, 500);
function checkStatusCode() {
pm.sendRequest('https://postman-echo.com/get', function (err, res) {
tests['status code should be 200']= res.code ===200;
});
}
I thought it'd be easy but, yeah... it wasn't. I already posted a question that went in the same direction, but formulated another question.
What I want to do
I have the collection songs, that has a time attribute (the playing-time of the song). This attribute should be handled different in the form-validation and the backend-validation!
! I'd like to do it with what autoform (and simple-schema / collection2) offers me. If that's possible...
in the form the time should be entered and validated as a string that fits the regex /^\d{1,2}:?[0-5][0-9]$/ (so either format "mm:ss" or mmss).
in the database it should be stored as a Number
What I tried to do
1. The "formToDoc-way"
This is my javascript
// schema for collection
var schema = {
time: {
label: "Time (MM:SS)",
type: Number // !!!
},
// ...
};
SongsSchema = new SimpleSchema(schema);
Songs.attachSchema(SongsSchema);
// schema for form validation
schema.time.type = String // changing from Number to String!
schema.time.regEx = /^\d{1,2}:?[0-5][0-9]$/;
SongsSchemaForm = new SimpleSchema(schema);
And this is my template:
{{>quickForm
id="..."
type="insert"
collection="Songs"
schema="SongsSchemaForm"
}}
My desired workflow would be:
time is validated as a String using the schema
time is being converted to seconds (Number)
time is validated as a Number in the backend
song is stored
And the way back.
I first tried to use the hook formToDoc and converted the string into seconds (Number).
The Problem:
I found out, that the form validation via the given schema (for the form) takes place AFTER the conversion in `formToDoc, so it is a Number already and validation as a String fails.
That is why I looked for another hook that fires after the form is validated. That's why I tried...
2. The "before.insert-way"
I used the hook before.insert and the way to the database worked!
AutoForm.hooks({
formCreateSong: {
before: {
insert: function (doc) {
// converting the doc.time to Number (seconds)
// ...
return doc;
}
},
docToForm: function (doc) {
// convert the doc.time (Number) back to a string (MM:SS)
// ...
return doc;
}
}
});
The Problem:
When I implemented an update-form, the docToForm was not called so in the update-form was the numerical value (in seconds).
Questions:
How can I do the way back from the database to the form, so the conversion from seconds to a string MM:SS?
Is there a better way how to cope with this usecase (different data types in the form-validation and backend-validation)?
I am looking for a "meteor autoform" way of solving this.
Thank you alot for reading and hopefully a good answer ;-)
I feel like the time should really be formatted inside the view and not inside the model. So here's the Schema for time I'd use:
...
function convertTimeToSeconds (timeString) {
var timeSplit = timeString.split(':')
return (parseInt(timeSplit[0]) * 60 + parseInt(timeSplit[1]))
}
time: {
type: Number,
autoValue: function () {
if(!/^\d{1,2}:?[0-5][0-9]$/.test(this.value)) return false
return convertTimeToSeconds(this.value)
}
}
...
This has a small disadvantage of course. You can't use the quickForm-helper anymore, but will have to use autoForm.
To then display the value I'd simply find the songs and then write a helper:
Template.registerHelper('formateTime', function (seconds) {
var secondsMod = seconds % 60
return [(seconds - secondsMod) / 60, secondsMod].join(':')
})
In your template:
{{ formatTime time }}
The easy answer is don't validate the string, validate the number that the string is converted into.
With simpleschema, all you do is create a custom validation. That custom validation is going to grab the string, turn it into a number, and then validate that number.
Then, when you pull it from the database, you'll have to take that number & convert it into a string. Now, simpleschema doesn't do this natively, but it's easy enough to do in your form.
Now, if you wanted to get fancy, here's what I'd recommend:
Add new schema fields:
SimpleSchema.extendOptions({
userValue: Match.Optional(Function),
dbValue: Match.Optional(Function),
});
Then, add a function to your time field (stored as Date field):
userValue: function () {
return moment(this.value).format('mm:ss');
},
dbValue: function () {
return timeToNumber(this.value);
}
Then, make a function that converts a timeString to a number (quick and dirty example, you'll have to add error checking):
function timeToNumber(str) {
str.replace(':',''); //remove colon
var mins = +str.substr(0,2);
var secs = +str.substr(2,2);
return mins * 60 + secs;
}
Then, for real-time validation you can use schema.namedContext().validateOne. To update the db, just send timeToNumber(input.value).
I'm making a simple app that informs a client that other clients clicked a button. I'm storing the clicks in a Firebase (db) using:
db.push({msg:data});
All clients get notified of other user's clicks with an on, such as
db.on('child_added',function(snapshot) {
var msg = snapshot.val().msg;
});
However, when the page first loads I want to discard any existing data on the stack. My strategy is to call db.once() before I define the db.on('child_added',...) in order to get the initial number of children, and then use that to discard that number of calls to db.on('child_added',...).
Unfortunately, though, all of the calls to db.on('child_added',...) are happening before I'm able to get the initial count, so it fails.
How can I effectively and simply discard the initial data?
For larger data sets, Firebase now offers (as of 2.0) some query methods that can make this simpler.
If we add a timestamp field on each record, we can construct a query that only looks at new values. Consider this contrived data:
{
"messages": {
"$messageid": {
"sender": "kato",
"message": "hello world"
"created": 123456 // Firebase.ServerValue.TIMESTAMP
}
}
}
We could find messages only after "now" using something like this:
var ref = new Firebase('https://<your instance>.firebaseio.com/messages');
var queryRef = ref.orderBy('created').startAt(Firebase.ServerValue.TIMESTAMP);
queryRef.on('child_added', function(snap) {
console.log(snap.val());
});
If I understand your question correctly, it sounds like you only want data that has been added since the user visited the page. In Firebase, the behavior you describe is by design, as the data is always changing and there isn't a notion of "old" data vs "new" data.
However, if you only want to display data added after the page has loaded, try ignoring all events prior until the complete set of children has loaded at least once. For example:
var ignoreItems = true;
var ref = new Firebase('https://<your-Firebase>.firebaseio.com');
ref.on('child_added', function(snapshot) {
if (!ignoreItems) {
var msg = snapshot.val().msg;
// do something here
}
});
ref.once('value', function(snapshot) {
ignoreItems = false;
});
The alternative to this approach would be to write your new items with a priority as well, where the priority is Firebase.ServerValue.TIMESTAMP (the current server time), and then use a .startAt(...) query using the current timestamp. However, this is more complex than the approach described above.
Can someone help me understand how I can pass the start date into the calendar. I have created a Delivery Scheduler calendar and I display the delivery details in a table under the calends that is feed via the database. This requires me to refresh the page when a user select a calendar day to load the table information. I can figure out how to start the calendar on a starting date that is passed into the page.
Seems like this would be easy but I am doing something wrong.
$('#calendar').fullCalendar(Options);
$('#calendar').fullCalendar('gotoDate', '2012-10-21');
Sample based on documentation http://arshaw.com/fullcalendar/docs/current_date/gotoDate/
Remember that month is 0-based, so 10 means November.
$(document).ready(function () {
var calendar = $('#calendar').fullCalendar({
events:[
{ title:'All Day Event', start:new Date(2012, 10, 20)},
{ title:'Long Event', start:new Date(2012, 10, 21), end:new Date(2012, 10, 22)}
]
});
$('#calendar').fullCalendar('gotoDate', 2012, 10, 21);
});
Thank you Biesior for your helpful answer. I was able to use your suggested code to get the behavior I was looking for.
While using the approach above, I notice that Firebug's console shows two AJAX data requests being executed simultaneously, one for the view associated with the current date, and one for the view associated with the specified gotoDate.
There doesn't appear to be any additional delay from the user's perspective, and the calendar displays the requested view from the start. However, 'loading' callbacks will be called multiple times which might cause strange behavior in certain circumstances. There may also be other undesired results associated with the superfluous AJAX request for the current date.
I was able to avoid the unnecessary AJAX request by initializing the calendar without an event source, then moving to the desired date as shown by Biesior above, and then adding the event source. The sequence is shown below. I've removed some unrelated FullCalendar options and callbacks to keep it concise. There are some additional AJAX parameters, and some PHP, but the important thing to notice is when the event source is specified.
The original code results in two simultaneous AJAX requests:
$('#calendar').fullCalendar({
events: {
url:'/Services/GetEvents.php',
type: 'POST',
data: {
lat: <?=$venLatitude?>,
lon: <?=$venLongitude?>,
userID: <?=$userID?>,
distance: <?=$distance?>
}
}
})
$('#calendar').fullCalendar('gotoDate', <?=(int)substr($startDate,0,4)?>, <?=((int)substr($startDate,5,2))-1?>);
This adjustment results in only the desired AJAX request:
$('#calendar').fullCalendar();
$('#calendar').fullCalendar('gotoDate', <?=(int)substr($startDate,0,4)?>, <?=((int)substr($startDate,5,2))-1?>);
$('#calendar').fullCalendar('addEventSource', {
url:'/Services/GetEvents.php',
type: 'POST',
data: {
lat: <?=$venLatitude?>,
lon: <?=$venLongitude?>,
userID: <?=$userID?>,
distance: <?=$distance?>
}
});