Skip to content

Commit

Permalink
Instance id (#342)
Browse files Browse the repository at this point in the history
  • Loading branch information
ludeknovy authored Sep 27, 2024
1 parent bda313f commit fc822e3
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 109 deletions.
1 change: 0 additions & 1 deletion migrations/1694620030174_global-settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,3 @@ exports.up = pgm => {
})

}

11 changes: 11 additions & 0 deletions migrations/1725550538880_global.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/* eslint-disable camelcase */
const { PgLiteral } = require("node-pg-migrate")
exports.up = pgm => {
pgm.createTable({ schema: "jtl", name: "global" }, {
instance: {
type: "uuid",
"default": new PgLiteral("uuid_generate_v4()"),
notNull: true,
},
})
}
7 changes: 7 additions & 0 deletions migrations/1725551629571_instance-id.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

exports.up = async pgm => {
await pgm.db.query({
text: `INSERT INTO jtl.global DEFAULT VALUES;`,
values: [],
})
}
164 changes: 82 additions & 82 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,96 +21,96 @@ const DEFAULT_PORT = 5000
const PORT = process.env.PORT || DEFAULT_PORT

export class App {
app: express.Application
router: Router = new Router()
private server: http.Server
app: express.Application
router: Router = new Router()
private server: http.Server

constructor() {
this.app = express()
this.config()
this.router.getRoutes(this.app)
this.databaseErrorHandler()
this.errorHandler()
}
constructor() {
this.app = express()
this.config()
this.router.getRoutes(this.app)
this.databaseErrorHandler()
this.errorHandler()
}

private config(): void {
this.app.use(bodyParser.json())
this.app.use(bodyParser.urlencoded({ extended: false }))
this.app.use(compression())
this.app.use(expressWinston.logger({
transports: [
new winston.transports.Console(),
],
meta: false,
expressFormat: true,
colorize: false,
}))
this.app.use(helmet())
private config(): void {
this.app.use(bodyParser.json())
this.app.use(bodyParser.urlencoded({ extended: false }))
this.app.use(compression())
this.app.use(expressWinston.logger({
transports: [
new winston.transports.Console(),
],
meta: false,
expressFormat: true,
colorize: false,
}))
this.app.use(helmet())

this.app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*")
res.header("Access-Control-Allow-Methods", "*")
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, x-access-token, Content-Type, Accept")
next()
})
}
this.app.use((req, res, next) => {
res.header("Access-Control-Allow-Origin", "*")
res.header("Access-Control-Allow-Methods", "*")
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, x-access-token, Content-Type, Accept")
next()
})
}

private errorHandler() {
// eslint-disable-next-line no-unused-vars
this.app.use(function (error: Error, req: Request, res: Response, next: NextFunction) {
if (boom.isBoom(error)) {
const { payload: { message } } = error.output
return res.status(error.output.statusCode).json({ message })
}
const errorId = uuidv4()
logger.error(`Unexpected error: ${error}, errorId: ${errorId}`)
AnalyticsEvent.reportUnexpectedError(error)
return res.status(StatusCode.InternalError).json({ message: `Unexpected error occurred: ${errorId}` })
private errorHandler() {
// eslint-disable-next-line no-unused-vars
this.app.use(function (error: Error, req: Request, res: Response, next: NextFunction) {
if (boom.isBoom(error)) {
const { payload: { message } } = error.output
return res.status(error.output.statusCode).json({ message })
}
const errorId = uuidv4()
logger.error(`Unexpected error: ${error}, errorId: ${errorId}`)
AnalyticsEvent.reportUnexpectedError(error)
return res.status(StatusCode.InternalError).json({ message: `Unexpected error occurred: ${errorId}` })

})
}
})
}

private databaseErrorHandler() {
this.app.use(function (error: PgError, req: Request, res: Response, next: NextFunction) {
logger.error(error)
if (error instanceof pgp.errors.QueryResultError) {
return next(boom.notFound())
}
if (error?.code === "ECONNREFUSED") {
return next(boom.serverUnavailable(`Could not connect to the database: ${error.address}:${error.port}`))
}
return next(error)
private databaseErrorHandler() {
this.app.use(function (error: PgError, req: Request, res: Response, next: NextFunction) {
logger.error(error)
if (error instanceof pgp.errors.QueryResultError) {
return next(boom.notFound())
}
if (error?.code === "ECONNREFUSED") {
return next(boom.serverUnavailable(`Could not connect to the database: ${error.address}:${error.port}`))
}
return next(error)

})
}
})
}

listen() {
if (!config.jwtToken || !config.jwtTokenLogin) {
logger.error("Please provide JWT_TOKEN and JWT_TOKEN_LOGIN env vars")
process.exit(1)
listen() {
if (!config.jwtToken || !config.jwtTokenLogin) {
logger.error("Please provide JWT_TOKEN and JWT_TOKEN_LOGIN env vars")
process.exit(1)
}
this.server = this.app.listen(PORT,
() => {
logger.info("Express server listening on port " + PORT)
bree.start().then(() => {

logger.info("Bree scheduler was started")
if (process.env.OPT_OUT_ANALYTICS === "true") {
bree.stop("analytics-report").then(() => {
logger.info("Analytics task was opted-out")
})
} else {
logger.info("By using this app you agree with the use of analytics" +
" in this app to help improve user experience and the overall functionality of the app.")
}
})
})
return this.server
}
this.server = this.app.listen(PORT,
() => {
logger.info("Express server listening on port " + PORT)
bree.start().then(() => {
process.env.ANALYTICS_IDENTIFIER = uuidv4()
logger.info("Bree scheduler was started")
if (process.env.OPT_OUT_ANALYTICS === "true") {
bree.stop("analytics-report").then(() => {
logger.info("Analytics task was opted-out")
})
} else {
logger.info("By using this app you agree with the use of analytics in this app to help improve" +
" user experience and the overall functionality of the app.")
}
})
})
return this.server
}

close() {
return this.server.close(() => {
logger.info("Server closed")
})
}
close() {
return this.server.close(() => {
logger.info("Server closed")
})
}
}
41 changes: 23 additions & 18 deletions src/server/utils/analytics/analytics-event.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { analytics } from "../analytics"

jest.mock("../analytics")


