fix: retrieve the build org job state

This commit is contained in:
Ahmed Bouhuolia
2025-05-10 22:33:54 +02:00
parent 7506c2f37f
commit a42143a996
8 changed files with 79 additions and 16 deletions

View File

@@ -2,7 +2,6 @@ import { Inject, Injectable } from '@nestjs/common';
import { ERRORS } from '../Bills.constants'; import { ERRORS } from '../Bills.constants';
import { Bill } from '../models/Bill'; import { Bill } from '../models/Bill';
import { ServiceError } from '@/modules/Items/ServiceError'; import { ServiceError } from '@/modules/Items/ServiceError';
import { IItemEntryDTO } from '@/modules/TransactionItemEntry/ItemEntry.types';
import { Item } from '@/modules/Items/models/Item'; import { Item } from '@/modules/Items/models/Item';
import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry'; import { BillPaymentEntry } from '@/modules/BillPayments/models/BillPaymentEntry';
import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost'; import { BillLandedCost } from '@/modules/BillLandedCosts/models/BillLandedCost';

View File

@@ -1,4 +1,10 @@
import { ApiTags, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger'; import {
ApiTags,
ApiOperation,
ApiResponse,
ApiBody,
ApiOkResponse,
} from '@nestjs/swagger';
import { import {
Controller, Controller,
Post, Post,
@@ -9,6 +15,7 @@ import {
Res, Res,
Next, Next,
HttpCode, HttpCode,
Param,
} from '@nestjs/common'; } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express'; import { Request, Response, NextFunction } from 'express';
import { BuildOrganizationService } from './commands/BuildOrganization.service'; import { BuildOrganizationService } from './commands/BuildOrganization.service';
@@ -20,6 +27,7 @@ import { GetCurrentOrganizationService } from './queries/GetCurrentOrganization.
import { UpdateOrganizationService } from './commands/UpdateOrganization.service'; import { UpdateOrganizationService } from './commands/UpdateOrganization.service';
import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard'; import { IgnoreTenantInitializedRoute } from '../Tenancy/EnsureTenantIsInitialized.guard';
import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards'; import { IgnoreTenantSeededRoute } from '../Tenancy/EnsureTenantIsSeeded.guards';
import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service';
@ApiTags('Organization') @ApiTags('Organization')
@Controller('organization') @Controller('organization')
@@ -30,6 +38,7 @@ export class OrganizationController {
private readonly buildOrganizationService: BuildOrganizationService, private readonly buildOrganizationService: BuildOrganizationService,
private readonly getCurrentOrgService: GetCurrentOrganizationService, private readonly getCurrentOrgService: GetCurrentOrganizationService,
private readonly updateOrganizationService: UpdateOrganizationService, private readonly updateOrganizationService: UpdateOrganizationService,
private readonly getBuildOrganizationJobService: GetBuildOrganizationBuildJob,
) {} ) {}
@Post('build') @Post('build')
@@ -51,6 +60,13 @@ export class OrganizationController {
}; };
} }
@Get('build/:buildJobId')
@HttpCode(200)
@ApiOperation({ summary: 'Gets the organization build job details' })
async buildJob(@Param('buildJobId') buildJobId: string) {
return this.getBuildOrganizationJobService.getJobDetails(buildJobId);
}
@Get('current') @Get('current')
@HttpCode(200) @HttpCode(200)
@ApiOperation({ summary: 'Get current organization' }) @ApiOperation({ summary: 'Get current organization' })

View File

