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

log-viewer-webui: Add server implementation for submitting IR extraction jobs and serving IR files; Don't copy unnecessary files when building component. #458

Merged
merged 56 commits into from
Jul 20, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
220aeb8
implement basic functions
wraymo Jun 21, 2024
b6dcb54
fix a bug
wraymo Jun 24, 2024
77cd631
fix a bug
wraymo Jun 24, 2024
8664a31
refactor a bit
wraymo Jun 24, 2024
b932901
add docstring
wraymo Jun 24, 2024
c13d39d
use 127.0.0.1 instead of localhost
wraymo Jun 24, 2024
f017d0b
remove example routes and test
wraymo Jun 24, 2024
4729971
Apply suggestions from code review
kirkrodrigues Jul 7, 2024
7859aba
Merge branch 'refs/heads/main' into log_viewer_server
junhaoliao Jul 11, 2024
15b7459
Add `.env.local` for debug env var overrides.
junhaoliao Jul 11, 2024
0c99ef1
Add .env.local to .gitignore for debug purposes.
junhaoliao Jul 11, 2024
f090aff
Remove .env.local from VCS.
junhaoliao Jul 11, 2024
7a892dc
Add extract-IR job and polling, rework routes and DB interaction.
junhaoliao Jul 16, 2024
eda691c
Add IR_DATA_DIR and LOG_VIEWER_DIR into .env.
junhaoliao Jul 16, 2024
d3fe4a4
Add EOF.
junhaoliao Jul 16, 2024
27b4d0d
Revert wrongly refactored docstring.
junhaoliao Jul 16, 2024
ea337cc
Rename `examples` to `example` in route URLs for consistency.
junhaoliao Jul 16, 2024
ea3210a
Update LOG_VIEWER_DIR default value in .env.
junhaoliao Jul 16, 2024
a9e9cc7
Merge branch 'refs/heads/main' into log_viewer_server
junhaoliao Jul 16, 2024
cf60f36
Specify `IR_DATA_DIR` when launching log-viewer-webui server in start…
junhaoliao Jul 16, 2024
f99a39e
Update package-lock.json.
junhaoliao Jul 16, 2024
a6a543f
Fix failed test cases.
junhaoliao Jul 16, 2024
be5a95f
Refactor server configuration to use settings.json.
junhaoliao Jul 16, 2024
469bf3a
Strip out log-viewer-webui/client deps in clp-package; add settings.j…
junhaoliao Jul 16, 2024
ce6d76a
Clean up component `log-viewer-webui` in CLP package.
junhaoliao Jul 16, 2024
0112dc1
Remove unused environment variables in main.js.
junhaoliao Jul 16, 2024
cf55e8c
Rearrange server initialization code in app.js to ensure fastifyStati…
junhaoliao Jul 16, 2024
8baf2da
lint code.
junhaoliao Jul 16, 2024
fba76c3
Rename task "log-viewer-webui-clients" -> "log-viewer-webui-client".
junhaoliao Jul 19, 2024
1af7e05
Add log-viewer-webui-client checksum to task `package`'s sources.
junhaoliao Jul 19, 2024
8bb6af7
package: Refactor read_and_update_settings_json().
junhaoliao Jul 19, 2024
4cb16ad
package: Add missing PORT envvar setting into start_log_viewer_webui …
junhaoliao Jul 19, 2024
e46f536
log-viewer-webui-server: replace dev dependency ``@babel/plugin-synta…
junhaoliao Jul 19, 2024
c6f3a87
Rename `irDataDir` -> `irFilesDir`.
junhaoliao Jul 19, 2024
094f330
Remove extra line.
junhaoliao Jul 19, 2024
ac01b8d
Make DbManager methods `initMySql()` and `initMongo()` private.
junhaoliao Jul 19, 2024
ae9d86c
Store mysql connection pool instead of connection.
junhaoliao Jul 19, 2024
c125729
Make DbManager member irFilesCollection private.
junhaoliao Jul 19, 2024
36a4f51
Rename `insertExtractIrJob` -> `submitAndWaitForExtractIrJob`.
junhaoliao Jul 19, 2024
9b19d8b
Rename `getExtractedIrMetadata` -> `getExtractedIrFileMetadata`.
junhaoliao Jul 19, 2024
0501a61
Rearrange parseEnvVars return object property order.
junhaoliao Jul 19, 2024
299257f
Move NODE_ENV=test setting from app.test.js to package.json.
junhaoliao Jul 19, 2024
c02c9ee
Remove redundant line.
junhaoliao Jul 19, 2024
f0f05e2
Remove unnecessary export of parseEnvVars.
junhaoliao Jul 19, 2024
06d4f84
Remove unnecessary `await`s on route registrations.
junhaoliao Jul 19, 2024
89a7036
Use shorthand resp.send (return directly) in Fastify route handler.
junhaoliao Jul 19, 2024
56791b5
Add "TODO: add tests for `query` routes." in app.test.js.
junhaoliao Jul 19, 2024
bcb3554
Refactor new read_and_update_settings_json method.
kirkrodrigues Jul 19, 2024
4327c27
Refactor some comments and strings.
kirkrodrigues Jul 19, 2024
1e1a85a
Clean-up previous PR.
kirkrodrigues Jul 19, 2024
85c2954
Nit: Reorder user before pass.
kirkrodrigues Jul 19, 2024
08cce9b
Mark method awaitJobCompletion as private.
junhaoliao Jul 19, 2024
9339a3d
Remove redundant `await` in mysql pool assignment.
junhaoliao Jul 19, 2024
bd96c03
Add webui-log-viewer-server sources to task `package`'s `sources`.
junhaoliao Jul 19, 2024
6a16fd8
Move var G_LOG_VIEWER_WEBUI_SRC_DIR definition lint-tasks.yml -> Task…
junhaoliao Jul 19, 2024
10debf9
Update awaitJobCompletion docstring.
junhaoliao Jul 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
268 changes: 255 additions & 13 deletions components/log-viewer-webui/server/package-lock.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions components/log-viewer-webui/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@
"license": "Apache-2.0",
"type": "module",
"dependencies": {
"@fastify/mysql": "^4.3.0",
"@fastify/mongodb": "^8.0.0",
"@msgpack/msgpack": "^3.0.0-beta2",
kirkrodrigues marked this conversation as resolved.
Show resolved Hide resolved
"dotenv": "^16.4.5",
"fastify": "^4.28.0",
"fastify-plugin": "^4.5.1",
"http-status-codes": "^2.3.0",
"pino-pretty": "^11.2.1"
},
Expand Down
116 changes: 116 additions & 0 deletions components/log-viewer-webui/server/src/DbManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import fastifyPlugin from "fastify-plugin";

