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

[WB-25] Handle installation events #20

Merged
merged 7 commits into from
Oct 14, 2017
Merged

Conversation

Druotic
Copy link
Contributor

@Druotic Druotic commented Oct 2, 2017

Doesn't distinguish between create/delete yet - just tries to always insert. Next PR will delete/update as well.

package.json Outdated
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^3.0.1",
"git-hooks": "^1.1.8",
"nyc": "^11.1.0",
"proxyquire": "^1.8.0",
"sinon": "^2.4.1"
"sinon": "^2.4.1",
"uuid": "^3.1.0"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ugh, forgot to remove this, not used.

package.json Outdated
},
"dependencies": {
"amqplib": "^0.5.1",
"bluebird": "^3.5.0",
"browser-refresh": "^1.7.2",
"fashion-model-defaults": "^1.0.2",
"bunyan": "^1.8.12",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ugh, forgot to remove this, not used.

package.json Outdated
"fashion-model-defaults": "^1.0.2",
"bunyan": "^1.8.12",
"fashion-model-defaults": "^1.1.0",
"github": "^10.1.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not used yet, but will be in the next PR.


userAgent: {
description: 'windbreaker User-Agent to use when making API calls',
default: 'Windbreaker'
Copy link
Contributor Author

@Druotic Druotic Oct 2, 2017

Choose a reason for hiding this comment

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

Technically not used in this PR - will be used in the next PR.

@@ -0,0 +1,26 @@
require('require-self-ref')
Copy link
Contributor Author

@Druotic Druotic Oct 2, 2017

Choose a reason for hiding this comment

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

This whole file is gross but exists due to limitations with knex/migrations. You can't create databases within a migration because it fails on the initial connect attempting to connect to a database that doesn't exist. Instead, we run this script just before the migration to ensure the database exists for the migrations.

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah that sucks. Maybe we should open an issue on knex to see if we can't get it supported.

Copy link
Contributor

Choose a reason for hiding this comment

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

Might be good to move this into another folder, possibly along the lines of src/dao/_migration-helpers. Just to make it obvious that it's a utility that shouldn't be imported.

const FashionKnex = require('windbreaker-service-util/dao/FashionKnex')

// TODO: move this base implementation class to shared utils?
class BaseDao extends FashionKnex {
Copy link
Contributor Author

@Druotic Druotic Oct 2, 2017

Choose a reason for hiding this comment

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

This approach is a little different from the dao pattern in hooks. I needed a way to extend common functionality for each dao/table and opted for this over using createDaoHelper.

Copy link
Contributor

Choose a reason for hiding this comment

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

It might be better to hide the underlying implementation, so that if we ever want to swap/change it in utils, it will be changed everywhere. Thoughts on just overriding the createDaoHelper function?

const createDaoUtil = require('windbreaker-service-util/dao')
const config = require('~/src/config')
const logger = require('~/src/logging').logger(module)

module.exports = function createDaoHelper (options) {
  return createDaoUtil({
    ...options,
    logger,
    knexConfig: config.getKnex()
  })
}

Copy link
Contributor

Choose a reason for hiding this comment

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

But I agree that ultimately this functionality should be overwritten since you're using it in multiple places.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went with this approach because it naturally felt like inheritance - there's some base behavior, and we want to extend it per dao type. Currently, there isn't much common functionality here but thought there might be eventually. I originally extended BaseDao for each specific Dao, but it started to trend toward a singleton pattern and I'm not a huge fan of singletons. It does seem odd to use a BaseDao and not extend it, though. I could be convinced either way.

// to the Installation model
table.integer('id').unsigned().primary()
table.integer('app_id').notNullable()
table.timestamps(true, true) // adds created_at/updated_at columns and default to now
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not actually sure if we'll ever care about these timestamps, but seemed like it'd be good to know create/updated times.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed.

@@ -0,0 +1,20 @@
const startupTasks = require('~/src/startup-tasks')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI - this file doesn't get run as a regular test. Ava ignores all directories named helpers by default. A bad choice imo, but since exclude patterns don't work, may as well take advantage of it for test helpers.

See issue/code:

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I agree that it was a bad decision. Definitely a random "gotcha" that adds little value.

Copy link
Contributor

@austinkelleher austinkelleher left a comment

Choose a reason for hiding this comment

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

Nice work on this. Most of my comments are optional. Just thoughts that I wanted to record as I was reviewing. I think this looks pretty good.

const knex = Knex(knexConfig)

logger.info(`Attempting to create database '${dbName}'`)
module.exports = knex
Copy link
Contributor

Choose a reason for hiding this comment

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

We should consider moving this part of this functionality to util

@@ -0,0 +1,26 @@
require('require-self-ref')
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah that sucks. Maybe we should open an issue on knex to see if we can't get it supported.

return dao.upsert({ id, app_id })
}

exports.findById = async function (id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I realize that this is just copied over from hooks. What do you about if a method is not being overridden in any way, we just reference the original:

exports.findById = dao.findById
exports.close = dao.destroy

I don't feel strongly about this. Just a thought. I may actually prefer it the way that it is because it explicitly shows that the methods are async.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ideally, we would just be extending the BaseDao here and everything would be a passthrough. I'm not sure these extra layers provide much value.

const FashionKnex = require('windbreaker-service-util/dao/FashionKnex')

// TODO: move this base implementation class to shared utils?
class BaseDao extends FashionKnex {
Copy link
Contributor

Choose a reason for hiding this comment

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

It might be better to hide the underlying implementation, so that if we ever want to swap/change it in utils, it will be changed everywhere. Thoughts on just overriding the createDaoHelper function?

const createDaoUtil = require('windbreaker-service-util/dao')
const config = require('~/src/config')
const logger = require('~/src/logging').logger(module)

module.exports = function createDaoHelper (options) {
  return createDaoUtil({
    ...options,
    logger,
    knexConfig: config.getKnex()
  })
}

const FashionKnex = require('windbreaker-service-util/dao/FashionKnex')

// TODO: move this base implementation class to shared utils?
class BaseDao extends FashionKnex {
Copy link
Contributor

Choose a reason for hiding this comment

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

But I agree that ultimately this functionality should be overwritten since you're using it in multiple places.

// to the Installation model
table.integer('id').unsigned().primary()
table.integer('app_id').notNullable()
table.timestamps(true, true) // adds created_at/updated_at columns and default to now
Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed.


return knex
.schema
.createTableIfNotExists(REPOSITORY_TABLE_NAME, (table) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is a good start, but it might be nice if we included more information so that we could do some statistics on the data later on. I think a few more that would be nice to have are:

  • private: Boolean # Whether this repo is private or public (this is subject to change...can we detect that?)
  • topics: String[] # Topics associated with this repo e.g. dom, vdom, front-end, javascript
  • created_at: Date # The original data when the repo was created (I think)
  • language: String # Main programming language of the repo
  • stargazers_count: Int # The number of repo stars at the time the integration was enabled

Is this supposed to serve as a generic table for all source control providers? If so, some of the above may not be applicable to all, but I think it's still worth having for the ones that do support it.

Some additional info regarding the data from GitHub: https://developer.github.com/v3/repos/#list-organization-repositories

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This table is merely meant for the installation:repository (1:many) relationship. Metadata about a repository should be in a different table. The primary key (id) can also be a foreign key into that repository metadata table since it's a 1:1 relationship. If we go with a setup like that, it might be better to call this table something like InstallationRepositories (or snakecased to be consistent)

* @param full_name {String} - owner + repository name e.g. druotic/cool-repo
* @param installation_id {Number} - foreign key id for associated installation
*/
exports.upsert = async function ({ id, name, full_name, installation_id }) {
Copy link
Contributor

Choose a reason for hiding this comment

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

FashionKnex.prototype.upsert already allows you to pass a Model. It might be cleaner to just do that. You may have to change up the way the id is passed though.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For consistency, I was merely following your existing pattern in hooks of providing a passthrough for everything. This suggestion seems like a good candidate for refactoring PRs across the projects to help maintain consistency.

@@ -0,0 +1,20 @@
const startupTasks = require('~/src/startup-tasks')
Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah I agree that it was a bad decision. Definitely a random "gotcha" that adds little value.

const INSTALLATION_TABLE_NAME = Installation.typeName

exports.up = function (knex, Promise) {
logger.info('Attempting to run "up" on "create-installation-table"')
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this an old debug log?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No -I followed the existing pattern here: https://github.com/windbreaker-io/windbreaker-hooks/blob/master/src/dao/migrations/20170714072908_create-webhook-table.js I left it in intentionally for debugging purposes (only runs on startup).

Copy link
Contributor

Choose a reason for hiding this comment

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

Gotcha, it seemed like a pretty similar message to the one below.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, now I see that you're pointing out the fact that there are two log statements, rather than suggesting to remove them entirely. These could be combined, I suppose. A future PR could combine them, but I was just being consistent.

const REPOSITORY_TABLE_NAME = Repository.typeName

exports.up = function (knex, Promise) {
logger.info('Attempting to run "up" on "create-repository-table"')
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here?

const repositories = [{
id: randomNum(),
name: 'three-mocha-puppeteers',
full_name: 'charlieDugong/three-mocha-puppeteers'
Copy link
Contributor

Choose a reason for hiding this comment

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

very funny...


const environment = config.getEnvironment().name().toLowerCase()
let exported = {}
exported[environment] = config.getKnex().clean()
Copy link
Contributor

Choose a reason for hiding this comment

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

minor: why not just do exports[environment] = config.getKnex().clean()?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do - copy/pasta OP.

test.after(async () => {
await startupTasks.stopAll()
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, I like this

package.json Outdated
"transform-object-rest-spread"
]
},
"failFast": true
Copy link
Contributor

Choose a reason for hiding this comment

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

I think I would rather not have this mode enabled. If we ever encounter a test failure, we could potentially miss out on running related tests that would better describe the root of the problem (ex. an integration test fails in a way that isn't obvious, but ends up being easier to debug because of related unit test failures).

Copy link
Contributor Author

@Druotic Druotic Oct 13, 2017

Choose a reason for hiding this comment

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

I generally find that the first failure is usually the relevant failure and everything else tends to be noise and/or spams output that causes the root issue to be harder to debug. It's a minor preference, though. I can just enable it locally only.

@@ -0,0 +1,32 @@
require('require-self-ref')
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this is needed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why not?

Copy link
Contributor

Choose a reason for hiding this comment

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

require-self-ref is pretty much the first thing that is loaded upon starting up the server and is always required in at the start of every ava test file (from package.json). Unless there is something strange that happens during the event handler loading, I don't see why we would need to explicitly import require-self-ref again.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I usually prefer to be explicit as possible so there's less magic, but I'm okay with removing it if this is how it's most commonly used.

@@ -0,0 +1,26 @@
require('require-self-ref')
Copy link
Contributor

Choose a reason for hiding this comment

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

Might be good to move this into another folder, possibly along the lines of src/dao/_migration-helpers. Just to make it obvious that it's a utility that shouldn't be imported.

* @param id {Number} - github repository id
* @param name {String} - name of repository
* @param full_name {String} - owner + repository name e.g. druotic/cool-repo
* @param installation_id {Number} - foreign key id for associated installation
Copy link
Contributor

Choose a reason for hiding this comment

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

Ugghh... not a fan of mixing cases like this. Do you think it's worth normalizing this data in our models? (using fashion model's coerce)

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it's worth discussing how we want our db models to look now so that we don't end up having mixed casing.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you give an example? It seems like coerce is meant for type coercion rather than coercing property names, or the docs certainly don't give any examples.

Ultimately, I don't care at all about how fashion model/typing is used, I just want it to be obvious what the casing of a payload looks like without an excessive number of abstractions.

Copy link
Contributor

Choose a reason for hiding this comment

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

Sorry, was thinking of init not coerce.

Here's an example using it.

const MyModel = require('fashion-model').extend({
  properties: {
    name: String,
    property: String
  },

  init (input) {
    this.setProperty(input.some_property)
  }
})

const model = MyModel.wrap({
  name: 'Jams',
  some_property: 'biebers'
})

Adds some extra cruft so not sure if worth or not (might be more performant to just change the data going in before it is passed into the model). I just really don't like having the mixed casing going on.


test('should pass options through to FashionKnex', function (t) {
const { BaseDao, argsStub } = t.context
new BaseDao({ rin: 'okumura' })
Copy link
Contributor

Choose a reason for hiding this comment

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

heh

Copy link
Contributor

@ctdio ctdio left a comment

Choose a reason for hiding this comment

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

Aside from the whole mixed casing thing, this looks fine to me. I'm cool with that being discussed later on (preferrably sooner rather than later). But for now, I'm okay with merging this so that no one ends up getting blocked.

@Druotic Druotic merged commit bc8cebc into master Oct 14, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants