-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Refactor bot commands #23
Conversation
e397f5c
to
030a0ac
Compare
@stasm - r? |
030a0ac
to
058224b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR, @zbraniecki! For now I left a few general comments about the coding style. I would also like to spend some time thinking about the log
collection. Perhaps an in-memory list would suffice for now? What would you say to that?
Would you also mind editing the initial comment in this PR and explaining the goals of this change and summarize what's new?
|
||
router.use(cors); | ||
router.use(bodyParser.json()); | ||
|
||
router.route('/reports').get(reports.get(reportsColl)); | ||
router.route('/reports/current').get(reports.current(reportsColl)); | ||
router.route('/reports').post(reports.create(reportsColl)); | ||
router.route('/reports/remove/:id').post(reports.remove(reportsColl)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use HTTP DELETE
verb here?
router.route('/updates/:id').post(updates.update(updatesColl)); | ||
router.route('/resolve').post(updates.resolve(updatesColl)); | ||
|
||
router.route('/log').get(log.get(logColl)); | ||
router.route('/log').post(log.insert(logColl)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you feel a mongo collection is needed for the log? I'd guess keeping the log in memory should be easier and sufficient for now. When we want to ensure persistency we can add Redis or something similar.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main reason is ability to debug actions. db log seems like a good place to store all actions and be able to come back and rewind later if needed looking into each operation.
Especially with ability to undo things it may be later hard for the maintainer to understand what has led to the state which you're debugging. Log may help.
Also, I'm not sure what's hard about log?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One thought I had was that logging could happen in the API app.
I'm not against in-memory logging per-se, I just don't think it would be easier and it would limit the value of log to just this single use-case (reverting action in bot).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This also enables #10
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, I think #10 is enabled by aggregating the updates
collection. I have a WIP.
I'd like to avoid increasing the complexity of the code for as long as possible. Also, let's try to avoid over-engineering the code by designing for future requirements. If an in-memory array serves the current purpose well, I say let's go with it, please. It's simple and synchronous.
@@ -60,18 +68,27 @@ function getUpdates(coll, raw) { | |||
function createUpdate(coll, body) { | |||
delete body._id; | |||
const d = new Date(); | |||
return coll.insert(Object.assign(body, { | |||
|
|||
let o = Object.assign(body, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const
({ops}) => res.json(ops[0]) | ||
).catch(console.error); | ||
createUpdate(coll, req.body) | ||
.then(resp => res.json(resp)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We rely on the API returning the full update object to assign not only the id but also proper creation dates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
'at your service', | ||
'sounds good' | ||
]; | ||
import { Commands } from './commands/index'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
from './commands';
should be sufficient here.
return Promise.resolve('that\'s not a valid date'); | ||
if (message.startsWith('test ')) { | ||
message = message.substr(5); | ||
test = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe delegate this flow to a separate function, testCommand
?
|
||
matches: (str) => { | ||
return true; | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using the shorthand method definitions:
matches(str) {
return true;
}
…ds in separate file and use API
7b2e43e
to
b70bf09
Compare
@stasm moves log to in-memory |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, this is getting really close and I really like the change overall. Thanks!
@@ -64,8 +75,17 @@ function createReport(coll, body) { | |||
const month = reportDate.getUTCMonth() + 1; | |||
const day = reportDate.getUTCDate(); | |||
const slug = `${year}-${pad(month)}-${pad(day)}`; | |||
return coll.insert(Object.assign(body, { | |||
|
|||
let o = Object.assign(body, { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use const
here.
})); | ||
}); | ||
return coll.insertOne(o).then(() => { | ||
return o._id; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we keep this consistent with createUpdate
and return the whole object? I'd like to revisit this in the near future and in fact I'd prefer to return the id
like here, but for now I vote consistency.
|
||
router.route('/updates').get(updates.get(updatesColl)); | ||
router.route('/updates').post(updates.create(updatesColl)); | ||
router.route('/updates/delete/:id').post(updates.remove(updatesColl)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the benefits of POST
over DELETE
here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idk, I don't know how delete works vs. post.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here you'll need to use the delete
method:
router.route('/updates/:id').delete(updates.remove(updatesColl));
And then probably write a new helper in utils.js
:
export function delete(url) {
return fetch(url, {
method: 'DELETE',
// not sure if headers are required
}).then(resp => resp.json());
}
}); | ||
return coll.insertOne(o).then(() => { | ||
return o; | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In such cases please use an implicit-return syntax:
return coll.insertOne(o).then(() => o);
if (isNaN(ts)) { | ||
return Promise.resolve('that\'s not a valid date'); | ||
function parseCommand(bot, author, channel, message) { | ||
console.log(message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind changing this to something like:
console.log(` --- got a command: ${message}`);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was for debugging, I removed it. You're logging the message in the listener anyway.
command: CreateReport.name, | ||
id: resp, | ||
}); | ||
}).then( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please use the implicit-return syntax.
); | ||
for (let command of bot.commands) { | ||
if (command.matches(message)) { | ||
return command.execute(bot, author, channel, message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please handle errors from execute
with console.error
here. You'll need to return Promise.reject()
from commands in case of errors.
if (!this.actionLog.has(hash)) { | ||
this.actionLog.set(hash, []); | ||
} | ||
const log = this.actionLog.get(hash); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use getActionLog()
here?
bot.logAction(author, channel, { | ||
command: RevertLastCommand.name, | ||
id: entry.object.id | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to add it to the log if it's not revertable?
import { get, post } from '../utils'; | ||
import { Commands } from './index'; | ||
|
||
function getCommandByName(name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you change commands' names to match the names of the exports (or write a dash-to-pascal-case helper), you can do:
import * as commands from './index';
and:
return commands[name];
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't. The babel then break all iterations over the result object. I can't for ... of
, and I can for ... in
, but with an additional __esModule
property which I have to filter out.
I changed commands names, but I left it as import/export.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see, that's a shame. I wonder why Babel doesn't make this additional property non-enumerable.
ok, I think I'm done with this round. :) Please re-review and land at will :) |
if (command.matches(message)) { | ||
return command.execute(bot, author, channel, message).catch(e => { | ||
console.log(`Command ${command.name} failed with error "${e}"`); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
console.error
and no braces, please:
return command.execute(bot, author, channel, message).catch(
e => console.error(`Command ${command.name} failed with "${e}"`)
);
import { get, post } from '../utils'; | ||
import { Commands } from './index'; | ||
|
||
function getCommandByName(name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I see, that's a shame. I wonder why Babel doesn't make this additional property non-enumerable.
const log = bot.getActionLog(author, channel); | ||
if (log.length === 0) { | ||
return Promise.resolve('There is no command to revert'); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A nit here and in a few other places: please insert a blank line after all terminating if
blocks.
const hash = `${user}-${channel}`; | ||
if (!this.actionLog.has(hash)) { | ||
this.actionLog.set(hash, []); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it make sense to move this.actionLog.set(hash, []);
into getActionLog
too and remove the above 4 lines?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
then, every attempt to retrieve values for non-existing key will create it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I know, I thought that maybe that was okay since it's not a common scenario, but on second thought it's probably better to keep it the way you wrote it right now. thanks!
No description provided.