Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 20 additions & 2 deletions lib/utils/payday.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { WorkspaceDBScheme } from '@hawk.so/types';
import { HOURS_IN_DAY, MINUTES_IN_HOUR, SECONDS_IN_MINUTE, MS_IN_SEC } from './consts';

/**
Expand All @@ -14,7 +15,7 @@ const MILLISECONDS_IN_DAY = HOURS_IN_DAY * MINUTES_IN_HOUR * SECONDS_IN_MINUTE *
* @param paidUntil - paid until date
* @param isDebug - flag for debug purposes
*/
export function daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number {
export function countDaysBeforePayday(date: Date, paidUntil: Date = null, isDebug = false): number {
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);

if (isDebug) {
Expand All @@ -37,7 +38,7 @@ export function daysBeforePayday(date: Date, paidUntil: Date = null, isDebug = f
* @param paidUntil - paid until date
* @param isDebug - flag for debug purposes
*/
export function daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number {
export function countDaysAfterPayday(date: Date, paidUntil: Date = null, isDebug = false): number {
const expectedPayDay = paidUntil ? new Date(paidUntil) : new Date(date);

if (isDebug) {
Expand All @@ -49,4 +50,21 @@ export function daysAfterPayday(date: Date, paidUntil: Date = null, isDebug = fa
const now = new Date().getTime();

return Math.floor((now - expectedPayDay.getTime()) / MILLISECONDS_IN_DAY);
}

/**
* Returns difference between day when workspace was blocked and now in days. Undefined for workspaces blocked before the "blockedDate" implemented.
*
* @param workspace - workspace object
*/
export function countDaysAfterBlock(workspace: WorkspaceDBScheme): number | undefined {
if (!workspace.blockedDate) {
return undefined;
}

const blockedDay = new Date(workspace.blockedDate);

const now = new Date().getTime();

return Math.floor((now - blockedDay.getTime()) / MILLISECONDS_IN_DAY);
}
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@
"run-limiter": "yarn worker hawk-worker-limiter"
},
"dependencies": {
"@babel/parser": "^7.26.9",
"@babel/traverse": "7.26.9",
"@hawk.so/nodejs": "^3.1.1",
"@hawk.so/types": "^0.1.35",
"@hawk.so/types": "^0.2.0",
"@types/amqplib": "^0.8.2",
"@types/jest": "^29.2.3",
"@types/mongodb": "^3.5.15",
Expand All @@ -73,9 +75,7 @@
"typescript": "^4.9.4",
"uuid": "^8.3.0",
"winston": "^3.2.1",
"yup": "^0.28.5",
"@babel/parser": "^7.26.9",
"@babel/traverse": "7.26.9"
"yup": "^0.28.5"
},
"devDependencies": {
"@shelf/jest-mongodb": "^1.2.3",
Expand Down
6 changes: 3 additions & 3 deletions workers/email/scripts/emailOverview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { ObjectId } from 'mongodb';
import * as path from 'path';
import * as dotenv from 'dotenv';
import { HttpStatusCode } from '../../../lib/utils/consts';
import { daysAfterPayday } from '../../../lib/utils/payday';
import { countDaysAfterPayday } from '../../../lib/utils/payday';

/**
* Merge email worker .env and root workers .env
Expand Down Expand Up @@ -148,7 +148,7 @@ class EmailTestServer {
user,
period: 10,
reason: 'error on the payment server side',
daysAfterPayday: await this.calculateDaysAfterPayday(workspace),
daysAfterPayday: countDaysAfterPayday(workspace.lastChargeDate, workspace.paidUntil),
};

try {
Expand Down Expand Up @@ -339,7 +339,7 @@ class EmailTestServer {
return 0;
}

const days = daysAfterPayday(workspace.lastChargeDate, workspace.paidUntil);
const days = countDaysAfterPayday(workspace.lastChargeDate, workspace.paidUntil);

return days > 0 ? days : 0;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<td align="center" style="padding: 15px 0;">
<font color="#dbe6ff" style="font-size: 15px; text-align: center; color: #dbe6ff; letter-spacing: 0.4px;">
<span style="vertical-align: middle; display: inline-block;">
{{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }} без мониторинга
{{ daysAfterBlock }} {{ pluralize_ru(daysAfterBlock, ['день', 'дня', 'дней']) }} без мониторинга
</span>
</font>
</td>
Expand Down
Original file line number Diff line number Diff line change
@@ -1 +1 @@
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }}
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterBlock }} {{ pluralize_ru(daysAfterBlock, ['день', 'дня', 'дней']) }}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterPayday }} {{ pluralize_ru(daysAfterPayday, ['день', 'дня', 'дней']) }}
Требуется действие: мониторинг ошибок в {{ workspace.name }} не работает уже {{ daysAfterBlock }} {{ pluralize_ru(daysAfterBlock, ['день', 'дня', 'дней']) }}

Чтобы снова видеть актуальные события, выберите подходящий тарифный план и продлите подписку в настройках оплаты: {{ host }}/workspace/{{ workspace._id }}/settings/billing

Expand Down
160 changes: 6 additions & 154 deletions workers/javascript/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,8 @@ import { JavaScriptEventWorkerTask } from '../types/javascript-event-worker-task
import { BeautifyBacktracePayload } from '../types/beautify-backtrace-payload';
import HawkCatcher from '@hawk.so/nodejs';
import { BacktraceFrame, CatcherMessagePayload, CatcherMessageType, ErrorsCatcherType, SourceCodeLine, SourceMapDataExtended } from '@hawk.so/types';
import { beautifyUserAgent } from './utils';
import { beautifyUserAgent, getFunctionContext } from './utils';
import { Collection } from 'mongodb';
import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
import { extname } from 'path';
/* eslint-disable-next-line no-unused-vars */
import { memoize } from '../../../lib/memoize';

Expand Down Expand Up @@ -135,6 +132,9 @@ export default class JavascriptEventWorker extends EventWorker {
*/
HawkCatcher.send(error, {
payload: backtrace as unknown as Record<string, never>,
releaseRecord: JSON.stringify(releaseRecord),
backtrace: JSON.stringify(backtrace),
projectId,
});

return backtrace[index];
Expand Down Expand Up @@ -232,13 +232,13 @@ export default class JavascriptEventWorker extends EventWorker {

const originalContent = consumer.sourceContentFor(originalLocation.source);

functionContext = await this.getFunctionContext(
functionContext = await getFunctionContext(
originalContent,
originalLocation.line,
originalLocation.source
) ?? originalLocation.name;
} catch (e) {
HawkCatcher.send(e);
HawkCatcher.send(e, { stackFrame: JSON.stringify(stackFrame) });
this.logger.error('Can\'t get function context');
this.logger.error(e);
}
Expand All @@ -253,103 +253,6 @@ export default class JavascriptEventWorker extends EventWorker {
}) as BacktraceFrame;
}

/**
* Method that is used to parse full function context of the code position
*
* @param sourceCode - content of the source file
* @param line - number of the line from the stack trace
* @param sourcePath - original source path from the source map (used to pick parser plugins)
* @returns {string | null} - string of the function context or null if it could not be parsed
*/
private getFunctionContext(sourceCode: string, line: number, sourcePath?: string): string | null {
let functionName: string | null = null;
let className: string | null = null;
let isAsync = false;

try {
const parserPlugins = this.getBabelParserPluginsForFile(sourcePath);

const ast = parse(sourceCode, {
sourceType: 'module',
plugins: parserPlugins,
});

traverse(ast as any, {
/**
* It is used to get class decorator of the position, it will save class that is related to original position
*
* @param path
*/
ClassDeclaration(path) {
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
console.log(`class declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);

className = path.node.id.name || null;
}
},
/**
* It is used to get class and its method decorator of the position
* It will save class and method, that are related to original position
*
* @param path
*/
ClassMethod(path) {
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
console.log(`class declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);

// Handle different key types
if (path.node.key.type === 'Identifier') {
functionName = path.node.key.name;
}
isAsync = path.node.async;
}
},
/**
* It is used to get function name that is declared out of class
*
* @param path
*/
FunctionDeclaration(path) {
if (path.node.loc && path.node.loc.start.line <= line && path.node.loc.end.line >= line) {
console.log(`function declaration: loc: ${path.node.loc}, line: ${line}, node.start.line: ${path.node.loc.start.line}, node.end.line: ${path.node.loc.end.line}`);

functionName = path.node.id.name || null;
isAsync = path.node.async;
}
},
/**
* It is used to get anonimous function names in function expressions or arrow function expressions
*
* @param path
*/
VariableDeclarator(path) {
if (
path.node.init &&
(path.node.init.type === 'FunctionExpression' || path.node.init.type === 'ArrowFunctionExpression') &&
path.node.loc &&
path.node.loc.start.line <= line &&
path.node.loc.end.line >= line
) {
console.log(`variable declaration: node.type: ${path.node.init.type}, line: ${line}, `);

// Handle different id types
if (path.node.id.type === 'Identifier') {
functionName = path.node.id.name;
}
isAsync = (path.node.init as any).async;
}
},
});
} catch (traverseError) {
console.error(`Failed to parse source code:`);
console.error(traverseError);

HawkCatcher.send(traverseError);
}

return functionName ? `${isAsync ? 'async ' : ''}${className ? `${className}.` : ''}${functionName}` : null;
}

/**
* Downloads source map file from Grid FS
*
Expand Down Expand Up @@ -451,55 +354,4 @@ export default class JavascriptEventWorker extends EventWorker {
this.logger.error(`Error on source-map consumer initialization: ${e}`);
}
}

/**
* Choose babel parser plugins based on source file extension
*
* @param sourcePath - original file path from source map (e.g. "src/App.tsx")
*/
private getBabelParserPluginsForFile(sourcePath?: string): any[] {
const basePlugins: string[] = [
'classProperties',
'decorators',
'optionalChaining',
'nullishCoalescingOperator',
'dynamicImport',
'bigInt',
'topLevelAwait',
];

/**
* Default - use only typescript plugin because it's more stable and less likely will produce errors
*/
let enableTypeScript = true;
let enableJSX = false;

if (sourcePath) {
// remove query/hash if there is any
const cleanPath = sourcePath.split('?')[0].split('#')[0];
const ext = extname(cleanPath).toLowerCase();

const isTs = ext === '.ts' || ext === '.d.ts';
const isTsx = ext === '.tsx';
const isJs = ext === '.js' || ext === '.mjs' || ext === '.cjs';
const isJsx = ext === '.jsx';

enableTypeScript = isTs || isTsx;
// JSX:
// - for .ts/.d.ts — DISABLE
// - for .tsx/.jsx — ENABLE
// - for .js — keep enabled, to not break App.js with JSX
enableJSX = isTsx || isJsx || isJs;
}

if (enableTypeScript) {
basePlugins.push('typescript');
}

if (enableJSX) {
basePlugins.push('jsx');
}

return basePlugins;
}
}
Loading
Loading