mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-09 01:17:06 +02:00
parent
f4aba80763
commit
9e649118e5
@ -5,6 +5,7 @@ const NotFoundError = require('../error/notfound-error');
|
|||||||
const COLUMNS = [
|
const COLUMNS = [
|
||||||
'app_name',
|
'app_name',
|
||||||
'created_at',
|
'created_at',
|
||||||
|
'created_by',
|
||||||
'updated_at',
|
'updated_at',
|
||||||
'description',
|
'description',
|
||||||
'strategies',
|
'strategies',
|
||||||
@ -20,6 +21,7 @@ const mapRow = row => ({
|
|||||||
updatedAt: row.updated_at,
|
updatedAt: row.updated_at,
|
||||||
description: row.description,
|
description: row.description,
|
||||||
strategies: row.strategies,
|
strategies: row.strategies,
|
||||||
|
createdBy: row.created_by,
|
||||||
url: row.url,
|
url: row.url,
|
||||||
color: row.color,
|
color: row.color,
|
||||||
icon: row.icon,
|
icon: row.icon,
|
||||||
@ -29,6 +31,8 @@ const remapRow = (input, old = {}) => ({
|
|||||||
app_name: input.appName,
|
app_name: input.appName,
|
||||||
updated_at: input.updatedAt,
|
updated_at: input.updatedAt,
|
||||||
description: input.description || old.description,
|
description: input.description || old.description,
|
||||||
|
created_by: input.createdBy || old.createdBy,
|
||||||
|
announced: input.announced || old.announced || false,
|
||||||
url: input.url || old.url,
|
url: input.url || old.url,
|
||||||
color: input.color || old.color,
|
color: input.color || old.color,
|
||||||
icon: input.icon || old.icon,
|
icon: input.icon || old.icon,
|
||||||
@ -118,6 +122,26 @@ class ClientApplicationsDb {
|
|||||||
? this.getAppsForStrategy(filter.strategyName)
|
? this.getAppsForStrategy(filter.strategyName)
|
||||||
: this.getAll();
|
: this.getAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUnannounced() {
|
||||||
|
const rows = await this.db(TABLE)
|
||||||
|
.select(COLUMNS)
|
||||||
|
.where('announced', false);
|
||||||
|
return rows.map(mapRow);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** *
|
||||||
|
* Updates all rows that have announced = false to announced =true and returns the rows altered
|
||||||
|
* @return {[app]} - Apps that hadn't been announced
|
||||||
|
*/
|
||||||
|
async setUnannouncedToAnnounced() {
|
||||||
|
const rows = await this.db(TABLE)
|
||||||
|
.update({ announced: true })
|
||||||
|
.where('announced', false)
|
||||||
|
.whereNotNull('announced')
|
||||||
|
.returning(COLUMNS);
|
||||||
|
return rows.map(mapRow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = ClientApplicationsDb;
|
module.exports = ClientApplicationsDb;
|
||||||
|
@ -10,6 +10,7 @@ const { clientRegisterSchema } = require('./register-schema');
|
|||||||
const { APPLICATION_CREATED } = require('../../event-type');
|
const { APPLICATION_CREATED } = require('../../event-type');
|
||||||
|
|
||||||
const FIVE_SECONDS = 5 * 1000;
|
const FIVE_SECONDS = 5 * 1000;
|
||||||
|
const FIVE_MINUTES = 5 * 60 * 1000;
|
||||||
|
|
||||||
module.exports = class ClientMetricsService {
|
module.exports = class ClientMetricsService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -21,7 +22,11 @@ module.exports = class ClientMetricsService {
|
|||||||
clientInstanceStore,
|
clientInstanceStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
},
|
},
|
||||||
{ getLogger, bulkInterval = FIVE_SECONDS },
|
{
|
||||||
|
getLogger,
|
||||||
|
bulkInterval = FIVE_SECONDS,
|
||||||
|
announcementInterval: appAnnouncementInterval = FIVE_MINUTES,
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
this.globalCount = 0;
|
this.globalCount = 0;
|
||||||
this.apps = {};
|
this.apps = {};
|
||||||
@ -63,6 +68,7 @@ module.exports = class ClientMetricsService {
|
|||||||
});
|
});
|
||||||
this.seenClients = {};
|
this.seenClients = {};
|
||||||
setInterval(() => this.bulkAdd(), bulkInterval);
|
setInterval(() => this.bulkAdd(), bulkInterval);
|
||||||
|
setInterval(() => this.announceUnannounced(), appAnnouncementInterval);
|
||||||
clientMetricsStore.on('metrics', m => this.addPayload(m));
|
clientMetricsStore.on('metrics', m => this.addPayload(m));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,10 +84,26 @@ module.exports = class ClientMetricsService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async announceUnannounced() {
|
||||||
|
if (this.clientAppStore) {
|
||||||
|
const appsToAnnounce = await this.clientAppStore.setUnannouncedToAnnounced();
|
||||||
|
if (appsToAnnounce.length > 0) {
|
||||||
|
const events = appsToAnnounce.map(app => {
|
||||||
|
return {
|
||||||
|
type: APPLICATION_CREATED,
|
||||||
|
createdBy: app.createdBy,
|
||||||
|
data: app,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
await this.eventStore.batchStore(events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async registerClient(data, clientIp) {
|
async registerClient(data, clientIp) {
|
||||||
const value = await clientRegisterSchema.validateAsync(data);
|
const value = await clientRegisterSchema.validateAsync(data);
|
||||||
value.clientIp = clientIp;
|
value.clientIp = clientIp;
|
||||||
this.logger.info(`${JSON.stringify(data)}`);
|
value.createdBy = clientIp;
|
||||||
this.seenClients[this.clientKey(value)] = value;
|
this.seenClients[this.clientKey(value)] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.up = function(db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE client_applications ADD COLUMN announced boolean DEFAULT false;
|
||||||
|
UPDATE client_applications SET announced = true;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE client_applications DROP COLUMN announced;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports._meta = {
|
||||||
|
version: 1,
|
||||||
|
};
|
@ -0,0 +1,18 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
exports.up = function(db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
ALTER TABLE client_applications ADD COLUMN created_by TEXT;
|
||||||
|
`,
|
||||||
|
cb,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function(db, cb) {
|
||||||
|
db.runSql('ALTER TABLE client_applications DROP COLUMN created_by;', cb);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports._meta = {
|
||||||
|
version: 1,
|
||||||
|
};
|
@ -24,17 +24,20 @@
|
|||||||
"applications": [
|
"applications": [
|
||||||
{
|
{
|
||||||
"appName": "demo-app-1",
|
"appName": "demo-app-1",
|
||||||
"strategies": ["default"]
|
"strategies": ["default"],
|
||||||
|
"announced": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"appName": "demo-app-2",
|
"appName": "demo-app-2",
|
||||||
"strategies": ["default", "extra"],
|
"strategies": ["default", "extra"],
|
||||||
"description": "hello"
|
"description": "hello",
|
||||||
|
"announced": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"appName": "deletable-app",
|
"appName": "deletable-app",
|
||||||
"strategies": ["default"],
|
"strategies": ["default"],
|
||||||
"description": "Some desc"
|
"description": "Some desc",
|
||||||
|
"announced": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"clientInstances": [
|
"clientInstances": [
|
||||||
|
58
src/test/e2e/services/client-metrics-service.e2e.test.js
Normal file
58
src/test/e2e/services/client-metrics-service.e2e.test.js
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
const test = require('ava');
|
||||||
|
const faker = require('faker');
|
||||||
|
const dbInit = require('../helpers/database-init');
|
||||||
|
const getLogger = require('../../fixtures/no-logger');
|
||||||
|
const ClientMetricsService = require('../../../lib/services/client-metrics');
|
||||||
|
const { APPLICATION_CREATED } = require('../../../lib/event-type');
|
||||||
|
|
||||||
|
let stores;
|
||||||
|
let clientMetricsService;
|
||||||
|
|
||||||
|
test.before(async () => {
|
||||||
|
const db = await dbInit('client_metrics_service_serial', getLogger);
|
||||||
|
stores = db.stores;
|
||||||
|
clientMetricsService = new ClientMetricsService(stores, {
|
||||||
|
getLogger,
|
||||||
|
bulkInterval: 500,
|
||||||
|
announcementInterval: 2000,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.after(async () => {
|
||||||
|
await stores.db.destroy();
|
||||||
|
});
|
||||||
|
test.serial('Apps registered should be announced', async t => {
|
||||||
|
t.plan(3);
|
||||||
|
const clientRegistration = {
|
||||||
|
appName: faker.internet.domainName(),
|
||||||
|
instanceId: faker.random.uuid(),
|
||||||
|
strategies: ['default'],
|
||||||
|
started: Date.now(),
|
||||||
|
interval: faker.random.number(),
|
||||||
|
icon: '',
|
||||||
|
description: faker.company.catchPhrase(),
|
||||||
|
color: faker.internet.color(),
|
||||||
|
};
|
||||||
|
const differentClient = {
|
||||||
|
appName: faker.lorem.slug(2),
|
||||||
|
instanceId: faker.random.uuid(),
|
||||||
|
strategies: ['default'],
|
||||||
|
started: Date.now(),
|
||||||
|
interval: faker.random.number(),
|
||||||
|
icon: '',
|
||||||
|
description: faker.company.catchPhrase(),
|
||||||
|
color: faker.internet.color(),
|
||||||
|
};
|
||||||
|
await clientMetricsService.registerClient(clientRegistration, '127.0.0.1');
|
||||||
|
await clientMetricsService.registerClient(differentClient, '127.0.0.1');
|
||||||
|
await new Promise(res => setTimeout(res, 1200));
|
||||||
|
const first = await stores.clientApplicationsStore.getUnannounced();
|
||||||
|
t.is(first.length, 2);
|
||||||
|
await clientMetricsService.registerClient(clientRegistration, '127.0.0.1');
|
||||||
|
await new Promise(res => setTimeout(res, 2000));
|
||||||
|
const second = await stores.clientApplicationsStore.getUnannounced();
|
||||||
|
t.is(second.length, 0);
|
||||||
|
const events = await stores.eventStore.getEvents();
|
||||||
|
const appCreatedEvents = events.filter(e => e.type === APPLICATION_CREATED);
|
||||||
|
t.is(appCreatedEvents.length, 2);
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user