Add support for exporting all of the events based on a type
This is certain to crush the node app for big queries, but we'll see what happens
This commit is contained in:
parent
c9454f641d
commit
5260c44037
28
src/app.ts
28
src/app.ts
|
@ -22,6 +22,9 @@ import services from './services';
|
|||
import { appHooks } from './app.hooks';
|
||||
import channels from './channels';
|
||||
|
||||
import Dashboard from './controllers/dashboard';
|
||||
import Export from './controllers/export';
|
||||
|
||||
const app = express(feathers());
|
||||
const settings = configuration();
|
||||
|
||||
|
@ -64,27 +67,10 @@ app.configure(oauth2(Object.assign(githubSettings, {
|
|||
})));
|
||||
|
||||
app.set('view engine', 'pug');
|
||||
/*
|
||||
* Render the dashboard view with authentication
|
||||
*/
|
||||
app.get('/dashboard',
|
||||
cookieParser(),
|
||||
authentication.express.authenticate('jwt'),
|
||||
(req, res, next) => {
|
||||
let query = Object.assign({
|
||||
$sort: {
|
||||
createdAt: -1,
|
||||
}
|
||||
}, req.query);
|
||||
app.service('events')
|
||||
.find({ query: query })
|
||||
.then(result =>
|
||||
res.render('dashboard', {
|
||||
events: result,
|
||||
user: (req as any).user,
|
||||
query: req.query,
|
||||
}));
|
||||
});
|
||||
|
||||
Dashboard(app);
|
||||
Export(app);
|
||||
|
||||
app.get('/logout',
|
||||
cookieParser(),
|
||||
(req, res, next) => {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* The Dashboard controller is responsible for stiching the /dashboard data
|
||||
* together
|
||||
*/
|
||||
|
||||
import authentication from '@feathersjs/authentication';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
export default (app) => {
|
||||
app.get('/dashboard',
|
||||
cookieParser(),
|
||||
authentication.express.authenticate('jwt'),
|
||||
(req, res, next) => {
|
||||
let query = Object.assign({
|
||||
$sort: {
|
||||
createdAt: -1,
|
||||
}
|
||||
}, req.query);
|
||||
|
||||
app.service('events')
|
||||
.find({ query: query })
|
||||
.then(result =>
|
||||
res.render('dashboard', {
|
||||
events: result,
|
||||
user: (req as any).user,
|
||||
query: req.query,
|
||||
})
|
||||
);
|
||||
});
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* The Export controller is responsible for generating a file download
|
||||
* containing entries matching a specific type
|
||||
*/
|
||||
|
||||
import authentication from '@feathersjs/authentication';
|
||||
import cookieParser from 'cookie-parser';
|
||||
|
||||
export default (app) => {
|
||||
app.get('/export/:type',
|
||||
cookieParser(),
|
||||
authentication.express.authenticate('jwt'),
|
||||
(req, res, next) => {
|
||||
app.service('/events/bulk')
|
||||
.find({ type: req.params.type })
|
||||
.then((result) => {
|
||||
res.setHeader('Content-Disposition', `attachment; filename=${req.params.type}.json`);
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.send(result);
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* The /events/bulk service is largely responsible for streaming responses in
|
||||
* the form of files to clients
|
||||
*/
|
||||
|
||||
import authentication from '@feathersjs/authentication';
|
||||
import { Application, HooksObject, Params, SKIP } from '@feathersjs/feathers';
|
||||
import { QueryTypes } from 'sequelize';
|
||||
|
||||
import logger from '../logger';
|
||||
import db from '../models';
|
||||
import Event from '../models/event';
|
||||
|
||||
export const bulkHooks : HooksObject = {
|
||||
before: {
|
||||
all: [
|
||||
(context) => {
|
||||
/*
|
||||
* Allow skipping for our tests
|
||||
*/
|
||||
if (process.env.NODE_ENV != 'production') {
|
||||
return SKIP;
|
||||
}
|
||||
return context;
|
||||
},
|
||||
authentication.hooks.authenticate(['jwt']),
|
||||
],
|
||||
},
|
||||
after: {},
|
||||
error: {},
|
||||
};
|
||||
|
||||
/**
|
||||
* The Bulk service class intentionally only implements the find method
|
||||
*/
|
||||
export class Bulk {
|
||||
protected readonly app : Application;
|
||||
|
||||
constructor(app : Application) {
|
||||
this.app = app;
|
||||
}
|
||||
|
||||
public async find(params : Params) : Promise<any> {
|
||||
if (!params.type) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
/*
|
||||
* This is clearly stupid. I have no idea how we'll query very large
|
||||
* datasets from PostgreSQL but this at least gets us _everything_
|
||||
*/
|
||||
return db.sequelize.query("SELECT * FROM events WHERE type = :type", {
|
||||
replacements: {
|
||||
type: params.type,
|
||||
},
|
||||
type: QueryTypes.SELECT,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default (app : Application) => {
|
||||
app.use('/events/bulk', new Bulk(app));
|
||||
app.service('events/bulk').hooks(bulkHooks);
|
||||
};
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* The /receive service is responsible receiving the data POSTed from the
|
||||
* The /events service is responsible receiving the data POSTed from the
|
||||
* Jenkins instances and storing it in the database
|
||||
*/
|
||||
|
||||
|
@ -8,6 +8,7 @@ import { Application, HooksObject } from '@feathersjs/feathers';
|
|||
import service from 'feathers-sequelize';
|
||||
import { DataTypes } from 'sequelize';
|
||||
|
||||
import logger from '../logger';
|
||||
import db from '../models';
|
||||
import Event from '../models/event';
|
||||
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import events from './events';
|
||||
import bulk from './bulk';
|
||||
import users from './users';
|
||||
|
||||
export default (app) => {
|
||||
app.configure(bulk);
|
||||
app.configure(events);
|
||||
app.configure(users);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import url from 'url';
|
||||
import request from 'request-promise';
|
||||
|
||||
import app from '../src/app';
|
||||
import { Bulk } from '../src/service/bulk';
|
||||
|
||||
// Offsetting a bit to ensure that we can watch and run at the same time
|
||||
const port = (app.get('port') || 3030) + 10;
|
||||
const getUrl = pathname => url.format({
|
||||
hostname: app.get('host') || 'localhost',
|
||||
protocol: 'http',
|
||||
port,
|
||||
pathname
|
||||
});
|
||||
|
||||
describe('Acceptance tests for /evetns/bulk', () => {
|
||||
beforeEach((done) => {
|
||||
this.server = app.listen(port);
|
||||
this.server.once('listening', () => done());
|
||||
});
|
||||
afterEach(done => this.server.close(done));
|
||||
|
||||
describe('GET on /events/bulk', () => {
|
||||
it('without a type parameter should be empty', () => {
|
||||
return request(getUrl('/events/bulk'), {
|
||||
json: true,
|
||||
resolveWithFullResponse: true,
|
||||
}).then((response) => {
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.body).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@ import url from 'url';
|
|||
import request from 'request-promise';
|
||||
|
||||
import app from '../src/app';
|
||||
import events from '../src/service/events';
|
||||
|
||||
// Offsetting a bit to ensure that we can watch and run at the same time
|
||||
const port = (app.get('port') || 3030) + 10;
|
||||
|
@ -17,10 +18,7 @@ describe('Acceptance tests for /evetns', () => {
|
|||
this.server = app.listen(port);
|
||||
this.server.once('listening', () => done());
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
this.server.close(done);
|
||||
});
|
||||
afterEach(done => this.server.close(done));
|
||||
|
||||
it('responds to GET /events', () => {
|
||||
return request(getUrl('/events'), {
|
||||
|
|
|
@ -20,11 +20,11 @@ html(lang="en")
|
|||
div
|
||||
ul.list-inline
|
||||
li.list-inline-item
|
||||
a(href='/dashboard').
|
||||
a(href='/dashboard', title='Reset the filter query').
|
||||
Reset query
|
||||
if events.total > events.limit
|
||||
li.list-inline-item
|
||||
a(href='?id[$lt]=' + events.data[events.data.length - 1].id).
|
||||
a(href='?id[$lt]=' + events.data[events.data.length - 1].id, title='View the next page of results').
|
||||
Next
|
||||
table.table-dark.table
|
||||
tr
|
||||
|
@ -50,9 +50,14 @@ html(lang="en")
|
|||
td.text-center
|
||||
textarea(cols=80, rows=4, readonly=true).
|
||||
#{JSON.stringify(e.payload)}
|
||||
td.text-center
|
||||
a(href='/events/' + e.id, target='_blank').
|
||||
View Raw
|
||||
td
|
||||
ul.list
|
||||
li.list-item
|
||||
a(href='/events/' + e.id, target='_blank', title='View the raw JSON of this event').
|
||||
View Raw
|
||||
li.list-item
|
||||
a(href='/export/' + e.type, title='Export a .json file of all `' + e.type + '` records').
|
||||
Export all of this type
|
||||
|
||||
// vim: ft=haml
|
||||
|
||||
|
|
Loading…
Reference in New Issue