import fastifyMysql from "@fastify/mysql";
import fastifyMongo from "@fastify/mongodb";
import msgpack from "@msgpack/msgpack";


junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
/**
* Class representing the database manager.
*/
class DbManager {
/**
* Creates a DbManager.
*
* @param {Object} app - The Fastify application instance
kirkrodrigues marked this conversation as resolved.
Show resolved Hide resolved
* @param {Object} dbConfig - The database configuration
* @param {Object} dbConfig.mysqlConfig - The MySQL configuration
* @param {Object} dbConfig.mongoConfig - The MongoDB configuration
*/
constructor(app, dbConfig) {
this.app = app;
this.initMySql(dbConfig.mysqlConfig);
this.initMongo(dbConfig.mongoConfig);
kirkrodrigues marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Initializes MySQL connection.
*
* @param {Object} config
* @param {string} config.user
* @param {string} config.password
* @param {string} config.host
* @param {number} config.port
* @param {string} config.database
* @param {string} config.queryJobsTableName
*/
initMySql(config) {
this.app.register(fastifyMysql, {
promise: true,
connectionString: `mysql://${config.user}:${config.password}@${config.host}:` +
`${config.port}/${config.database}`,
}).after(async (err) => {
if (err) {
throw err;
}
this.mysqlConnection = await this.app.mysql.getConnection();
this.queryJobsTableName = config.queryJobsTableName;
});
}

/**
* Initializes MongoDB connection.
*
* @param {Object} config
* @param {string} config.host
* @param {number} config.port
* @param {string} config.database
* @param {string} config.statsCollectionName
*/
initMongo(config) {
this.app.register(fastifyMongo, {
forceClose: true,
url: `mongodb://${config.host}:${config.port}/${config.database}`,
}).after(err => {
if (err) {
throw err;
}
this.mongoStatsCollection = this.app.mongo.db.collection(config.statsCollectionName);
});
}

/**
* Inserts a decompression job into MySQL.
*
* @param {Object} jobConfig - The job configuration.
* @returns {Promise<Object>} The result of the insert query or null if an error occurred.
*/
async insertDecompressionJob(jobConfig) {
return await this.mysqlConnection.query(
`INSERT INTO ${this.queryJobsTableName} (id, job_config)
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
VALUES (?, ?)`,
[1,
Buffer.from(msgpack.encode(jobConfig))]
kirkrodrigues marked this conversation as resolved.
Show resolved Hide resolved
);
}

/**
* Retrieves a decompression job from MySQL.
*
* @param {number} jobId - The ID of the job.
* @returns {Promise<Object>} The job configuration.
*/
async getDecompressionJob(jobId) {
const [results] = await this.mysqlConnection.query(
`SELECT job_config
FROM ${this.queryJobsTableName}
WHERE id = ?`,
[jobId],
);

return msgpack.decode(results[0].job_config);
}

/**
* Retrieve statistics from MongoDB.
*
* @returns {Promise<Array>} The array of statistics documents.
*/
async getStats() {
return await this.mongoStatsCollection.find().toArray();
}
}

export default fastifyPlugin(async (app, options) => {
await app.decorate("dbManager", new DbManager(app, options));
});
27 changes: 24 additions & 3 deletions components/log-viewer-webui/server/src/app.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,38 @@
import fastify from "fastify";

import DbManager from "./DbManager.js";
import exampleRoutes from "./routes/examples.js";


/**
* Creates the Fastify app with the given options.
*
* @param {import("fastify").FastifyServerOptions} fastifyOptions
* @param {Object} options - The options for creating the Fastify app.
* @param {import("fastify").FastifyServerOptions} options.fastifyOptions - The Fastify server options.
* @param {string} options.dbPass - The MySQL database password.
* @param {string} options.dbUser - The MySQL database user.
* @return {Promise<import("fastify").FastifyInstance>}
*/
const app = async (fastifyOptions = {}) => {
const server = fastify(fastifyOptions);
const app = async (options = {}) => {
console.log(options)
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
const server = fastify(options.fastifyOptions);
await server.register(exampleRoutes);
await server.register(DbManager, {
mysqlConfig: {
host: "127.0.0.1",
database: "clp-db",
user: options.dbUser,
password: options.dbPass,
port: 3306,
queryJobsTableName: "query_jobs",
},
mongoConfig: {
host: "127.0.0.1",
port: 27017,
database: "clp-search",
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
statsCollectionName: "stats",
},
});

return server;
};
Expand Down
39 changes: 31 additions & 8 deletions components/log-viewer-webui/server/src/app.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,48 @@ import httpStatusCodes from "http-status-codes";
import {test} from "tap";

import app from "./app.js";
import {parseEnvVars} from "./utils.js";


test("Tests the example routes", async (t) => {
kirkrodrigues marked this conversation as resolved.
Show resolved Hide resolved
const server = await app();
const envVars = parseEnvVars();
junhaoliao marked this conversation as resolved.
Show resolved Hide resolved
const server = await app(
{
fastifyOptions: {
logger: false,
},
dbUser: envVars.CLP_DB_USER,
dbPass: envVars.CLP_DB_PASS,
},
);
t.teardown(() => server.close());

let resp = await server.inject({
method: "GET",
url: "/examples/get/Alice",
method: "POST",
url: "/decompression_job",
payload: {
jobId: 1,
status: "pending",
},
});

t.equal(resp.statusCode, httpStatusCodes.OK);
t.match(JSON.parse(resp.body), {msg: String});

resp = await server.inject({
method: "POST",
url: "/examples/post",
payload: {name: "Bob"},
method: "GET",
url: "/decompression_job/1",
});
t.equal(resp.statusCode, httpStatusCodes.OK);
t.match(JSON.parse(resp.body), {
jobId: 1,
status: "pending",
});

resp = await server.inject({
method: "GET",
url: "/stats",
});
t.equal(resp.statusCode, httpStatusCodes.OK);
t.match(JSON.parse(resp.body), {msg: String});
console.log(JSON.parse(resp.body));
t.match(JSON.parse(resp.body), []);
});
39 changes: 8 additions & 31 deletions components/log-viewer-webui/server/src/main.js
Original file line number Diff line number Diff line change
@@ -1,37 +1,9 @@
import dotenv from "dotenv";
import process from "node:process";

import app from "./app.js";
import {parseEnvVars} from "./utils.js";


/**
* Parses environment variables into config values for the application.
*
* @return {{PORT: string, HOST: string}}
* @throws {Error} if any required environment variable is undefined.
*/
const parseEnvVars = () => {
dotenv.config({
path: ".env",
});

const {
HOST, PORT,
} = process.env;
const envVars = {
HOST, PORT,
};

// Check for mandatory environment variables
for (const [key, value] of Object.entries(envVars)) {
if ("undefined" === typeof value) {
throw new Error(`Environment variable ${key} must be defined.`);
}
}

return envVars;
};

/**
* Sets up and runs the server.
*/
Expand All @@ -45,12 +17,17 @@ const main = async () => {
production: true,
test: false,
};

const envVars = parseEnvVars();
const server = await app({
logger: envToLogger[process.env.NODE_ENV] ?? true,
fastifyOptions: {
logger: envToLogger[process.env.NODE_ENV] ?? true
},
dbUser: envVars.CLP_DB_USER,
dbPass: envVars.CLP_DB_PASS,
});

try {
const envVars = parseEnvVars();
await server.listen({host: envVars.HOST, port: Number(envVars.PORT)});
} catch (e) {
server.log.error(e);
Expand Down
16 changes: 12 additions & 4 deletions components/log-viewer-webui/server/src/routes/examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,20 @@
* @return {Promise<void>}
*/
const routes = async (fastify, options) => {
Copy link
Member

Choose a reason for hiding this comment

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

I feel it might be better keeping the examples here. (Those can serve as heart beat / echo routes whenever we need check whether the server is online for debug purposes. )

Can we keep the examples and move the decompression routes into another file like query.js or decompression.js?

fastify.get("/examples/get/:name", async (req, resp) => {
return {msg: `Hello, ${req.params.name}!`};
fastify.get("/decompression_job/:jobId", async (req, resp) => {
const result = await fastify.dbManager.getDecompressionJob(req.params.jobId);
resp.send(result);
});

fastify.post("/examples/post", async (req, resp) => {
return {msg: `Goodbye, ${req.body.name}!`};
fastify.post("/decompression_job", async (req, resp) => {
const result = await fastify.dbManager.insertDecompressionJob(req.body);
Copy link
Member

Choose a reason for hiding this comment

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

Can we validate the parameters before passing it to the db manager? / Can we simply accept a job id from the request body?

resp.send(result);
});

fastify.get("/stats", async (req, resp) => {
const result = await fastify.dbManager.getStats();
console.log(result);
resp.send(result);
});
Copy link
Member

Choose a reason for hiding this comment

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

This was added as an example route in WIP #434 before the Alice / Bob examples were added. Now we have the better example routes, maybe we don't need this route any more.

};

Expand Down
32 changes: 32 additions & 0 deletions components/log-viewer-webui/server/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import dotenv from "dotenv";


/**
* Parses environment variables into config values for the application.
*
* @return {{HOST: string, PORT: string, CLP_DB_USER: string, CLP_DB_PASS: string}}
* @throws {Error} if any required environment variable is undefined.
*/
const parseEnvVars = () => {
dotenv.config({
path: ".env",
});

const {
HOST, PORT, CLP_DB_USER, CLP_DB_PASS
} = process.env;
const envVars = {
HOST, PORT, CLP_DB_USER, CLP_DB_PASS
};

// Check for mandatory environment variables
for (const [key, value] of Object.entries(envVars)) {
if ("undefined" === typeof value) {
throw new Error(`Environment variable ${key} must be defined.`);
}
}

return envVars;
};

export {parseEnvVars};