-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/add workflow #30
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
Merged
Merged
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
f04cb9e
feat(docker): update Temporal setup and add worker configuration
anatolyshipitz 509d906
feat(error-handling): refactor error handling in main script and add …
anatolyshipitz 745a049
fix(eslint): update ESLint configuration and improve test structure
anatolyshipitz f4207fb
refactor(docker): simplify Dockerfile by removing netcat installation
anatolyshipitz 31dfaff
Update dependencies (#29)
anatolyshipitz c930aef
refactor(logging): enhance error handling and logging in main worker
anatolyshipitz 49898bd
refactor(tests): clean up error handling tests in index.test.ts
anatolyshipitz 9b04e97
fix(workflows): update dependency installation method in code quality…
anatolyshipitz c198eb2
feat(dependencies): add new Temporal dependencies and source-map-support
anatolyshipitz 39f1d79
feat(dependencies): update package-lock.json with new and modified de…
anatolyshipitz 7abed8e
feat(worker): enhance worker functionality and error handling
anatolyshipitz f370612
refactor(tests): improve test structure and readability in index.test…
anatolyshipitz e9a7431
Merge branch 'main' into feature/add-workflow
anatolyshipitz 58e4370
refactor(workflows): improve financial report workflow and error hand…
anatolyshipitz 37071df
fix(tests): update report string expectation in weeklyFinancialReport…
anatolyshipitz File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { validationResult } from '../main/src/configs'; | ||
| import {logger} from "../main"; | ||
|
|
||
| export const formatValidationIssues = (issues: { path: (string | number)[]; message: string }[]): string => | ||
| issues | ||
| .map(({ path, message }) => `Missing or invalid environment variable: ${path.join('.') || '(unknown variable)'} (${message})`) | ||
| .join('\n'); | ||
|
|
||
| export function validateEnv() { | ||
| if (!validationResult.success) { | ||
| console.error(formatValidationIssues(validationResult.error.issues)); | ||
| process.exit(1); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Logs a worker error in a consistent format. | ||
| * @param workerName - The name of the workflow | ||
| * @param error - The error object | ||
| */ | ||
| export function logWorkerError(workerName: string, error: unknown) { | ||
| logger.error( | ||
| `Error in ${workerName} workerName: ${error instanceof Error ? error.message : String(error)}`, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Logs a workflow error in a consistent format. | ||
| * @param workflowName - The name of the workflow | ||
| * @param error - The error object | ||
| */ | ||
| export function logWorkflowError(workflowName: string, error: unknown) { | ||
| logger.error( | ||
| `Error in ${workflowName} workflow: ${error instanceof Error ? error.message : String(error)}`, | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,37 +1,38 @@ | ||
| import { describe, expect, it } from 'vitest'; | ||
| import { vi } from 'vitest'; | ||
|
|
||
| import { handleRunError, logger, run } from '../index'; | ||
| import * as utils from '../../../common/utils'; | ||
| import { handleRunError, run } from '../index'; | ||
|
|
||
| vi.mock('@temporalio/worker', () => ({ | ||
| DefaultLogger: class { | ||
| error() {} | ||
| }, | ||
| NativeConnection: { | ||
| connect: vi.fn().mockResolvedValue({ close: vi.fn() }), | ||
| }, | ||
| Worker: { | ||
| create: vi | ||
| .fn() | ||
| .mockResolvedValue({ run: vi.fn().mockResolvedValue(undefined) }), | ||
| }, | ||
| })); | ||
|
|
||
| describe('run', () => { | ||
| it('should return true', async () => { | ||
| await expect(run()).resolves.toBe(true); | ||
| await expect(run()).resolves.toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('handleRunError', () => { | ||
| it('should log error and exit process', () => { | ||
| vi.useFakeTimers(); | ||
| const error = new Error('test error'); | ||
| const loggerErrorSpy = vi | ||
| .spyOn(logger, 'error') | ||
| it('should log the error and throw the error', () => { | ||
| const logSpy = vi | ||
| .spyOn(utils, 'logWorkerError') | ||
| .mockImplementation(() => {}); | ||
| const processExitSpy = vi.spyOn(process, 'exit').mockImplementation(() => { | ||
| throw new Error('exit'); | ||
| }); | ||
| const error = new Error('test error'); | ||
|
|
||
| expect(() => handleRunError(error)).toThrow(error); | ||
| expect(loggerErrorSpy).toHaveBeenCalledWith( | ||
| `Unhandled error in main: ${error.message}`, | ||
| ); | ||
| expect(processExitSpy).not.toHaveBeenCalled(); | ||
| expect(() => { | ||
| vi.runAllTimers(); | ||
| }).toThrow('exit'); | ||
| expect(processExitSpy).toHaveBeenCalledWith(1); | ||
|
|
||
| loggerErrorSpy.mockRestore(); | ||
| processExitSpy.mockRestore(); | ||
| vi.useRealTimers(); | ||
| expect(logSpy).toHaveBeenCalledWith('main', error); | ||
| logSpy.mockRestore(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import { describe, expect, it, vi } from 'vitest'; | ||
|
|
||
| import * as utils from '../../../common/utils'; | ||
| import { weeklyFinancialReportsWorkflow } from '../workflows'; | ||
|
|
||
| describe('weeklyFinancialReportsWorkflow', () => { | ||
| it('should return the report string with default parameters', async () => { | ||
| const result = await weeklyFinancialReportsWorkflow(); | ||
|
|
||
| expect(typeof result).toBe('string'); | ||
| expect(result.length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('should return the report string for a custom period', async () => { | ||
| const result = await weeklyFinancialReportsWorkflow({ | ||
| period: 'Q1 2025', | ||
| }); | ||
|
|
||
| expect(result.startsWith('Weekly Financial Report')).toBe(true); | ||
| expect(result).toContain('Period: Q1 2025'); | ||
| }); | ||
|
|
||
| it('should log and rethrow errors', async () => { | ||
| const logSpy = vi | ||
| .spyOn(utils, 'logWorkflowError') | ||
| .mockImplementation(() => {}); | ||
| const originalToLocaleString = Number.prototype.toLocaleString.bind( | ||
| Number.prototype, | ||
| ); | ||
|
|
||
| Number.prototype.toLocaleString = () => { | ||
| throw new Error('Test error'); | ||
| }; | ||
|
|
||
| await expect(weeklyFinancialReportsWorkflow()).rejects.toThrow( | ||
| 'Test error', | ||
| ); | ||
| expect(logSpy).toHaveBeenCalledWith( | ||
| 'Weekly Financial Reports', | ||
| expect.any(Error), | ||
| ); | ||
|
|
||
| Number.prototype.toLocaleString = originalToLocaleString; | ||
| logSpy.mockRestore(); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| export interface FinancialData { | ||
| period: string; | ||
| contractType: string; | ||
| revenue: number; | ||
| cogs: number; | ||
| margin: number; | ||
| marginality: number; | ||
| effectiveRevenue: number; | ||
| effectiveMargin: number; | ||
| effectiveMarginality: number; | ||
| } | ||
|
|
||
| /** | ||
| * Fetches financial data for a given period from an external source or database. | ||
| * @param period - The period to fetch data for (e.g., 'Q1 2025', 'current') | ||
| */ | ||
| export async function fetchFinancialData( | ||
| period: string = 'current', | ||
| ): Promise<FinancialData> { | ||
| // TODO: Replace this stub with actual data fetching logic (e.g., DB query, API call) | ||
| return { | ||
| period: period, | ||
| contractType: 'T&M', | ||
| revenue: 120000, | ||
| cogs: 80000, | ||
| margin: 40000, | ||
| marginality: 33.3, | ||
| effectiveRevenue: 110000, | ||
| effectiveMargin: 35000, | ||
| effectiveMarginality: 31.8, | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { temporalSchema } from './temporal'; | ||
| import { workerSchema } from './worker'; | ||
|
|
||
| export const validationResult = temporalSchema | ||
| .merge(workerSchema) | ||
| .safeParse(process.env); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { NativeConnectionOptions } from '@temporalio/worker'; | ||
| import { z } from 'zod'; | ||
|
|
||
| const DEFAULT_TEMPORAL_ADDRESS = 'temporal:7233'; | ||
|
|
||
| export const temporalConfig: NativeConnectionOptions = { | ||
| address: process.env.TEMPORAL_ADDRESS || DEFAULT_TEMPORAL_ADDRESS, | ||
| }; | ||
|
|
||
| export const temporalSchema = z.object({ | ||
| TEMPORAL_ADDRESS: z.string().default(DEFAULT_TEMPORAL_ADDRESS), | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { WorkerOptions } from '@temporalio/worker'; | ||
| import path from 'path'; | ||
| import { z } from 'zod'; | ||
|
|
||
| export const workerConfig: WorkerOptions = { | ||
| taskQueue: 'main-queue', | ||
| workflowsPath: | ||
| process.env.WORKFLOWS_PATH || path.join(__dirname, '../workflows'), | ||
| }; | ||
|
|
||
| export const workerSchema = z.object({ | ||
| WORKFLOWS_PATH: z.string().optional(), | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,52 @@ | ||
| import { DefaultLogger } from '@temporalio/worker'; | ||
| import { DefaultLogger, NativeConnection, Worker } from '@temporalio/worker'; | ||
|
|
||
| import { logWorkerError, validateEnv } from '../../common/utils'; | ||
| import { temporalConfig } from './configs/temporal'; | ||
| import { workerConfig } from './configs/worker'; | ||
|
|
||
| export const logger = new DefaultLogger('ERROR'); | ||
|
|
||
| /** | ||
| * Executes the main worker process. | ||
| * @returns {Promise<boolean>} Returns true when the worker completes successfully. | ||
| */ | ||
| export async function run(): Promise<boolean> { | ||
| return true; | ||
| validateEnv(); | ||
|
|
||
| export async function createConnection() { | ||
| return NativeConnection.connect(temporalConfig); | ||
| } | ||
|
|
||
| export async function createWorker(connection: NativeConnection) { | ||
| const workerOptions = { | ||
| ...workerConfig, | ||
| connection, | ||
| }; | ||
|
|
||
| return Worker.create(workerOptions); | ||
| } | ||
|
|
||
| export function handleRunError(err: Error): never { | ||
| logger.error(`Unhandled error in main: ${err.message}`); | ||
| export async function run(): Promise<void> { | ||
| const connection = await createConnection(); | ||
|
|
||
| try { | ||
| const worker = await createWorker(connection); | ||
|
|
||
| await worker.run(); | ||
| } catch (err) { | ||
| handleRunError(err); | ||
| } finally { | ||
| if (connection) { | ||
| await connection.close(); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| export function handleRunError(err: unknown): never { | ||
| logWorkerError('main', err); | ||
| setTimeout(() => process.exit(1), 100); | ||
| throw err; | ||
| } | ||
|
|
||
| run().catch(handleRunError); | ||
| export function mainEntry() { | ||
| if (require.main === module) { | ||
| run().catch(handleRunError); | ||
| } | ||
| } | ||
|
|
||
| mainEntry(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export * from './weeklyFinancialReports'; |
24 changes: 24 additions & 0 deletions
24
workers/main/src/workflows/weeklyFinancialReports/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| import { logWorkflowError } from '../../../../common/utils'; | ||
| import { fetchFinancialData } from '../../activities/fetchFinancialData'; | ||
|
|
||
| export async function weeklyFinancialReportsWorkflow({ | ||
| period = 'current', | ||
| }: { period?: string } = {}): Promise<string> { | ||
| try { | ||
| const reportTitle = 'Weekly Financial Report'; | ||
| const data = await fetchFinancialData(period); | ||
| const report = `Period: ${data.period} | ||
| Contract Type: ${data.contractType} | ||
| Revenue: $${data.revenue.toLocaleString()} | ||
| COGS: $${data.cogs.toLocaleString()} | ||
| Margin: $${data.margin.toLocaleString()} | ||
| Marginality: ${data.marginality}%\n\nEffective Revenue (last 4 months): $${data.effectiveRevenue.toLocaleString()} | ||
| Effective Margin: $${data.effectiveMargin.toLocaleString()} | ||
| Effective Marginality: ${data.effectiveMarginality}%`; | ||
|
|
||
| return `${reportTitle}\n${report}`; | ||
| } catch (error) { | ||
| logWorkflowError('Weekly Financial Reports', error); | ||
| throw error; | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.