How can Hubot run function regularly, without any command? - hubot

I'm trying to make a function for hubot send a message every 5 minutes to a room without any command, just by himself.
module.exports = (robot, scripts) ->
setTimeout () ->
setInterval () ->
msg.send "foo"
, 5 * 60 * 1000
, 5 * 60 * 1000
What do I need to change?

Use node-cron.
$ npm install --save cron time
And your script should look like this:
# Description:
# Defines periodic executions
module.exports = (robot) ->
cronJob = require('cron').CronJob
tz = 'America/Los_Angeles'
new cronJob('0 0 9 * * 1-5', workdaysNineAm, null, true, tz)
new cronJob('0 */5 * * * *', everyFiveMinutes, null, true, tz)
room = 12345678
workdaysNineAm = ->
robot.emit 'slave:command', 'wake everyone up', room
everyFiveMinutes = ->
robot.messageRoom room, 'I will nag you every 5 minutes'
More details: https://leanpub.com/automation-and-monitoring-with-hubot/read#leanpub-auto-periodic-task-execution

Actually the hubot documentation shows the usage of setInterval() and setTimeout() without the use of extra modules.
You could even do it inline by binding onto the msg object.
For example this:
module.exports = (robot) ->
updateId = null
robot.respond /start/i, (msg) ->
msg.update = () -> # this is your update
console.log "updating" # function
if not this.updateId # and here it
this.updateId = setInterval () -> # gets
msg.update() # periodically
, 60*1000 # triggered minutely
msg.send("starting")

The real issue is that you are trying to use msg.send without a message object. You should instead replace that line with a robot.messageRoom command (or some equivalent command if you want to send a private message).

Related

Sinon managing multiple clocks independently

