Skip to content

Commit

Permalink
feat(server): automatically migrate outdated statistics
Browse files Browse the repository at this point in the history
fixes #4
  • Loading branch information
patrickhulce committed Nov 11, 2019
1 parent c2dc676 commit 0f4dd6b
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 7 deletions.
22 changes: 20 additions & 2 deletions packages/server/src/api/storage/sql/sql.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ function createUmzug(sequelize, options) {
});
}

/**
* @param {LHCI.ServerCommand.Statistic} statistic
* @return {LHCI.ServerCommand.Statistic}
*/
function normalizeStatistic(statistic) {
return {...statistic, version: Number(statistic.version), value: Number(statistic.value)};
}

/** @typedef {LHCI.ServerCommand.TableAttributes<LHCI.ServerCommand.Project>} ProjectAttrs */
/** @typedef {LHCI.ServerCommand.TableAttributes<LHCI.ServerCommand.Build>} BuildAttrs */
/** @typedef {LHCI.ServerCommand.TableAttributes<LHCI.ServerCommand.Run>} RunAttrs */
Expand Down Expand Up @@ -534,7 +542,7 @@ class SqlStorageMethod {
statistic = await statisticModel.create({...unsavedStatistic, id: uuid.v4()}, {transaction});
}

return clone(statistic);
return normalizeStatistic(clone(statistic));
}

/**
Expand All @@ -545,7 +553,17 @@ class SqlStorageMethod {
async _getStatistics(projectId, buildId) {
const {statisticModel} = this._sql();
const statistics = await this._findAll(statisticModel, {where: {projectId, buildId}, order});
return clone(statistics);
return clone(statistics).map(normalizeStatistic);
}

/**
* @param {string} projectId
* @param {string} buildId
* @return {Promise<void>}
*/
async _invalidateStatistics(projectId, buildId) {
const {statisticModel} = this._sql();
await statisticModel.update({version: 0}, {where: {projectId, buildId}});
}
}

Expand Down
17 changes: 15 additions & 2 deletions packages/server/src/api/storage/storage-method.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,16 @@ class StorageMethod {
throw new Error('Unimplemented');
}

/**
* @param {string} projectId
* @param {string} buildId
* @return {Promise<void>}
*/
// eslint-disable-next-line no-unused-vars
async _invalidateStatistics(projectId, buildId) {
throw new Error('Unimplemented');
}

/**
* @param {StorageMethod} storageMethod
* @param {string} projectId
Expand All @@ -214,7 +224,10 @@ class StorageMethod {
const urls = await storageMethod.getUrls(projectId, buildId);
const statisicDefinitionEntries = Object.entries(statisticDefinitions);
const existingStatistics = await storageMethod._getStatistics(projectId, buildId);
if (existingStatistics.length === urls.length * statisicDefinitionEntries.length) {
if (
existingStatistics.length === urls.length * statisicDefinitionEntries.length &&
existingStatistics.every(stat => stat.version === STATISTIC_VERSION)
) {
return existingStatistics;
}

Expand Down Expand Up @@ -250,7 +263,7 @@ class StorageMethod {
const existing = existingStatistics.find(
s => s.name === name && s.value === value && s.url === url
);
if (existing) return existing;
if (existing && existing.version === STATISTIC_VERSION) return existing;

return storageMethod._createOrUpdateStatistic(
{
Expand Down
3 changes: 2 additions & 1 deletion packages/server/src/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ async function createApp(options) {

/**
* @param {LHCI.ServerCommand.Options} options
* @return {Promise<{port: number, close: () => void}>}
* @return {Promise<{port: number, close: () => void, storageMethod: StorageMethod}>}
*/
async function createServer(options) {
const {app, storageMethod} = await createApp(options);
Expand All @@ -63,6 +63,7 @@ async function createServer(options) {
typeof serverAddress === 'string' || !serverAddress ? options.port : serverAddress.port;

resolve({
storageMethod,
port: listenPort,
close: () => {
server.close();
Expand Down
3 changes: 2 additions & 1 deletion packages/server/test/postgres-server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ describe('postgres server', () => {
};

beforeAll(async () => {
const {port, close} = await runServer({
const {port, close, storageMethod} = await runServer({
logLevel: 'silent',
port: 0,
storage: {
Expand All @@ -36,6 +36,7 @@ describe('postgres server', () => {

state.port = port;
state.closeServer = close;
state.storageMethod = storageMethod;
});

afterAll(() => {
Expand Down
24 changes: 24 additions & 0 deletions packages/server/test/server-test-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,30 @@ function runTests(state) {
},
]);
});

it('should not recompute on every call', async () => {
const stat1 = await client.getStatistics(projectA.id, buildA.id);
const stat2 = await client.getStatistics(projectA.id, buildA.id);

for (const pre of stat1) {
const post = stat2.find(stat => stat.url === pre.url && stat.name === pre.name);
expect(post).toEqual(pre); // ensure that updatedAt has not changed
}
});

it('should invalidate statistics and get updated statistics', async () => {
const preInvalidated = await client.getStatistics(projectA.id, buildA.id);
await state.storageMethod._invalidateStatistics(projectA.id, buildA.id);
const postInvalidated = await client.getStatistics(projectA.id, buildA.id);

for (const pre of preInvalidated) {
const post = postInvalidated.find(stat => stat.url === pre.url && stat.name === pre.name);
expect({...post, updatedAt: ''}).toEqual({...pre, updatedAt: ''});
expect(new Date(post.updatedAt).getTime()).toBeGreaterThan(
new Date(pre.updatedAt).getTime()
);
}
});
});

describe('/:projectId/urls', () => {
Expand Down
3 changes: 2 additions & 1 deletion packages/server/test/sqlite-server.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ describe('sqlite server', () => {
beforeAll(async () => {
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);

const {port, close} = await runServer({
const {port, close, storageMethod} = await runServer({
logLevel: 'silent',
port: 0,
storage: {
Expand All @@ -35,6 +35,7 @@ describe('sqlite server', () => {

state.port = port;
state.closeServer = close;
state.storageMethod = storageMethod;
});

afterAll(() => {
Expand Down
10 changes: 10 additions & 0 deletions packages/utils/src/api-client.js
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,16 @@ class ApiClient {
throw new Error('Unimplemented');
}

/**
* @param {string} projectId
* @param {string} buildId
* @return {Promise<void>}
*/
// eslint-disable-next-line no-unused-vars
async _invalidateStatistics(projectId, buildId) {
throw new Error('Unimplemented');
}

async close() {}
}

Expand Down

0 comments on commit 0f4dd6b

Please sign in to comment.