Skip to content
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

Merged
merged 13 commits into from Oct 19, 2016
Merged

Refactor bot commands #23

merged 13 commits into from Oct 19, 2016

Conversation

zbraniecki
Copy link
Collaborator

No description provided.

@zbraniecki
Copy link
Collaborator Author

@stasm - r?

Copy link
Owner

@stasm stasm left a 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));
Copy link
Owner

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));
Copy link
Owner

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.

Copy link
Collaborator Author

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?

Copy link
Collaborator Author

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).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also enables #10

Copy link
Owner

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, {
Copy link
Owner

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))
Copy link
Owner

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.

Copy link
Collaborator Author

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';
Copy link
Owner

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;
Copy link
Owner

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;
},
Copy link
Owner

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;
}

@zbraniecki
Copy link
Collaborator Author

@stasm moves log to in-memory

Copy link
Owner

@stasm stasm left a 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, {
Copy link
Owner

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;
Copy link
Owner

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));
Copy link
Owner

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?

Copy link
Collaborator Author

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.

Copy link
Owner

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;
})
Copy link
Owner

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);
Copy link
Owner

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}`);

Copy link
Collaborator Author

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(
Copy link
Owner

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);
Copy link
Owner

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);
Copy link
Owner

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
});
Copy link
Owner

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) {
Copy link
Owner

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];

Copy link
Collaborator Author

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.

Copy link
Owner

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.

@zbraniecki
Copy link
Collaborator Author

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}"`);
});
Copy link
Owner

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) {
Copy link
Owner

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');
}
Copy link
Owner

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, []);
}
Copy link
Owner

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?

Copy link
Collaborator Author

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.

Copy link
Owner

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!

@stasm stasm changed the title Bot to cards Refactor bot commands Oct 19, 2016
@stasm stasm merged commit d01ec9c into stasm:master Oct 19, 2016
@stasm
Copy link
Owner

stasm commented Oct 19, 2016

giphy

@stasm stasm modified the milestone: 0.1 Jan 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Development

Successfully merging this pull request may close these issues.

None yet

2 participants