add log aggregation with HyperDX
This commit is contained in:
parent
0951282f11
commit
62e663d053
File diff suppressed because it is too large
Load Diff
|
|
@ -25,6 +25,7 @@
|
|||
"@apollo/client": "^3.13.8",
|
||||
"@azure/msal-angular": "^3.0.22",
|
||||
"@azure/msal-browser": "^3.19.1",
|
||||
"@hyperdx/browser": "^0.22.0",
|
||||
"@ng-icons/core": "^31.4.0",
|
||||
"@ng-icons/lucide": "^31.4.0",
|
||||
"@ngneat/dialog": "^5.1.1",
|
||||
|
|
|
|||
|
|
@ -2,17 +2,21 @@ import { Component } from '@angular/core';
|
|||
import { RouterOutlet } from '@angular/router';
|
||||
import { AppShellComponent } from './core/components/app-shell/app-shell.component';
|
||||
import { MsalService } from '@azure/msal-angular';
|
||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [AppShellComponent, RouterOutlet],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss'
|
||||
selector: 'app-root',
|
||||
imports: [AppShellComponent, RouterOutlet],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.scss',
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'dashboard';
|
||||
|
||||
constructor(private readonly authService: MsalService) {
|
||||
this.authService.handleRedirectObservable().subscribe();
|
||||
this.authService
|
||||
.handleRedirectObservable()
|
||||
.pipe(takeUntilDestroyed())
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
import { registerInstrumentations } from '@opentelemetry/instrumentation';
|
||||
import {
|
||||
WebTracerProvider,
|
||||
ConsoleSpanExporter,
|
||||
SimpleSpanProcessor,
|
||||
BatchSpanProcessor,
|
||||
ParentBasedSampler,
|
||||
TraceIdRatioBasedSampler,
|
||||
} from '@opentelemetry/sdk-trace-web';
|
||||
import { getWebAutoInstrumentations } from '@opentelemetry/auto-instrumentations-web';
|
||||
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import { B3Propagator } from '@opentelemetry/propagator-b3';
|
||||
import {
|
||||
ATTR_SERVICE_NAME,
|
||||
ATTR_SERVICE_VERSION,
|
||||
} from '@opentelemetry/semantic-conventions';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
const resource = Resource.default().merge(
|
||||
new Resource({
|
||||
[ATTR_SERVICE_NAME]: environment.production ? 'avicenna.dashboard.prod' : 'avicenna.dashboard',
|
||||
// [ATTR_SERVICE_NAMESPACE]: 'avicenna',
|
||||
[ATTR_SERVICE_VERSION]: '1.0',
|
||||
environment: environment.production ? 'production' : 'staging'
|
||||
})
|
||||
);
|
||||
|
||||
const provider = new WebTracerProvider({
|
||||
resource,
|
||||
sampler: new ParentBasedSampler({
|
||||
root: new TraceIdRatioBasedSampler(0.1),
|
||||
}),
|
||||
spanProcessors: [
|
||||
new SimpleSpanProcessor(new ConsoleSpanExporter()),
|
||||
new BatchSpanProcessor(
|
||||
new OTLPTraceExporter({
|
||||
url: 'https://collector.genius.ceo/v1/traces',
|
||||
}),
|
||||
{
|
||||
maxQueueSize: 100,
|
||||
maxExportBatchSize: 10,
|
||||
scheduledDelayMillis: 500,
|
||||
}
|
||||
),
|
||||
],
|
||||
});
|
||||
|
||||
provider.register({
|
||||
propagator: new B3Propagator(),
|
||||
});
|
||||
|
||||
registerInstrumentations({
|
||||
instrumentations: [
|
||||
getWebAutoInstrumentations({
|
||||
'@opentelemetry/instrumentation-document-load': {},
|
||||
'@opentelemetry/instrumentation-user-interaction': {},
|
||||
'@opentelemetry/instrumentation-fetch': {
|
||||
propagateTraceHeaderCorsUrls: /.+/,
|
||||
},
|
||||
'@opentelemetry/instrumentation-xml-http-request': {
|
||||
propagateTraceHeaderCorsUrls: /.+/,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
@ -115,11 +115,7 @@ export class DayviewComponent {
|
|||
this.selectedDate.value ?? new Date(),
|
||||
days
|
||||
);
|
||||
if (isSunday(nextSelectedDate)) {
|
||||
this.selectedDate.setValue(addDays(nextSelectedDate, days));
|
||||
} else {
|
||||
this.selectedDate.setValue(nextSelectedDate);
|
||||
}
|
||||
this.selectedDate.setValue(nextSelectedDate);
|
||||
}
|
||||
|
||||
changeSelectedType(event: MatChipListboxChange) {
|
||||
|
|
|
|||
|
|
@ -2,9 +2,18 @@
|
|||
|
||||
import '@angular/common/locales/global/de';
|
||||
import { bootstrapApplication } from '@angular/platform-browser';
|
||||
import HyperDX from '@hyperdx/browser';
|
||||
import { AppComponent } from './app/app.component';
|
||||
import { appConfig } from './app/app.config';
|
||||
import './app/core/telemetry/instrument';
|
||||
|
||||
HyperDX.init({
|
||||
url: 'https://otlp.avicenna.hamburg',
|
||||
apiKey: '71addf09-bcd2-4e83-8158-97458402137b',
|
||||
service: 'avicenna-dashboard',
|
||||
tracePropagationTargets: [/api/i, /graphql/i],
|
||||
consoleCapture: true,
|
||||
advancedNetworkCapture: true
|
||||
});
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig).catch((err) =>
|
||||
console.error(err)
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ WORKDIR /app
|
|||
|
||||
COPY --from=builder /app/builder/node_modules ./node_modules
|
||||
COPY --from=builder /app/builder/prisma ./prisma
|
||||
COPY --from=builder /app/builder/dist .
|
||||
COPY --from=builder /app/builder/dist ./dist
|
||||
COPY --from=builder /app/builder/docker-entrypoint.sh .
|
||||
|
||||
ENTRYPOINT [ "./docker-entrypoint.sh" ]
|
||||
|
||||
CMD ["node", "./src/main.js" ]
|
||||
CMD ["node", "./dist/src/main.js" ]
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -26,6 +26,8 @@
|
|||
"dependencies": {
|
||||
"@apollo/server": "^4.12.0",
|
||||
"@faker-js/faker": "^9.6.0",
|
||||
"@hyperdx/node-logger": "^0.4.0",
|
||||
"@hyperdx/node-opentelemetry": "^0.10.2",
|
||||
"@nestjs-modules/mailer": "^2.0.2",
|
||||
"@nestjs/apollo": "^13.0.4",
|
||||
"@nestjs/axios": "^4.0.0",
|
||||
|
|
@ -40,6 +42,7 @@
|
|||
"@opentelemetry/api": "^1.9.0",
|
||||
"@opentelemetry/auto-instrumentations-node": "^0.60.0",
|
||||
"@opentelemetry/exporter-trace-otlp-http": "^0.202.0",
|
||||
"@opentelemetry/instrumentation-runtime-node": "^0.22.0",
|
||||
"@opentelemetry/sdk-node": "^0.202.0",
|
||||
"@prisma/client": "^6.6.0",
|
||||
"bullmq": "^5.56.0",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { Controller, Get, Logger, Query } from '@nestjs/common';
|
||||
import { endOfDay, startOfDay } from 'date-fns';
|
||||
import { PrismaService } from './core/database/prisma.service';
|
||||
import { AnomaliesService } from './modules/feat-anomalies/anomalies.service';
|
||||
import { CalculationService } from './modules/feat-calculation/calculation.service';
|
||||
|
|
@ -7,9 +8,8 @@ import { TicketService } from './modules/feat-tickets/tickets.service';
|
|||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
private logger = new Logger(AppController.name);
|
||||
|
||||
constructor(
|
||||
private readonly logger: Logger,
|
||||
private readonly importer: ImporterService,
|
||||
private readonly anomaliesService: AnomaliesService,
|
||||
private readonly ticketService: TicketService,
|
||||
|
|
@ -30,6 +30,7 @@ export class AppController {
|
|||
start: new Date(from),
|
||||
end: new Date(to),
|
||||
};
|
||||
|
||||
return this.importer.runImportForRange(interval);
|
||||
}
|
||||
|
||||
|
|
@ -41,8 +42,8 @@ export class AppController {
|
|||
};
|
||||
}
|
||||
const interval = {
|
||||
start: new Date(from),
|
||||
end: new Date(to),
|
||||
start: startOfDay(new Date(from)),
|
||||
end: endOfDay(new Date(to)),
|
||||
};
|
||||
return this.ticketService.createTickets(interval);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ type AttendanceRegistration {
|
|||
dateTime: DateTime!
|
||||
employeeId: Int!
|
||||
event: String!
|
||||
personnelNumber: String!
|
||||
personnelNumber: String
|
||||
}
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,25 @@
|
|||
import { Injectable, NestMiddleware, Logger } from '@nestjs/common';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
@Injectable()
|
||||
export class RequestLoggerMiddleware implements NestMiddleware {
|
||||
private logger = new Logger('HTTP');
|
||||
|
||||
use(request: Request, response: Response, next: NextFunction): void {
|
||||
const { ip, method, originalUrl } = request;
|
||||
const userAgent = request.get('user-agent') || '';
|
||||
const start = Date.now();
|
||||
|
||||
response.on('finish', () => {
|
||||
const { statusCode } = response;
|
||||
const contentLength = response.get('content-length');
|
||||
const duration = Date.now() - start;
|
||||
|
||||
this.logger.log(
|
||||
`${method} ${originalUrl} ${statusCode} ${contentLength} - ${userAgent} ${ip} +${duration}ms`
|
||||
);
|
||||
});
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
import * as HyperDX from '@hyperdx/node-opentelemetry';
|
||||
import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node';
|
||||
|
||||
|
||||
HyperDX.init({
|
||||
apiKey: '71addf09-bcd2-4e83-8158-97458402137b',
|
||||
service: 'avicenna-data-connector',
|
||||
|
||||
instrumentations: {
|
||||
'@opentelemetry/instrumentation-http': { enabled: true },
|
||||
'@opentelemetry/instrumentation-express': { enabled: true },
|
||||
'@opentelemetry/instrumentation-nestjs-core': { enabled: true },
|
||||
'@opentelemetry/instrumentation-graphql': { enabled: true },
|
||||
'@opentelemetry/instrumentation-pg': { enabled: true },
|
||||
},
|
||||
additionalInstrumentations: [
|
||||
//@ts-ignore
|
||||
new RuntimeNodeInstrumentation({
|
||||
monitoringPrecision: 5000,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
|
@ -1,16 +1,23 @@
|
|||
// const tracer = require('./tracer');
|
||||
import tracer from './tracer'
|
||||
// import tracer from './tracer'
|
||||
import { HyperDXNestLoggerModule } from '@hyperdx/node-logger';
|
||||
import * as HyperDX from '@hyperdx/node-opentelemetry';
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { ConsoleLogger } from '@nestjs/common';
|
||||
import './instrumentation';
|
||||
|
||||
async function bootstrap() {
|
||||
await tracer.start();
|
||||
|
||||
const logger = HyperDXNestLoggerModule.createLogger({
|
||||
apiKey: '71addf09-bcd2-4e83-8158-97458402137b',
|
||||
service: 'avicenna-data-connector',
|
||||
maxLevel: 'info',
|
||||
});
|
||||
const app = await NestFactory.create(AppModule, {
|
||||
logger: new ConsoleLogger({ json: true }),
|
||||
logger: logger,
|
||||
});
|
||||
app.enableCors();
|
||||
HyperDX.setupExpressErrorHandler(app);
|
||||
|
||||
await app.listen(3000);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export class PlannedTimeObjectType implements PlannedTime {
|
|||
pause: number;
|
||||
@Field(() => Int)
|
||||
duration: number;
|
||||
@Field({nullable: true})
|
||||
@Field({ nullable: true })
|
||||
note: string;
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +83,7 @@ export class EmployeeObjectType implements Employee {
|
|||
export class AttendanceRegistrationObjectType
|
||||
implements AttendanceRegistration
|
||||
{
|
||||
@Field()
|
||||
@Field({ nullable: true })
|
||||
personnelNumber: string;
|
||||
@Field(() => Int)
|
||||
attendanceRegistrationId: number;
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ export class EmployeesLoggedInDetector extends AnomalyDetector {
|
|||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
private validateEmployee(employee: Employee | undefined) {
|
||||
private validateEmployee(employee: Employee | undefined): employee is Employee {
|
||||
if (!employee) {
|
||||
this.logger.warn(`Employee does not exist`);
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -14,68 +14,6 @@ type TourEb = ExpressionBuilder<DB, 'Tour'>;
|
|||
const PAUSE_PATIENT_ID = 'b7424477-a4b2-42f7-b7e7-a4bef24f6979';
|
||||
const FEIERABEND_PATIENT_ID = '3554e1a9-9c07-45e5-b53c-692a77e4fa9a';
|
||||
|
||||
function onlyRealTours(): Prisma.TourWhereInput[] {
|
||||
return [
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
carName: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
carName: {
|
||||
not: 'Storno',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Löschen',
|
||||
},
|
||||
},
|
||||
{
|
||||
carName: {
|
||||
not: 'Abgabe',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
check: {
|
||||
not: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
deletedAt: null,
|
||||
},
|
||||
{
|
||||
OR: [
|
||||
{
|
||||
patientId: null,
|
||||
},
|
||||
{
|
||||
AND: [
|
||||
{
|
||||
patientId: {
|
||||
// Pause
|
||||
not: PAUSE_PATIENT_ID,
|
||||
},
|
||||
},
|
||||
{
|
||||
patientId: {
|
||||
// Feierabend
|
||||
not: FEIERABEND_PATIENT_ID,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function onlyRealToursKysely(eb: TourEb) {
|
||||
return eb.and([
|
||||
eb.or([
|
||||
|
|
|
|||
|
|
@ -1,20 +1,20 @@
|
|||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { MailModule } from 'src/core/mail/mail.module';
|
||||
import { MAILING_QUEUE } from 'src/core/mail/mail.tokens';
|
||||
import { TOURS_QUEUE } from 'src/modules/feat-business-objects/entities/tour/tour.tokens';
|
||||
import { FeatBusinessObjectsModule } from 'src/modules/feat-business-objects/feat-business-objects.module';
|
||||
import { DataAccessExtractorsModule } from '../feat-extractors/data-access-extractors.module';
|
||||
import { DataAccessLoadersModule } from '../feat-loaders/data-access-loaders.module';
|
||||
import { DataAccessTransformersModule } from '../feat-transformers/data-access-transformers.module';
|
||||
import { ImporterService } from './importer.service';
|
||||
import { MailConsumer } from './processors/mail.consumer';
|
||||
import { SchichtplanungConsumer } from './processors/schichtplanung.consumer';
|
||||
import { TourFileUploadConsumer } from './processors/tour-file-upload.consumer';
|
||||
import { ToursConsumer } from './processors/tours.consumer';
|
||||
import { SchichtplanungSynchronizer } from './producer/schichtplanung';
|
||||
import { TourUpdateChecker } from './producer/tour-update';
|
||||
import { SCHICHTPLANUNG_QUEUE } from './tokens';
|
||||
import { TourFileUploadConsumer } from './processors/tour-file-upload.consumer';
|
||||
import { MAILING_QUEUE } from 'src/core/mail/mail.tokens';
|
||||
import { MailConsumer } from './processors/mail.consumer';
|
||||
import { MailModule } from 'src/core/mail/mail.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
|
@ -34,7 +34,7 @@ import { MailModule } from 'src/core/mail/mail.module';
|
|||
SchichtplanungSynchronizer,
|
||||
SchichtplanungConsumer,
|
||||
TourFileUploadConsumer,
|
||||
MailConsumer
|
||||
MailConsumer,
|
||||
],
|
||||
exports: [ImporterService],
|
||||
})
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { randomUUID } from 'crypto';
|
|||
import { Interval } from 'date-fns';
|
||||
import { Observable, combineLatest, from, map, switchMap, tap } from 'rxjs';
|
||||
|
||||
import { DyflexisUserAccessExtractorsService } from '../feat-extractors/dyflexis-user-access-extractors.service';
|
||||
import { ExtractorsService } from '../feat-extractors/extractors.service';
|
||||
import { LoadersService } from '../feat-loaders/loaders.service';
|
||||
import { TransformersService } from '../feat-transformers/transformers.service';
|
||||
|
|
@ -11,9 +12,8 @@ import { ETLEventTypes } from './events/event-types.enum';
|
|||
import { ExtractionDoneEvent } from './events/extraction-done.event';
|
||||
import { LoadingDoneEvent } from './events/loading-done.event';
|
||||
import { TransformationDoneEvent } from './events/transformation-done.event';
|
||||
import { TourUpdateChecker } from './producer/tour-update';
|
||||
import { SchichtplanungSynchronizer } from './producer/schichtplanung';
|
||||
import { DyflexisUserAccessExtractorsService } from '../feat-extractors/dyflexis-user-access-extractors.service';
|
||||
import { TourUpdateChecker } from './producer/tour-update';
|
||||
|
||||
@Injectable()
|
||||
export class ImporterService {
|
||||
|
|
@ -25,11 +25,11 @@ export class ImporterService {
|
|||
private readonly eventEmitter: EventEmitter2,
|
||||
private readonly tourUpdateChecker: TourUpdateChecker,
|
||||
private readonly schichtplanungSynchronizer: SchichtplanungSynchronizer,
|
||||
private readonly dyflexisUserAccessService: DyflexisUserAccessExtractorsService
|
||||
private readonly dyflexisUserAccessService: DyflexisUserAccessExtractorsService,
|
||||
) {}
|
||||
|
||||
runDyflexisUserAccessTest() {
|
||||
return this.dyflexisUserAccessService.loadData()
|
||||
return this.dyflexisUserAccessService.loadData();
|
||||
}
|
||||
|
||||
runTourUpdateCheck(interval: Interval) {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
import { InjectQueue } from '@nestjs/bullmq';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Queue } from 'bullmq';
|
||||
import { addMinutes, endOfDay, getMonth, getYear, isSameDay, startOfDay } from 'date-fns';
|
||||
import {
|
||||
lastValueFrom
|
||||
} from 'rxjs';
|
||||
addMinutes,
|
||||
endOfDay,
|
||||
getMonth,
|
||||
getYear,
|
||||
isSameDay,
|
||||
startOfDay,
|
||||
} from 'date-fns';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { EmployeeService } from 'src/modules/feat-business-objects/entities/employee/employee.service';
|
||||
import { DispoLiveApiExtractorsService } from '../../feat-extractors/dispo-live-api-extractors.service';
|
||||
import { DyflexisExtractorsService } from '../../feat-extractors/dyflexis-extractors.service';
|
||||
|
|
@ -17,7 +22,7 @@ import { SCHICHTPLANUNG_QUEUE } from '../tokens';
|
|||
|
||||
@Injectable()
|
||||
export class SchichtplanungSynchronizer {
|
||||
private logger = new Logger(SchichtplanungSynchronizer.name)
|
||||
private logger = new Logger(SchichtplanungSynchronizer.name);
|
||||
|
||||
constructor(
|
||||
private readonly dlApiService: DispoLiveApiExtractorsService,
|
||||
|
|
@ -42,7 +47,9 @@ export class SchichtplanungSynchronizer {
|
|||
.filter(({ user_id }) => user_id != '0')
|
||||
.filter(
|
||||
({ department_name }) =>
|
||||
department_name !== 'Büro' && department_name !== 'Leitstelle',
|
||||
department_name !== 'Büro' &&
|
||||
department_name !== 'Leitstelle' &&
|
||||
department_name !== 'Fortbildung',
|
||||
)
|
||||
.filter(({ start_date }) => isSameDay(new Date(start_date), day));
|
||||
|
||||
|
|
@ -63,7 +70,9 @@ export class SchichtplanungSynchronizer {
|
|||
plannedTime.user_id,
|
||||
);
|
||||
if (!employee) {
|
||||
this.logger.error(`couldn\'t find employee with id ${plannedTime.user_id}`);
|
||||
this.logger.error(
|
||||
`couldn\'t find employee with id ${plannedTime.user_id}`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -71,7 +80,7 @@ export class SchichtplanungSynchronizer {
|
|||
this.isCarNameForDepartment(plannedTime.department_name, kfzNr),
|
||||
);
|
||||
if (!car) {
|
||||
console.error(
|
||||
this.logger.error(
|
||||
`couldn\'t find car with department ${plannedTime.department_name}`,
|
||||
);
|
||||
continue;
|
||||
|
|
@ -83,13 +92,13 @@ export class SchichtplanungSynchronizer {
|
|||
end: end_date.toISOString(),
|
||||
group: car._id,
|
||||
mitarbeiterId: employee.id,
|
||||
}
|
||||
};
|
||||
|
||||
if(car.kfzNr.indexOf("KTW") > -1) {
|
||||
if (car.kfzNr.indexOf('KTW') > -1) {
|
||||
planning = {
|
||||
...planning,
|
||||
start: addMinutes(new Date(planning.start), 15).toISOString(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
schichtplanung.push(planning);
|
||||
|
|
@ -126,7 +135,9 @@ export class SchichtplanungSynchronizer {
|
|||
}
|
||||
if (
|
||||
!!plannedTime &&
|
||||
(plannedTime?.start !== plan.start || plannedTime?.end !== plan.end || plannedTime?.group !== plan.group)
|
||||
(plannedTime?.start !== plan.start ||
|
||||
plannedTime?.end !== plan.end ||
|
||||
plannedTime?.group !== plan.group)
|
||||
) {
|
||||
toUpdate.push({ ...plan, _id: plannedTime._id });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ export class TicketLifecycleService {
|
|||
|
||||
private async validateCreatedTickets(): Promise<void> {
|
||||
const tickets = await this.ticketService.findAllCreated();
|
||||
this.logger.debug(`Found ${tickets.length} with state CREATED`);
|
||||
this.logger.log(`validating ${tickets.length} created tickets`);
|
||||
|
||||
for (const ticket of tickets) {
|
||||
const {
|
||||
|
|
|
|||
Loading…
Reference in New Issue