describe("AnalyticEvents", () => {

beforeEach(() => {
Expand All @@ -29,66 +28,72 @@ describe("AnalyticEvents", () => {
})
})
describe("reportProcessingFinished", () => {
it("should not track the event when analytics disabled", function () {
it("should not track the event when analytics disabled", async function () {
process.env.OPT_OUT_ANALYTICS = "true"
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportProcessingFinished()
await AnalyticsEvent.reportProcessingFinished()
expect(trackMock).not.toHaveBeenCalled()

})
it("should track the event only when analytics enabled", function () {
it("should track the event only when analytics enabled", async function () {
process.env.OPT_OUT_ANALYTICS = "false"
jest.spyOn(AnalyticsEvent as any, "getInstanceId").mockResolvedValueOnce("mocked-id")
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportProcessingFinished()
await AnalyticsEvent.reportProcessingFinished()
expect(trackMock).toHaveBeenCalled()
})
})

describe("reportLabelCount", () => {
it("should not track the event when analytics disabled", function () {
it("should not track the event when analytics disabled", async function () {
process.env.OPT_OUT_ANALYTICS = "true"
jest.spyOn(AnalyticsEvent as any, "getInstanceId").mockResolvedValueOnce("mocked-id")
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportDetails(1, 1)
await AnalyticsEvent.reportDetails(1, 1)
expect(trackMock).not.toHaveBeenCalled()

})
it("should track the event only when analytics enabled", function () {
it("should track the event only when analytics enabled", async function () {
process.env.OPT_OUT_ANALYTICS = "false"
jest.spyOn(AnalyticsEvent as any, "getInstanceId").mockResolvedValueOnce("mocked-id")
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportDetails(1, 1)
await AnalyticsEvent.reportDetails(1, 1)
expect(trackMock).toHaveBeenCalled()
})
})

describe("reportProcessingStarted", () => {
it("should not track the event when analytics disabled", function () {
it("should not track the event when analytics disabled", async function () {
process.env.OPT_OUT_ANALYTICS = "true"
jest.spyOn(AnalyticsEvent as any, "getInstanceId").mockResolvedValueOnce("mocked-id")
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportProcessingStarted()
await AnalyticsEvent.reportProcessingStarted()
expect(trackMock).not.toHaveBeenCalled()

})
it("should track the event only when analytics enabled", function () {
it("should track the event only when analytics enabled", async function () {
process.env.OPT_OUT_ANALYTICS = "false"
jest.spyOn(AnalyticsEvent as any, "getInstanceId").mockResolvedValueOnce("mocked-id")
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportProcessingStarted()
await AnalyticsEvent.reportProcessingStarted()
expect(trackMock).toHaveBeenCalled()
})
})
describe("unexpectedError", () => {
it("should not track the event when analytics disabled", function () {
it("should not track the event when analytics disabled", async function () {
process.env.OPT_OUT_ANALYTICS = "true"
jest.spyOn(AnalyticsEvent as any, "getInstanceId").mockResolvedValueOnce("mocked-id")
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportUnexpectedError(Error("test"))
await AnalyticsEvent.reportUnexpectedError(Error("test"))
expect(trackMock).not.toHaveBeenCalled()

})
it("should track the event only when analytics enabled", function () {
it("should track the event only when analytics enabled", async function () {
process.env.OPT_OUT_ANALYTICS = "false"
jest.spyOn(AnalyticsEvent as any, "getInstanceId").mockResolvedValueOnce("mocked-id")
const trackMock = (analytics.track as any).mockResolvedValueOnce(undefined)
AnalyticsEvent.reportUnexpectedError(Error("test"))
await AnalyticsEvent.reportUnexpectedError(Error("test"))
expect(trackMock).toHaveBeenCalled()
})
})

})
41 changes: 33 additions & 8 deletions src/server/utils/analytics/anyltics-event.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,69 @@
import { analytics } from "../analytics"
import { db } from "../../../db/db"
import { logger } from "../../../logger"
import { v4 as uuidv4 } from "uuid"

let INSTANCE_ID = null
const FALLBACK_ID = uuidv4()

export class AnalyticsEvent {

private static async getInstanceId(): Promise<string> {
if (INSTANCE_ID !== null) {
return INSTANCE_ID
}
try {
const result = await db.oneOrNone("SELECT instance FROM jtl.global")
if (result && result.instance) {
INSTANCE_ID = result.instance
return INSTANCE_ID
}
return FALLBACK_ID

} catch(error) {
logger.info("Instance id could not be loaded " + error)
return FALLBACK_ID
}

}

static isAnalyticEnabled(): boolean {
return !(process.env.OPT_OUT_ANALYTICS === "true")
}

static reportProcessingStarted() {
static async reportProcessingStarted() {
if (this.isAnalyticEnabled()) {
analytics.track("reportProcessingStarted", {
// eslint-disable-next-line camelcase
distinct_id: process.env.ANALYTICS_IDENTIFIER,
distinct_id: await this.getInstanceId(),
})
}
}

static reportProcessingFinished() {
static async reportProcessingFinished() {
if (this.isAnalyticEnabled()) {
analytics.track("reportProcessingFinished", {
// eslint-disable-next-line camelcase
distinct_id: process.env.ANALYTICS_IDENTIFIER,
distinct_id: await this.getInstanceId(),
})
}
}

static reportDetails(labelCount, duration) {
static async reportDetails(labelCount, duration) {
if (this.isAnalyticEnabled()) {
analytics.track("reportInformation", {
// eslint-disable-next-line camelcase
distinct_id: process.env.ANALYTICS_IDENTIFIER,
distinct_id: await this.getInstanceId(),
labelCount,
duration,
})
}
}

static reportUnexpectedError(error) {
static async reportUnexpectedError(error) {
if (this.isAnalyticEnabled()) {
analytics.track("unexpectedError", {
distinct_id: process.env.ANALYTICS_IDENTIFIER,
distinct_id: await this.getInstanceId(),
error,
})
}
Expand Down

0 comments on commit fc822e3

Please sign in to comment.