From 64f06231cd616ee5fda6765c04a2fadc8f3bb3fb Mon Sep 17 00:00:00 2001 From: Bruce Berrios <58147810+Bruception@users.noreply.github.com> Date: Wed, 16 Mar 2022 11:53:11 -0400 Subject: [PATCH] Add initial client version tracking (#2710) bruception * Add initial client version tracking * Make headers optional * Add client version * Add client version on build * header fix Co-authored-by: Miodec --- backend/api/routes/index.ts | 6 + backend/utils/prometheus.ts | 10 ++ frontend/gulpfile.js | 18 +-- frontend/package-lock.json | 138 ++++++++------------- frontend/package.json | 2 +- frontend/src/scripts/ape/endpoints/psas.ts | 8 +- frontend/src/scripts/ape/index.ts | 1 + frontend/src/scripts/ape/types/ape.d.ts | 1 + frontend/src/scripts/version.ts | 1 + frontend/webpack.config.prod.js | 22 +++- 10 files changed, 104 insertions(+), 103 deletions(-) create mode 100644 frontend/src/scripts/version.ts diff --git a/backend/api/routes/index.ts b/backend/api/routes/index.ts index f0618297a..2feeeef03 100644 --- a/backend/api/routes/index.ts +++ b/backend/api/routes/index.ts @@ -10,6 +10,7 @@ import leaderboards from "./leaderboards"; import addSwaggerMiddlewares from "./swagger"; import { asyncHandler } from "../../middlewares/api-utils"; import { MonkeyResponse } from "../../utils/monkey-response"; +import { recordClientVersion } from "../../utils/prometheus"; import { Application, NextFunction, Response, Router } from "express"; const pathOverride = process.env.API_PATH_OVERRIDE; @@ -46,6 +47,11 @@ function addApiRoutes(app: Application): void { return; } + const clientVersion = req.headers["client-version"]; + if (clientVersion) { + recordClientVersion(clientVersion.toString()); + } + requestsProcessed++; next(); } diff --git a/backend/utils/prometheus.ts b/backend/utils/prometheus.ts index fdf0d812d..17755414e 100644 --- a/backend/utils/prometheus.ts +++ b/backend/utils/prometheus.ts @@ -144,3 +144,13 @@ export function incrementResult( resultDuration.observe(res.testDuration); } + +const clientVersionsCounter = new Counter({ + name: "api_client_versions", + help: "Records frequency of client versions", + labelNames: ["version"], +}); + +export function recordClientVersion(version: string) : void { + clientVersionsCounter.inc({ version }); +} diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index d8574e9b4..09c9e939e 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -1,19 +1,15 @@ -const { task, src, dest, series, watch } = require("gulp"); -// const axios = require("axios"); -const concat = require("gulp-concat"); const del = require("del"); -const vinylPaths = require("vinyl-paths"); -const eslint = require("gulp-eslint-new"); -const sass = require("gulp-sass")(require("dart-sass")); +const concat = require("gulp-concat"); const { webpack } = require("webpack"); +const eslint = require("gulp-eslint-new"); +const vinylPaths = require("vinyl-paths"); +const sass = require("gulp-sass")(require("dart-sass")); +const { task, src, dest, series, watch } = require("gulp"); const webpackDevConfig = require("./webpack.config.dev.js"); const webpackProdConfig = require("./webpack.config.prod.js"); -const ts = require("gulp-typescript"); const JSONValidation = require("./json-validation"); - const eslintConfig = "../.eslintrc.json"; -const tsProject = ts.createProject("tsconfig.json"); task("clean", function () { return src(["./public/"], { allowEmpty: true }).pipe(vinylPaths(del)); @@ -41,10 +37,6 @@ task("copy-src-contents", function () { return src("./src/scripts/**").pipe(dest("./dist/")); }); -task("transpile-ts", function () { - return tsProject.src().pipe(tsProject()).js.pipe(dest("dist")); -}); - task("webpack", async function () { return new Promise((resolve, reject) => { webpack(webpackDevConfig, (err, stats) => { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index febf17c01..a92eb0f67 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -44,10 +44,10 @@ "gulp-concat": "^2.6.1", "gulp-eslint-new": "^1.3.0", "gulp-sass": "^5.0.0", - "gulp-typescript": "^6.0.0-alpha.1", "html2canvas": "^1.4.1", "madge": "5.0.1", "stream-browserify": "^3.0.0", + "string-replace-loader": "3.1.0", "ts-loader": "9.2.6", "tsify": "^5.0.4", "typescript": "^4.5.5", @@ -6643,54 +6643,6 @@ "node": ">=12" } }, - "node_modules/gulp-typescript": { - "version": "6.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", - "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "plugin-error": "^1.0.1", - "source-map": "^0.7.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0", - "vinyl-fs": "^3.0.3" - }, - "engines": { - "node": ">= 8" - }, - "peerDependencies": { - "typescript": "~2.7.1 || >=2.8.0-dev || >=2.9.0-dev || ~3.0.0 || >=3.0.0-dev || >=3.1.0-dev || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.7.0-dev " - } - }, - "node_modules/gulp-typescript/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/gulp-typescript/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/gulp-typescript/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, "node_modules/gulplog": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", @@ -10652,6 +10604,33 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-replace-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz", + "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "peerDependencies": { + "webpack": "^5" + } + }, + "node_modules/string-replace-loader/node_modules/loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -17214,44 +17193,6 @@ "vinyl-sourcemaps-apply": "^0.2.1" } }, - "gulp-typescript": { - "version": "6.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/gulp-typescript/-/gulp-typescript-6.0.0-alpha.1.tgz", - "integrity": "sha512-KoT0TTfjfT7w3JItHkgFH1T/zK4oXWC+a8xxKfniRfVcA0Fa1bKrIhztYelYmb+95RB80OLMBreknYkdwzdi2Q==", - "dev": true, - "requires": { - "ansi-colors": "^4.1.1", - "plugin-error": "^1.0.1", - "source-map": "^0.7.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0", - "vinyl-fs": "^3.0.3" - }, - "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dev": true, - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - } - } - }, "gulplog": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", @@ -20303,6 +20244,29 @@ "safe-buffer": "~5.2.0" } }, + "string-replace-loader": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-replace-loader/-/string-replace-loader-3.1.0.tgz", + "integrity": "sha512-5AOMUZeX5HE/ylKDnEa/KKBqvlnFmRZudSOjVJHxhoJg9QYTwl1rECx7SLR8BBH7tfxb4Rp7EM2XVfQFxIhsbQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", + "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + } + } + }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index b41056169..0aa76e5dc 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -37,10 +37,10 @@ "gulp-concat": "^2.6.1", "gulp-eslint-new": "^1.3.0", "gulp-sass": "^5.0.0", - "gulp-typescript": "^6.0.0-alpha.1", "html2canvas": "^1.4.1", "madge": "5.0.1", "stream-browserify": "^3.0.0", + "string-replace-loader": "3.1.0", "ts-loader": "9.2.6", "tsify": "^5.0.4", "typescript": "^4.5.5", diff --git a/frontend/src/scripts/ape/endpoints/psas.ts b/frontend/src/scripts/ape/endpoints/psas.ts index 2194d03cb..21605fd74 100644 --- a/frontend/src/scripts/ape/endpoints/psas.ts +++ b/frontend/src/scripts/ape/endpoints/psas.ts @@ -1,10 +1,16 @@ +import { CLIENT_VERSION } from "../../version"; + const BASE_PATH = "/psas"; export default function getPsasEndpoints( apeClient: Ape.Client ): Ape.Endpoints["psas"] { async function get(): Ape.EndpointData { - return await apeClient.get(BASE_PATH); + return await apeClient.get(BASE_PATH, { + headers: { + "Client-Version": CLIENT_VERSION, + }, + }); } return { get }; diff --git a/frontend/src/scripts/ape/index.ts b/frontend/src/scripts/ape/index.ts index 69ce59a8d..9de4bed7b 100644 --- a/frontend/src/scripts/ape/index.ts +++ b/frontend/src/scripts/ape/index.ts @@ -20,6 +20,7 @@ async function adaptRequestOptions( params: options.searchQuery, data: options.payload, headers: { + ...options.headers, Accept: "application/json", "Content-Type": "application/json", ...(idToken && { Authorization: `Bearer ${idToken}` }), diff --git a/frontend/src/scripts/ape/types/ape.d.ts b/frontend/src/scripts/ape/types/ape.d.ts index 06a64f5b2..d54261a6b 100644 --- a/frontend/src/scripts/ape/types/ape.d.ts +++ b/frontend/src/scripts/ape/types/ape.d.ts @@ -20,6 +20,7 @@ declare namespace Ape { type MethodTypes = keyof Client; interface RequestOptions { + headers?: Record; searchQuery?: Record; payload?: any; } diff --git a/frontend/src/scripts/version.ts b/frontend/src/scripts/version.ts new file mode 100644 index 000000000..bbccfa6e9 --- /dev/null +++ b/frontend/src/scripts/version.ts @@ -0,0 +1 @@ +export const CLIENT_VERSION = "DEVELOPMENT-CLIENT"; diff --git a/frontend/webpack.config.prod.js b/frontend/webpack.config.prod.js index cf5ff7bb3..889f21e95 100644 --- a/frontend/webpack.config.prod.js +++ b/frontend/webpack.config.prod.js @@ -16,10 +16,30 @@ module.exports = { }, module: { rules: [ + { + test: /version\.ts$/, + loader: "string-replace-loader", + options: { + search: /^export const CLIENT_VERSION =.*/, + replace(_match, _p1, _offset, _string) { + const date = new Date(); + const dateString = [ + date.getFullYear(), + date.getMonth() + 1, + date.getDate(), + date.getHours(), + date.getMinutes(), + date.getSeconds(), + ].join("-"); + return `export const CLIENT_VERSION = "${dateString}";`; + }, + flags: "g", + }, + }, { test: /\.tsx?$/, loader: "ts-loader" }, { test: /\.m?js$/, - exclude: /(node_modules|bower_components)/, + exclude: /(node_modules)/, use: { loader: "babel-loader", options: {