@@ -12,6 +12,7 @@ import { TenantDBManagerModule } from '../TenantDBManager/TenantDBManager.module
import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service'; import { OrganizationBaseCurrencyLocking } from './Organization/OrganizationBaseCurrencyLocking.service';
import { SyncSystemUserToTenantService } from './commands/SyncSystemUserToTenant.service'; import { SyncSystemUserToTenantService } from './commands/SyncSystemUserToTenant.service';
import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserToTenant.subscriber'; import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserToTenant.subscriber';
import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob.service';
@Module({ @Module({
providers: [ providers: [
@@ -23,7 +24,8 @@ import { SyncSystemUserToTenantSubscriber } from './subscribers/SyncSystemUserTo
CommandOrganizationValidators, CommandOrganizationValidators,
OrganizationBaseCurrencyLocking, OrganizationBaseCurrencyLocking,
SyncSystemUserToTenantService, SyncSystemUserToTenantService,
SyncSystemUserToTenantSubscriber SyncSystemUserToTenantSubscriber,
GetBuildOrganizationBuildJob
], ],
imports: [ imports: [
BullModule.registerQueue({ name: OrganizationBuildQueue }), BullModule.registerQueue({ name: OrganizationBuildQueue }),

View File

@@ -58,3 +58,9 @@ export const OrganizationBuildQueueJob = 'OrganizationBuildQueueJob';
export interface OrganizationBuildQueueJobPayload extends TenantJobPayload { export interface OrganizationBuildQueueJobPayload extends TenantJobPayload {
buildDto: BuildOrganizationDto; buildDto: BuildOrganizationDto;
} }
export interface BuildOrganizationResult {
delay: number;
processedOn: number;
jobId: string;
}

View File

@@ -1,13 +1,14 @@
import { Queue } from 'bullmq'; import { Queue } from 'bullmq';
import { InjectQueue } from '@nestjs/bullmq'; import { InjectQueue } from '@nestjs/bullmq';
import { Injectable } from '@nestjs/common';
import { import {
BuildOrganizationResult,
IOrganizationBuildEventPayload, IOrganizationBuildEventPayload,
IOrganizationBuiltEventPayload, IOrganizationBuiltEventPayload,
OrganizationBuildQueue, OrganizationBuildQueue,
OrganizationBuildQueueJob, OrganizationBuildQueueJob,
OrganizationBuildQueueJobPayload, OrganizationBuildQueueJobPayload,
} from '../Organization.types'; } from '../Organization.types';
import { Injectable } from '@nestjs/common';
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service'; import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
import { import {
throwIfTenantInitizalized, throwIfTenantInitizalized,
@@ -48,7 +49,7 @@ export class BuildOrganizationService {
await this.tenantsManager.createDatabase(); await this.tenantsManager.createDatabase();
await this.tenantsManager.migrateTenant(); await this.tenantsManager.migrateTenant();
await this.tenantsManager.seedTenant() await this.tenantsManager.seedTenant();
// Throws `onOrganizationBuild` event. // Throws `onOrganizationBuild` event.
await this.eventPublisher.emitAsync(events.organization.build, { await this.eventPublisher.emitAsync(events.organization.build, {
@@ -77,7 +78,7 @@ export class BuildOrganizationService {
*/ */
async buildRunJob( async buildRunJob(
buildDTO: BuildOrganizationDto, buildDTO: BuildOrganizationDto,
): Promise<{ nextRunAt: Date; jobId: string }> { ): Promise<BuildOrganizationResult> {
const tenant = await this.tenancyContext.getTenant(); const tenant = await this.tenancyContext.getTenant();
const systemUser = await this.tenancyContext.getSystemUser(); const systemUser = await this.tenancyContext.getSystemUser();
@@ -106,8 +107,9 @@ export class BuildOrganizationService {
await this.tenantRepository.markAsBuilding(jobMeta.id).findById(tenant.id); await this.tenantRepository.markAsBuilding(jobMeta.id).findById(tenant.id);
return { return {
nextRunAt: jobMeta.data.nextRunAt, delay: jobMeta.delay,
jobId: jobMeta.data.id, processedOn: jobMeta.processedOn,
jobId: jobMeta.id,
}; };
} }

View File

@@ -0,0 +1,37 @@
import { Queue } from 'bullmq';
import { Injectable } from '@nestjs/common';
import { InjectQueue } from '@nestjs/bullmq';
import { OrganizationBuildQueue } from '../Organization.types';
import { ServiceError } from '@/modules/Items/ServiceError';
@Injectable()
export class GetBuildOrganizationBuildJob {
constructor(
@InjectQueue(OrganizationBuildQueue)
private readonly organizationBuildQueue: Queue,
) {}
/**
* Gets the build job details by job ID.
* @param {string} jobId - The ID of the job to retrieve.
* @returns {Promise<any>} - Returns the job details.
*/
async getJobDetails(jobId: string): Promise<any> {
const job = await this.organizationBuildQueue.getJob(jobId);
if (!job) {
throw new ServiceError('Job not found', 'JOB.NOT_FOUND');
}
const state = await job.getState();
return {
id: job.id,
state,
progress: job.progress,
isCompleted: state === 'completed',
isRunning: state === 'active',
isWaiting: state === 'waiting' || state === 'waiting-children',
isFailed: state === 'failed',
};
}
}

View File

@@ -25,7 +25,7 @@ function SetupInitializingForm({
const [isJobDone, setIsJobDone] = React.useState(false); const [isJobDone, setIsJobDone] = React.useState(false);
const { const {
data: { running, queued, failed, completed }, data: { isRunning, isWaiting, isFailed, isCompleted },
isFetching: isJobFetching, isFetching: isJobFetching,
} = useJob(organization?.build_job_id, { } = useJob(organization?.build_job_id, {
refetchInterval: 2000, refetchInterval: 2000,
@@ -33,11 +33,11 @@ function SetupInitializingForm({
}); });
React.useEffect(() => { React.useEffect(() => {
if (completed) { if (isCompleted) {
refetch(); refetch();
setIsJobDone(true); setIsJobDone(true);
} }
}, [refetch, completed, setOrganizationSetupCompleted]); }, [refetch, isCompleted, setOrganizationSetupCompleted]);
React.useEffect(() => { React.useEffect(() => {
if (isSuccess && isJobDone) { if (isSuccess && isJobDone) {
@@ -48,11 +48,11 @@ function SetupInitializingForm({
return ( return (
<div class="setup-initializing-form"> <div class="setup-initializing-form">
{failed ? ( {isFailed ? (
<SetupInitializingFailed /> <SetupInitializingFailed />
) : running || queued || isJobFetching ? ( ) : isRunning || isWaiting || isJobFetching ? (
<SetupInitializingRunning /> <SetupInitializingRunning />
) : completed ? ( ) : isCompleted ? (
<SetupInitializingCompleted /> <SetupInitializingCompleted />
) : ( ) : (
<SetupInitializingFailed /> <SetupInitializingFailed />

View File

@@ -1,4 +1,5 @@
// @ts-nocheck // @ts-nocheck
import { transformToCamelCase } from '@/utils';
import { useRequestQuery } from '../useQueryRequest'; import { useRequestQuery } from '../useQueryRequest';
/** /**
@@ -7,9 +8,9 @@ import { useRequestQuery } from '../useQueryRequest';
export function useJob(jobId, props = {}) { export function useJob(jobId, props = {}) {
return useRequestQuery( return useRequestQuery(
['JOB', jobId], ['JOB', jobId],
{ method: 'get', url: `jobs/${jobId}` }, { method: 'get', url: `organization/build/${jobId}` },
{ {
select: (res) => res.data.job, select: (res) => transformToCamelCase(res.data),
defaultData: {}, defaultData: {},
...props, ...props,
}, },