(core) Adding UI for timing API

Summary:
Adding new buttons to control the `timing` API and a way to view the results
using virtual table features.

Test Plan: Added new

Reviewers: georgegevoian

Reviewed By: georgegevoian

Subscribers: paulfitz

Differential Revision: https://phab.getgrist.com/D4252
This commit is contained in:
Jarosław Sadziński
2024-05-21 18:27:06 +02:00
parent 60423edc17
commit a6ffa6096a
29 changed files with 858 additions and 144 deletions

View File

@@ -4261,7 +4261,7 @@ function testDocApi() {
await notFoundCalled.waitAndReset();
// But the working endpoint won't be called more then once.
assert.isFalse(successCalled.called());
successCalled.assertNotCalled();
// Trigger second event.
await doc.addRows("Table1", {
@@ -4273,13 +4273,13 @@ function testDocApi() {
assert.deepEqual(firstRow, 1);
// But the working endpoint won't be called till we reset the queue.
assert.isFalse(successCalled.called());
successCalled.assertNotCalled();
// Now reset the queue.
await clearQueue(docId);
assert.isFalse(successCalled.called());
assert.isFalse(notFoundCalled.called());
successCalled.assertNotCalled();
notFoundCalled.assertNotCalled();
// Prepare for new calls.
successCalled.reset();
@@ -4297,7 +4297,7 @@ function testDocApi() {
// And the situation will be the same, the working endpoint won't be called till we reset the queue, but
// the error endpoint will be called with the third row multiple times.
await notFoundCalled.waitAndReset();
assert.isFalse(successCalled.called());
successCalled.assertNotCalled();
// Cleanup everything, we will now test request timeouts.
await Promise.all(cleanup.map(fn => fn())).finally(() => cleanup.length = 0);
@@ -4319,7 +4319,7 @@ function testDocApi() {
// Long will be started immediately.
await longStarted.waitAndReset();
// But it won't be finished.
assert.isFalse(longFinished.called());
longFinished.assertNotCalled();
// It will be aborted.
controller.abort();
assert.deepEqual(await longFinished.waitAndReset(), [408, 4]);
@@ -4333,7 +4333,7 @@ function testDocApi() {
// abort it till the end of this test.
assert.deepEqual(await successCalled.waitAndReset(), 5);
assert.deepEqual(await longStarted.waitAndReset(), 5);
assert.isFalse(longFinished.called());
longFinished.assertNotCalled();
// Remember this controller for cleanup.
const controller5 = controller;
@@ -4343,8 +4343,8 @@ function testDocApi() {
B: [true],
});
// We are now completely stuck on the 5th row webhook.
assert.isFalse(successCalled.called());
assert.isFalse(longFinished.called());
successCalled.assertNotCalled();
longFinished.assertNotCalled();
// Clear the queue, it will free webhooks requests, but it won't cancel long handler on the external server
// so it is still waiting.
assert.isTrue((await axios.delete(
@@ -4356,8 +4356,8 @@ function testDocApi() {
assert.deepEqual(await longFinished.waitAndReset(), [408, 5]);
// We won't be called for the 6th row at all, as it was stuck and the queue was purged.
assert.isFalse(successCalled.called());
assert.isFalse(longStarted.called());
successCalled.assertNotCalled();
longStarted.assertNotCalled();
// Trigger next event.
await doc.addRows("Table1", {
@@ -4368,7 +4368,7 @@ function testDocApi() {
assert.deepEqual(await successCalled.waitAndReset(), 7);
assert.deepEqual(await longStarted.waitAndReset(), 7);
// But we are stuck again.
assert.isFalse(longFinished.called());
longFinished.assertNotCalled();
// And we can abort current request from 7th row (6th row was skipped).
controller.abort();
assert.deepEqual(await longFinished.waitAndReset(), [408, 7]);
@@ -4411,7 +4411,7 @@ function testDocApi() {
controller.abort();
await longFinished.waitAndReset();
// The second one is not called.
assert.isFalse(successCalled.called());
successCalled.assertNotCalled();
// Triggering next event, we will get only calls to the probe (first webhook).
await doc.addRows("Table1", {
A: [2],
@@ -4438,14 +4438,12 @@ function testDocApi() {
await axios.post(`${serverUrl}/api/docs/${docId}/apply`, [
['UpdateRecord', 'Table1', newRowIds[0], newValues],
], chimpy);
await delay(100);
};
const assertSuccessNotCalled = async () => {
assert.isFalse(successCalled.called());
successCalled.assertNotCalled();
successCalled.reset();
};
const assertSuccessCalled = async () => {
assert.isTrue(successCalled.called());
await successCalled.waitAndReset();
};
@@ -4460,8 +4458,6 @@ function testDocApi() {
B: [true],
C: ['c1']
});
await delay(100);
assert.isTrue(successCalled.called());
await successCalled.waitAndReset();
await modifyColumn({ C: 'c2' });
await assertSuccessNotCalled();

View File

@@ -232,7 +232,8 @@ describe('DocApi2', function() {
// And check that we are still on.
resp = await axios.get(`${homeUrl}/api/docs/${docId}/timing`, chimpy);
assert.equal(resp.status, 200, JSON.stringify(resp.data));
assert.deepEqual(resp.data, {status: 'active', timing: []});
assert.equal(resp.data.status, 'active');
assert.isNotEmpty(resp.data.timing);
});
});
});

View File

@@ -306,7 +306,7 @@ describe('Webhooks-Proxy', function () {
await notFoundCalled.waitAndReset();
// But the working endpoint won't be called more then once.
assert.isFalse(successCalled.called());
successCalled.assertNotCalled();
//Cleanup all
await Promise.all(cleanup.map(fn => fn())).finally(() => cleanup.length = 0);

View File

@@ -1,44 +1,58 @@
import {delay} from "bluebird";
import {delay} from 'bluebird';
import {assert} from 'chai';
/**
* Helper that creates a promise that can be resolved from outside.
*
* @example
* const methodCalled = signal();
* setTimeout(() => methodCalled.emit(), 1000);
* methodCalled.assertNotCalled(); // won't throw as the method hasn't been called yet
* await methodCalled.wait(); // will wait for the method to be called
* await methodCalled.wait(); // can be called multiple times
* methodCalled.reset(); // resets the signal (so that it can be awaited again)
* setTimeout(() => methodCalled.emit(), 3000);
* await methodCalled.wait(); // will fail, as we wait only 2 seconds
*/
export function signal() {
let resolve: null | ((data: any) => void) = null;
let promise: null | Promise<any> = null;
let called = false;
return {
emit(data: any) {
if (!resolve) {
throw new Error("signal.emit() called before signal.reset()");
}
called = true;
resolve(data);
},
async wait() {
if (!promise) {
throw new Error("signal.wait() called before signal.reset()");
}
const proms = Promise.race([promise, delay(2000).then(() => {
throw new Error("signal.wait() timed out");
})]);
return await proms;
},
async waitAndReset() {
try {
return await this.wait();
} finally {
this.reset();
}
},
called() {
return called;
},
reset() {
called = false;
promise = new Promise((res) => {
resolve = res;
});
}
};
let resolve: null | ((data: any) => void) = null;
let promise: null | Promise<any> = null;
let called = false;
return {
emit(data: any) {
if (!resolve) {
throw new Error("signal.emit() called before signal.reset()");
}
called = true;
resolve(data);
},
async wait() {
if (!promise) {
throw new Error("signal.wait() called before signal.reset()");
}
const proms = Promise.race([
promise,
delay(2000).then(() => {
throw new Error("signal.wait() timed out");
}),
]);
return await proms;
},
async waitAndReset() {
try {
return await this.wait();
} finally {
this.reset();
}
},
assertNotCalled() {
assert.isFalse(called);
},
reset() {
called = false;
promise = new Promise((res) => {
resolve = res;
});
},
};
}