Recently I updated from sinon 9 to sinon 14 which causes one of my tests to fail with the error Can't install fake timers twice on the same global object.. It looks like since version 12 it is not possible to call useFakeTimers multiple times to manage different clocks. What would be the alternative to achieve the same thing in sinon version 14?
t.test(`Skip calling the callback when the delay exceeds maxDelay`, t => {
t.plan(1);
const oneMinute = 60e3;
let rules = ['0 * * * * *']; // every minute
let timezone = null;
let maxDelay = 10;
let timerLag = 11;
let callback = spy();
let realClock = useFakeTimers({toFake: ['Date']});
let timerClock = useFakeTimers({ toFake: ['setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'setImmediate', 'clearImmediate'] });
scheduleInterval(rules, timezone, maxDelay, callback);
realClock.tick(oneMinute + timerLag);
timerClock.tick(oneMinute);
t.equal(callback.callCount, 0);
realClock.restore();
timerClock.restore();
});

Airflow: get xcom from previous dag run

I am writing a sensor which scan s3 files for fix period of time and add the list of new files arrived at that period to xcom for next task. For that, I am trying to access list of files passed in xcom from previous run. I can do that using below snippet.
context['task_instance'].get_previous_ti(state=State.SUCCESS).xcom_pull(key='new_files',task_ids=self.task_id,dag_id=self.dag_id)
However, context object is passed in poke method and I was to access it in init. Is there another way to do it without using context.
Note - I do not want to directly access underlying database for xcom.
Thanks
I found this solution which (kinda) uses the underlying database but you dont have to create a sqlalchemy connection directly to use it.
The trick is using the airflow.models.DagRun object and specifically the find() function which allows you to grab all dags by id between two dates, then pull out the task instances and from there, access the xcoms.
default_args = {
"start_date": days_ago(0),
"retries": 0,
"max_active_runs": 1,
}
with models.DAG(
f"prev_xcom_tester",
catchup=False,
default_args=default_args,
schedule_interval="#hourly",
tags=["testing"],
) as dag:
def get_new_value(**context):
num = randint(1, 100)
logging.info(f"building new value: {num}")
return num
def get_prev_xcom(**context):
try:
dag_runs = models.DagRun.find(
dag_id="prev_xcom_tester",
execution_start_date=(datetime.now(timezone.utc) - timedelta(days=1)),
execution_end_date=datetime.now(timezone.utc),
)
this_val = context["ti"].xcom_pull(task_ids="get_new_value")
for dr in dag_runs[:-1]:
prev_val = dr.get_task_instance("get_new_value").xcom_pull(
"get_new_value"
)
logging.info(f"Checking dag run: {dr}, xcom was: {prev_val}")
if this_val == prev_val:
logging.info(f"we already processed {this_val} in {dr}")
return (
dag_runs[-2]
.get_task_instance("get_new_value")
.xcom_pull("get_new_value")
)
except Exception as e:
logging.info(e)
return 0
def check_vals_match(**context):
ti = context["ti"]
prev_run_val = ti.xcom_pull(task_ids="get_prev_xcoms")
current_run_val = ti.xcom_pull(task_ids="get_new_value")
logging.info(
f"Prev Run Val: {prev_run_val}\nCurrent Run Val: {current_run_val}"
)
return prev_run_val == current_run_val
xcom_setter = PythonOperator(task_id="get_new_value", python_callable=get_new_value)
xcom_getter = PythonOperator(
task_id="get_prev_xcoms",
python_callable=get_prev_xcom,
)
xcom_checker = PythonOperator(
task_id="check_xcoms_match", python_callable=check_vals_match
)
xcom_setter >> xcom_getter >> xcom_checker
This dag demonstrates how to:
Set a random int between 1 and 100 and passing it through xcom
Find all dagruns by dag_id and time span -> check if we have processed this value in the past
Return True if current value matches value from previous run.
Hope this helps!

Airflow UI Changing Execution Datetime to Readable Format

In Airflow's UI, if I hover over any of my task IDs, it'll show me the "Run", "Started", and "Ended" dates all with a very verbose format i.e. 2021-02-12T18:57:45.314249+00:00.
How do I change the default preferences in Airflow's UI so that it simply shows 2/12/21 6:57:45pm? (i.e. without the fractions of a second)
Additionally, how do I ensure that this time is showing in America/Chicago time as opposed to UTC? I've tried editing the "default_timezone" and the "default_ui_timezone" arguments in my airflow.cfg file to America/Chicago, but the changes don't seem to be reflected on the UI even after rebooting the webserver.
I have managed to achieve the result you wanted. You'll need the edit a javascript file in the source code of airflow to achieve that.
Firstly, locate your module location by launching:
python3 -m pip show apache-airflow
And look for the "Location" attribute, which is the path to where the module is contained. Open that folder, then navigate as follows:
airflow -> www -> static -> dist
Here you need to look for a file named taskInstances.somehash.js
Open it with your IDE and locate the following lines:
const defaultFormat = 'YYYY-MM-DD, HH:mm:ss';
const defaultFormatWithTZ = 'YYYY-MM-DD, HH:mm:ss z';
const defaultTZFormat = 'z (Z)';
const dateTimeAttrFormat = 'YYYY-MM-DDThh:mm:ssTZD';
You can hereby change the format as you please, such as:
const defaultFormat = 'DD/MM/YY hh:mm:ss';
const defaultFormatWithTZ = 'DD/MM/YY hh:mm:ss z';
const defaultTZFormat = 'z (Z)';
const dateTimeAttrFormat = 'DD/MM/YY hh:mm:ss';
Now jump to the makeDateTimeHTML function and modify as follows:
function makeDateTimeHTML(start, end) {
// check task ended or not
const isEnded = end && end instanceof moment && end.isValid();
return `Started: ${start.format('DD/MM/YY hh:mm:ss')}<br>Ended: ${isEnded ? end.format('DD/MM/YY hh:mm:ss') : 'Not ended yet'}<br>`;
}
Lastly, locate these this statement:
if (ti.start_date instanceof moment) {
tt += `Started: ${Object(_main__WEBPACK_IMPORTED_MODULE_0__["escapeHtml"])(ti.start_date.toISOString())}<br>`;
} else {
tt += `Started: ${Object(_main__WEBPACK_IMPORTED_MODULE_0__["escapeHtml"])(ti.start_date)}<br>`;
}
// Calculate duration on the fly if task instance is still running
And change to:
if (ti.start_date instanceof moment) {
tt += `Started: ${Object(_main__WEBPACK_IMPORTED_MODULE_0__["escapeHtml"])(ti.start_date)}<br>`;
} else {
tt += `Started: ${Object(_main__WEBPACK_IMPORTED_MODULE_0__["escapeHtml"])(ti.start_date)}<br>`;
}
// Calculate duration on the fly if task instance is still running
Took me a while to figure out, so hopefully this will be of your liking.

How to get document _id from Meteor cursor?

I have rewritten this question as i now understand my problem a bit more. The answers below remain relevant.
I have the following query which returns a record.
Template.game.helpers({
Game: function () {
var myGame = Games.findOne(
{
game_minutes: {$gt: MinutesSinceMidnightNow},
court_id: court,
game_date: {$gt: lastMidnight}
},
{
sort: {game_minutes: 1}
}
); // find
console.log(myGame);
console.log(myGame._id);
return myGame;
} // game function
}); //template scoreboard.helpers
Meteor.startup(function () {
Meteor.call('removeGames', court, MinutesSinceMidnightNow);
for(var i=0;i<incomingGames.length;i++){
var game = incomingGames[i];
var gameTime = game.game_time;
if ( MinutesSinceMidnightGameTime(gameTime) > MinutesSinceMidnightNow ) {
console.log("game # " + i + ' game time ' + MinutesSinceMidnightGameTime(gameTime) + ' now' + ' ' + MinutesSinceMidnightNow);
Meteor.call('insertGame', game);
} // if
} // for
// game = Meteor.call("nextGame", MinutesSinceMidnightNow, court, lastMidnight);
console.log(MinutesSinceMidnightNow + ', ' + court + ', ' + lastMidnight);
}); // startup
The first console.log shows a game object which includes the _id property. The second console log throws an error. How can I get the _id value?
On thinking more about this, the code may actually work. Console log eventually displays nthe id number. The strange thing is the error occurs before the game inserts in server startup. I guess the client started before the server and then reactively aligned with the real data once the server started? This is hard to get my head around coming from traditional web development.
Here is the console output
undefined scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:118
Exception in template helper: TypeError: Cannot read property '_id' of undefined
at Object.Template.game.helpers.Game (http://localhost:3000/client/scoreboard/scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:122:19)
at http://localhost:3000/packages/blaze.js?88aac5d3c26b7576ac55bb3afc5324f465757709:2693:16
at http://localhost:3000/packages/blaze.js?88aac5d3c26b7576ac55bb3afc5324f465757709:1602:16
at Object.Spacebars.call (http://localhost:3000/packages/spacebars.js?3c496d2950151d744a8574297b46d2763a123bdf:169:18)
at Template.game.HTML.DIV.Spacebars.With.HTML.SPAN.class (http://localhost:3000/client/scoreboard/template.scoreboard.js?0ad2de4b00dfdc1e702345d82ba32c20d943ac63:16:22)
at null.<anonymous> (http://localhost:3000/packages/spacebars.js?3c496d2950151d744a8574297b46d2763a123bdf:261:18)
at http://localhost:3000/packages/blaze.js?88aac5d3c26b7576ac55bb3afc5324f465757709:1795:16
at Object.Blaze._withCurrentView (http://localhost:3000/packages/blaze.js?88aac5d3c26b7576ac55bb3afc5324f465757709:2029:12)
at viewAutorun (http://localhost:3000/packages/blaze.js?88aac5d3c26b7576ac55bb3afc5324f465757709:1794:18)
at Tracker.Computation._compute (http://localhost:3000/packages/tracker.js?192a05cc46b867dadbe8bf90dd961f6f8fd1574f:288:36) debug.js:41
game # 0 game time 1395 now 549 scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:148
game # 1 game time 1110 now 549 scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:148
game # 2 game time 1185 now 549 scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:148
game # 3 game time 1260 now 549 scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:148
549, 1, Wed Oct 22 2014 00:00:00 GMT+0930 (CST) scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:157
Object {_id: "scYEdthygZFHgP2G9", court_id: 1, game_date: Wed Oct 22 2014 09:09:50 GMT+0930 (CST), court_name: "Court 1", game_time: "18:30"…} scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:118
scYEdthygZFHgP2G9
I cannot comment on the accepted answer, so I'll put the explaination as to why you see the log error here.
Your code runs just fine, the problem is (and reason for your log error) that you don't take into account that your collection of games isn't populated with any data yet. The first line in your log output reads:
undefined scoreboard.js?c19ff4a1d16ab47e5473a6e43694b3c42ec1cc22:118
which corresponds to
console.log(myGame);
The first time Meteor renders your templates, you simply don't have any data in the Games collection - it's on the wire on the way to your client. Meteor then automatically reruns your templates when data has arrived, explaining the subsequent console outputs.
So basically, the only thing that is wrong with your code at this moment, is the console log that tries to output the _id, since the during the first evaluation there is no game (thus you trying to access the property "_id" of the object "undefined" - the log error message). Remove that line and you should be ready to go!
If the parameter being passed to the function is an array, you can use Array.every. If it's a cursor, you'd need to fetch the results first.
UPDATE
I've just seen your comment. If you're looking for the first game after timenow, just do:
game = Games.findOne({game_minutes: {$gt: timenow}, [ANY OTHER FILTER]}, {sort: {game_minutes: 1}});
I've assumed the collection is called Games, and obviously you need to substitute in any other filter details to get the right set of games to look through, but it should work.
If you can access the game collection, I prefer adding selector and options to your query:
next_game = Games.find(
{
game_minutes: {$gt: timenow}
},
{
sort: {game_minutes: 1},
limit: 1
});
If not, fetch, filter, and then get the minimum one.
new_games = games.fetch().filter(function(game){
return game.game_minutes > timenow;
});
next_game = _.min(new_games, function(game){
return game.game_minutes;
});

Awesome wm keyup and keydown events

I'm using Awesome Window Manager. I want to show my top bar by pressing mod4 and then hide it when I release. I tired passing "keyup Mod4" to awful.key but it does not work. How can I tell it that I want to trigger an event on keyup?
Try
`awful.key({ modkey }, "", nil, function () staff here end)`
3rd param is handler for "release" event when passed.
I wanted the same thing! After some research I came up with:
Use external program to execute echo 'mywibox[1].visible = true' | awesome-client when mod4 is pressed and echo 'mywibox[1].visible = false' | awesome-client when released.
Use other key, not modifier, like Menu (near right Ctrl), because for some reason you can't hook up press and release event to mod4 (or it just doesn't work).
Here is my solution (timer is required because pressed key sends events as long as it is pressed):
-- Put it somewhere at the beginning
presswait = { started = false }
-- Put it in key bindings section (globalkeys = within awful.table.join)
awful.key({ }, "Menu", function()
if presswait.started then
presswait:stop()
else
-- One second to tell if key is released
presswait = timer({ timeout = 1 })
presswait:connect_signal("timeout", function()
presswait:stop()
-- Key is released
for i = 1, screen.count() do
mywibox[i].visible = false
end
end)
-- Key is pressed
for i = 1, screen.count() do
mywibox[i].visible = true
end
end
presswait:start()
end)
You could connect a signal to the key object:
key.connect_signal("press", function(k)
-- Analyze k and act accordingly
end)
More about signals here:
http://awesome.naquadah.org/wiki/Signals
Using the first suggestion from https://stackoverflow.com/a/21837280/2656413
I wrote this python script: https://github.com/grandchild/autohidewibox
What it does is, it runs xinput in the background and parses its output. You could also parse /dev/input/event1 directly in python, but I was lazy.
It then pipes the following lua code to awesome every time the key is pressed or released:
echo 'for i, box in pairs(mywibox) do box.visible = true end' | awesome-client
and
echo 'for i, box in pairs(mywibox) do box.visible = false end' | awesome-client
respectively.
Update:
For awesome 4+ use:
echo "for s in screen do s.mywibox.visible = false end" | awesome-client
or true.

Resources