From 50a3c4496c41ed0c0ccf3366dbed5d494f394c61 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:09:31 +0530 Subject: [PATCH 01/12] feat: added screen analyze columns --- packages/contracts/src/index.ts | 16 +-- packages/contracts/src/screenshot.model.ts | 40 ++++++ packages/contracts/src/timesheet.model.ts | 36 +---- .../1701081154869-AlterScreenshotTable.ts | 123 ++++++++++++++++++ .../screenshot/screenshot.entity.ts | 79 ++++++++++- 5 files changed, 247 insertions(+), 47 deletions(-) create mode 100644 packages/contracts/src/screenshot.model.ts create mode 100644 packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts diff --git a/packages/contracts/src/index.ts b/packages/contracts/src/index.ts index ca88ee0ced5..17146cafe7a 100644 --- a/packages/contracts/src/index.ts +++ b/packages/contracts/src/index.ts @@ -1,4 +1,6 @@ export * from './accounting-template.model'; +/** App Setting Model */ +export * from './app.model'; export * from './appointment-employees.model'; export * from './approval-policy.model'; export * from './availability-slots.model'; @@ -47,6 +49,7 @@ export * from './expense.model'; export * from './feature.model'; export * from './file-provider'; export * from './geo-location.model'; +export * from './github.model'; export * from './goal-settings.model'; export * from './goals.model'; export * from './help-center-article.model'; @@ -77,6 +80,7 @@ export * from './organization-positions.model'; export * from './organization-projects.model'; export * from './organization-recurring-expense.model'; export * from './organization-sprint.model'; +export * from './organization-task-setting.model'; export * from './organization-team-employee-model'; export * from './organization-team-join-request.model'; export * from './organization-team.model'; @@ -96,17 +100,19 @@ export * from './request-approval-team.model'; export * from './request-approval.model'; export * from './role-permission.model'; export * from './role.model'; +export * from './screenshot.model'; export * from './seed.model'; export * from './skill-entity.model'; export * from './sms.model'; export * from './tag.model'; +export * from './task-estimation.model'; +export * from './task-linked-issue.model'; export * from './task-priority.model'; -export * from './task-size.model'; export * from './task-related-issue-type.model'; +export * from './task-size.model'; export * from './task-status.model'; export * from './task-version.model'; export * from './task.model'; -export * from './task-linked-issue.model'; export * from './tenant.model'; export * from './time-off.model'; export * from './timesheet-statistics.model'; @@ -117,12 +123,6 @@ export * from './upwork.model'; export * from './user-organization.model'; export * from './user.model'; export * from './wakatime.model'; -export * from './organization-task-setting.model'; -export * from './task-estimation.model'; -export * from './github.model'; - -/** App Setting Model */ -export * from './app.model'; export { IBaseEntityModel as BaseEntityModel } from './base-entity.model'; export { diff --git a/packages/contracts/src/screenshot.model.ts b/packages/contracts/src/screenshot.model.ts new file mode 100644 index 00000000000..c619d3777ea --- /dev/null +++ b/packages/contracts/src/screenshot.model.ts @@ -0,0 +1,40 @@ +import { IBasePerTenantAndOrganizationEntityModel } from "./base-entity.model"; +import { FileStorageProviderEnum } from "./file-provider"; +import { ITimeSlot } from "./timesheet.model"; +import { IRelationalUser } from "./user.model"; + +export interface IScreenshot extends IBasePerTenantAndOrganizationEntityModel, IRelationalUser { + [x: string]: any; + file: string; + thumb?: string; + fileUrl?: string; + thumbUrl?: string; + fullUrl?: string; + recordedAt?: Date; + storageProvider?: FileStorageProviderEnum; + /** Image/Screenshot Analysis Through Gauzy AI */ + isWorkRelated?: boolean; + description?: string; + apps?: string; + /** Relations */ + timeSlot?: ITimeSlot; + timeSlotId?: ITimeSlot['id']; +} + +export interface IScreenshotMap { + startTime: string; + endTime: string; + timeSlots: ITimeSlot[]; +} + +export interface IUpdateScreenshotInput extends ICreateScreenshotInput { + id: string; +} + +export interface ICreateScreenshotInput extends IBasePerTenantAndOrganizationEntityModel { + activityTimestamp: string; + employeeId?: string; + file: string; + thumb?: string; + recordedAt: Date | string; +} diff --git a/packages/contracts/src/timesheet.model.ts b/packages/contracts/src/timesheet.model.ts index 6673f574fa3..d726301b30f 100644 --- a/packages/contracts/src/timesheet.model.ts +++ b/packages/contracts/src/timesheet.model.ts @@ -20,9 +20,9 @@ import { ITask } from './task.model'; import { ITag } from './tag.model'; import { IPaginationInput } from './core.model'; import { ReportGroupByFilter } from './report.model'; -import { FileStorageProviderEnum } from './file-provider'; -import { IRelationalUser, IUser } from './user.model'; +import { IUser } from './user.model'; import { IRelationalOrganizationTeam } from './organization-team.model'; +import { IScreenshot } from './screenshot.model'; export interface ITimesheet extends IBasePerTenantAndOrganizationEntityModel { employee: IEmployee; @@ -309,39 +309,7 @@ export interface IURLMetaData { [x: string]: any; } -export interface IUpdateScreenshotInput extends ICreateScreenshotInput { - id: string; -} - -export interface ICreateScreenshotInput - extends IBasePerTenantAndOrganizationEntityModel { - activityTimestamp: string; - employeeId?: string; - file: string; - thumb?: string; - recordedAt: Date | string; -} - -export interface IScreenshot - extends IBasePerTenantAndOrganizationEntityModel, - IRelationalEmployee, - IRelationalUser { - [x: string]: any; - timeSlot?: ITimeSlot; - timeSlotId?: ITimeSlot['id']; - file: string; - thumb?: string; - fileUrl?: string; - thumbUrl?: string; - recordedAt?: Date; - storageProvider?: FileStorageProviderEnum; -} -export interface IScreenshotMap { - startTime: string; - endTime: string; - timeSlots: ITimeSlot[]; -} export interface ITimerStatusInput extends ITimeLogTodayFilters, diff --git a/packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts b/packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts new file mode 100644 index 00000000000..333bbb98735 --- /dev/null +++ b/packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts @@ -0,0 +1,123 @@ + +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AlterScreenshotTable1701081154869 implements MigrationInterface { + + name = 'AlterScreenshotTable1701081154869'; + + /** + * Up Migration + * + * @param queryRunner + */ + public async up(queryRunner: QueryRunner): Promise { + if (['sqlite', 'better-sqlite3'].includes(queryRunner.connection.options.type)) { + await this.sqliteUpQueryRunner(queryRunner); + } else { + await this.postgresUpQueryRunner(queryRunner); + } + } + + /** + * Down Migration + * + * @param queryRunner + */ + public async down(queryRunner: QueryRunner): Promise { + if (['sqlite', 'better-sqlite3'].includes(queryRunner.connection.options.type)) { + await this.sqliteDownQueryRunner(queryRunner); + } else { + await this.postgresDownQueryRunner(queryRunner); + } + } + + /** + * PostgresDB Up Migration + * + * @param queryRunner + */ + public async postgresUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "screenshot" ADD "isWorkRelated" boolean`); + await queryRunner.query(`ALTER TABLE "screenshot" ADD "description" character varying`); + await queryRunner.query(`ALTER TABLE "screenshot" ADD "apps" json`); + await queryRunner.query(`CREATE INDEX "IDX_1b0867d86ead2332f3d4edba7d" ON "screenshot" ("isWorkRelated") `); + await queryRunner.query(`CREATE INDEX "IDX_eea7986acfb827bf5d0622c41f" ON "screenshot" ("description") `); + await queryRunner.query(`CREATE INDEX "IDX_0b6582d5ceeeef670c5e053ea6" ON "screenshot" ("apps") `); + } + + /** + * PostgresDB Down Migration + * + * @param queryRunner + */ + public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_0b6582d5ceeeef670c5e053ea6"`); + await queryRunner.query(`DROP INDEX "public"."IDX_eea7986acfb827bf5d0622c41f"`); + await queryRunner.query(`DROP INDEX "public"."IDX_1b0867d86ead2332f3d4edba7d"`); + await queryRunner.query(`ALTER TABLE "screenshot" DROP COLUMN "apps"`); + await queryRunner.query(`ALTER TABLE "screenshot" DROP COLUMN "description"`); + await queryRunner.query(`ALTER TABLE "screenshot" DROP COLUMN "isWorkRelated"`); + } + + /** + * SqliteDB and BetterSQlite3DB Up Migration + * + * @param queryRunner + */ + public async sqliteUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_892e285e1da2b3e61e51e50628"`); + await queryRunner.query(`DROP INDEX "IDX_742688858e0484d66f04e4d4c4"`); + await queryRunner.query(`DROP INDEX "IDX_2b374e5cdee1145ebb2a832f20"`); + await queryRunner.query(`DROP INDEX "IDX_3d7feb5fe793e4811cdb79f983"`); + await queryRunner.query(`DROP INDEX "IDX_235004f3dafac90692cd64d915"`); + await queryRunner.query(`DROP INDEX "IDX_0951aacffe3f8d0cff54cf2f34"`); + await queryRunner.query(`DROP INDEX "IDX_5b594d02d98d5defcde323abe5"`); + await queryRunner.query(`DROP INDEX "IDX_fa1896dc735403799311968f7e"`); + await queryRunner.query(`CREATE TABLE "temporary_screenshot" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "file" varchar NOT NULL, "thumb" varchar, "recordedAt" datetime, "deletedAt" datetime, "timeSlotId" varchar, "storageProvider" varchar, "userId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "isWorkRelated" boolean, "description" varchar, "apps" text, CONSTRAINT "FK_235004f3dafac90692cd64d9158" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_0951aacffe3f8d0cff54cf2f341" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_5b594d02d98d5defcde323abe5b" FOREIGN KEY ("timeSlotId") REFERENCES "time_slot" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_fa1896dc735403799311968f7ec" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_screenshot"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "file", "thumb", "recordedAt", "deletedAt", "timeSlotId", "storageProvider", "userId", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "file", "thumb", "recordedAt", "deletedAt", "timeSlotId", "storageProvider", "userId", "isActive", "isArchived" FROM "screenshot"`); + await queryRunner.query(`DROP TABLE "screenshot"`); + await queryRunner.query(`ALTER TABLE "temporary_screenshot" RENAME TO "screenshot"`); + await queryRunner.query(`CREATE INDEX "IDX_892e285e1da2b3e61e51e50628" ON "screenshot" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_742688858e0484d66f04e4d4c4" ON "screenshot" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_2b374e5cdee1145ebb2a832f20" ON "screenshot" ("storageProvider") `); + await queryRunner.query(`CREATE INDEX "IDX_3d7feb5fe793e4811cdb79f983" ON "screenshot" ("recordedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_235004f3dafac90692cd64d915" ON "screenshot" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_0951aacffe3f8d0cff54cf2f34" ON "screenshot" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_5b594d02d98d5defcde323abe5" ON "screenshot" ("timeSlotId") `); + await queryRunner.query(`CREATE INDEX "IDX_fa1896dc735403799311968f7e" ON "screenshot" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_1b0867d86ead2332f3d4edba7d" ON "screenshot" ("isWorkRelated") `); + await queryRunner.query(`CREATE INDEX "IDX_eea7986acfb827bf5d0622c41f" ON "screenshot" ("description") `); + await queryRunner.query(`CREATE INDEX "IDX_0b6582d5ceeeef670c5e053ea6" ON "screenshot" ("apps") `); + } + + /** + * SqliteDB and BetterSQlite3DB Down Migration + * + * @param queryRunner + */ + public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_0b6582d5ceeeef670c5e053ea6"`); + await queryRunner.query(`DROP INDEX "IDX_eea7986acfb827bf5d0622c41f"`); + await queryRunner.query(`DROP INDEX "IDX_1b0867d86ead2332f3d4edba7d"`); + await queryRunner.query(`DROP INDEX "IDX_fa1896dc735403799311968f7e"`); + await queryRunner.query(`DROP INDEX "IDX_5b594d02d98d5defcde323abe5"`); + await queryRunner.query(`DROP INDEX "IDX_0951aacffe3f8d0cff54cf2f34"`); + await queryRunner.query(`DROP INDEX "IDX_235004f3dafac90692cd64d915"`); + await queryRunner.query(`DROP INDEX "IDX_3d7feb5fe793e4811cdb79f983"`); + await queryRunner.query(`DROP INDEX "IDX_2b374e5cdee1145ebb2a832f20"`); + await queryRunner.query(`DROP INDEX "IDX_742688858e0484d66f04e4d4c4"`); + await queryRunner.query(`DROP INDEX "IDX_892e285e1da2b3e61e51e50628"`); + await queryRunner.query(`ALTER TABLE "screenshot" RENAME TO "temporary_screenshot"`); + await queryRunner.query(`CREATE TABLE "screenshot" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "file" varchar NOT NULL, "thumb" varchar, "recordedAt" datetime, "deletedAt" datetime, "timeSlotId" varchar, "storageProvider" varchar, "userId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), CONSTRAINT "FK_235004f3dafac90692cd64d9158" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_0951aacffe3f8d0cff54cf2f341" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_5b594d02d98d5defcde323abe5b" FOREIGN KEY ("timeSlotId") REFERENCES "time_slot" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_fa1896dc735403799311968f7ec" FOREIGN KEY ("userId") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "screenshot"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "file", "thumb", "recordedAt", "deletedAt", "timeSlotId", "storageProvider", "userId", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "file", "thumb", "recordedAt", "deletedAt", "timeSlotId", "storageProvider", "userId", "isActive", "isArchived" FROM "temporary_screenshot"`); + await queryRunner.query(`DROP TABLE "temporary_screenshot"`); + await queryRunner.query(`CREATE INDEX "IDX_fa1896dc735403799311968f7e" ON "screenshot" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_5b594d02d98d5defcde323abe5" ON "screenshot" ("timeSlotId") `); + await queryRunner.query(`CREATE INDEX "IDX_0951aacffe3f8d0cff54cf2f34" ON "screenshot" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_235004f3dafac90692cd64d915" ON "screenshot" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_3d7feb5fe793e4811cdb79f983" ON "screenshot" ("recordedAt") `); + await queryRunner.query(`CREATE INDEX "IDX_2b374e5cdee1145ebb2a832f20" ON "screenshot" ("storageProvider") `); + await queryRunner.query(`CREATE INDEX "IDX_742688858e0484d66f04e4d4c4" ON "screenshot" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_892e285e1da2b3e61e51e50628" ON "screenshot" ("isArchived") `); + } +} diff --git a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts index f9a0199c739..0f9ef86dce8 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts @@ -1,3 +1,4 @@ +import { TypeOrmModuleOptions } from '@nestjs/typeorm'; import { Entity, Column, @@ -6,19 +7,30 @@ import { Index, JoinColumn } from 'typeorm'; -import { FileStorageProviderEnum, IScreenshot, ITimeSlot, IUser } from '@gauzy/contracts'; +import { getConfig } from '@gauzy/config'; import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { IsString, IsOptional, IsDateString, IsUUID, IsNotEmpty, IsEnum } from 'class-validator'; +import { IsString, IsOptional, IsDateString, IsUUID, IsNotEmpty, IsEnum, IsBoolean } from 'class-validator'; import { Exclude } from 'class-transformer'; +import { FileStorageProviderEnum, IScreenshot, ITimeSlot, IUser } from '@gauzy/contracts'; import { TenantOrganizationBaseEntity, TimeSlot, User } from './../../core/entities/internal'; +/** + * + */ +let options: TypeOrmModuleOptions; +try { + options = getConfig().dbConnectionOptions +} catch (error) { } + +/** + * + */ @Entity('screenshot') -export class Screenshot extends TenantOrganizationBaseEntity - implements IScreenshot { +export class Screenshot extends TenantOrganizationBaseEntity implements IScreenshot { @ApiProperty({ type: () => String, }) @IsNotEmpty() @@ -51,6 +63,55 @@ export class Screenshot extends TenantOrganizationBaseEntity }) storageProvider?: FileStorageProviderEnum; + /* + |-------------------------------------------------------------------------- + | Image/Screenshot Analysis Through Gauzy AI + |-------------------------------------------------------------------------- + */ + + /** + * Indicates whether the image or screenshot is work-related. + */ + @ApiPropertyOptional({ + type: () => String, + description: 'Specifies whether the image or screenshot is work-related.' + }) + @IsOptional() + @IsBoolean() + @Index() + @Column({ nullable: true }) + isWorkRelated?: boolean; + + /** + * Description of the image or screenshot. + */ + @ApiPropertyOptional({ + type: () => String, + description: 'Description of the image or screenshot.' + }) + @IsOptional() + @IsString() + @Index() + @Column({ nullable: true }) + description?: string; + + /** + * Applications associated with the image or screenshot. + */ + @ApiPropertyOptional({ + type: () => String, + description: 'Applications associated with the image or screenshot.' + }) + @IsOptional() + @IsString() + @Index() + @Column({ + nullable: true, + type: ['sqlite', 'better-sqlite3'].includes(options.type) ? 'text' : 'json' + }) + apps?: string; + + /** Additional fields */ fullUrl?: string; thumbUrl?: string; /* @@ -62,7 +123,11 @@ export class Screenshot extends TenantOrganizationBaseEntity /** * TimeSlot */ - @ManyToOne(() => TimeSlot, (timeSlot) => timeSlot.screenshots, { + @ManyToOne(() => TimeSlot, (it) => it.screenshots, { + /** Indicates if relation column value can be nullable or not. */ + nullable: true, + + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @JoinColumn() @@ -80,6 +145,10 @@ export class Screenshot extends TenantOrganizationBaseEntity * User */ @ManyToOne(() => User, { + /** Indicates if relation column value can be nullable or not. */ + nullable: true, + + /** Database cascade action on delete. */ onDelete: 'CASCADE' }) @JoinColumn() From d3f0c72e80ab8dce365a6a424f1ed2edac36ee70 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Mon, 27 Nov 2023 16:36:10 +0530 Subject: [PATCH 02/12] fix: indexing not working with json field (PostgreSQL) --- .../database/migrations/1701081154869-AlterScreenshotTable.ts | 4 ---- .../core/src/time-tracking/screenshot/screenshot.entity.ts | 1 - 2 files changed, 5 deletions(-) diff --git a/packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts b/packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts index 333bbb98735..8acce21d768 100644 --- a/packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts +++ b/packages/core/src/database/migrations/1701081154869-AlterScreenshotTable.ts @@ -42,7 +42,6 @@ export class AlterScreenshotTable1701081154869 implements MigrationInterface { await queryRunner.query(`ALTER TABLE "screenshot" ADD "apps" json`); await queryRunner.query(`CREATE INDEX "IDX_1b0867d86ead2332f3d4edba7d" ON "screenshot" ("isWorkRelated") `); await queryRunner.query(`CREATE INDEX "IDX_eea7986acfb827bf5d0622c41f" ON "screenshot" ("description") `); - await queryRunner.query(`CREATE INDEX "IDX_0b6582d5ceeeef670c5e053ea6" ON "screenshot" ("apps") `); } /** @@ -51,7 +50,6 @@ export class AlterScreenshotTable1701081154869 implements MigrationInterface { * @param queryRunner */ public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP INDEX "public"."IDX_0b6582d5ceeeef670c5e053ea6"`); await queryRunner.query(`DROP INDEX "public"."IDX_eea7986acfb827bf5d0622c41f"`); await queryRunner.query(`DROP INDEX "public"."IDX_1b0867d86ead2332f3d4edba7d"`); await queryRunner.query(`ALTER TABLE "screenshot" DROP COLUMN "apps"`); @@ -87,7 +85,6 @@ export class AlterScreenshotTable1701081154869 implements MigrationInterface { await queryRunner.query(`CREATE INDEX "IDX_fa1896dc735403799311968f7e" ON "screenshot" ("userId") `); await queryRunner.query(`CREATE INDEX "IDX_1b0867d86ead2332f3d4edba7d" ON "screenshot" ("isWorkRelated") `); await queryRunner.query(`CREATE INDEX "IDX_eea7986acfb827bf5d0622c41f" ON "screenshot" ("description") `); - await queryRunner.query(`CREATE INDEX "IDX_0b6582d5ceeeef670c5e053ea6" ON "screenshot" ("apps") `); } /** @@ -96,7 +93,6 @@ export class AlterScreenshotTable1701081154869 implements MigrationInterface { * @param queryRunner */ public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP INDEX "IDX_0b6582d5ceeeef670c5e053ea6"`); await queryRunner.query(`DROP INDEX "IDX_eea7986acfb827bf5d0622c41f"`); await queryRunner.query(`DROP INDEX "IDX_1b0867d86ead2332f3d4edba7d"`); await queryRunner.query(`DROP INDEX "IDX_fa1896dc735403799311968f7e"`); diff --git a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts index 0f9ef86dce8..c9b9ef3723a 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts @@ -104,7 +104,6 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens }) @IsOptional() @IsString() - @Index() @Column({ nullable: true, type: ['sqlite', 'better-sqlite3'].includes(options.type) ? 'text' : 'json' From fcb3417e5ed4126506aa85ad129b87863e649edc Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Tue, 28 Nov 2023 20:11:23 +0530 Subject: [PATCH 03/12] wip: send screenshot to gauzy ai to analysis --- .../screenshot/screenshot.controller.ts | 37 +++++++- .../screenshot/screenshot.module.ts | 6 +- packages/plugins/integration-ai/package.json | 3 +- .../integration-ai/src/gauzy-ai.service.ts | 94 +++++++++++++------ 4 files changed, 105 insertions(+), 35 deletions(-) diff --git a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts index b614d6a773e..fa514b0b099 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts @@ -9,7 +9,7 @@ import { Param, Query, UsePipes, - ValidationPipe, + ValidationPipe } from '@nestjs/common'; import { ApiTags, ApiOperation, ApiResponse } from '@nestjs/swagger'; import * as path from 'path'; @@ -17,6 +17,7 @@ import * as moment from 'moment'; import * as fs from 'fs'; import { v4 as uuid } from 'uuid'; import * as Jimp from 'jimp'; +import { GauzyAIService } from '@gauzy/integration-ai'; import { FileStorageProviderEnum, IScreenshot, @@ -33,6 +34,7 @@ import { Permissions } from './../../shared/decorators'; import { PermissionGuard, TenantPermissionGuard } from './../../shared/guards'; import { UUIDValidationPipe } from './../../shared/pipes'; import { DeleteQueryDTO } from './../../shared/dto'; +import { IntegrationTenantService } from 'integration-tenant/integration-tenant.service'; @ApiTags('Screenshot') @UseGuards(TenantPermissionGuard, PermissionGuard) @@ -41,9 +43,18 @@ import { DeleteQueryDTO } from './../../shared/dto'; export class ScreenshotController { constructor( - private readonly screenshotService: ScreenshotService + private readonly screenshotService: ScreenshotService, + private readonly _gauzyAIService: GauzyAIService, + private readonly _integrationTenantService: IntegrationTenantService ) { } + /** + * + * @param entity + * @param file + * @param _req + * @returns + */ @ApiOperation({ summary: 'Create start/stop screenshot.' }) @ApiResponse({ status: HttpStatus.OK, @@ -73,6 +84,9 @@ export class ScreenshotController { const provider = new FileStorage().getProvider(); let thumb: UploadedFile; + /** */ + let data: Buffer; + try { const fileContent = await provider.getFile(file.key); const inputFile = await tempFile('screenshot-thumb'); @@ -91,7 +105,9 @@ export class ScreenshotController { reject(error); } }); - const data = await fs.promises.readFile(outputFile); + + data = await fs.promises.readFile(outputFile); + await fs.promises.unlink(inputFile); await fs.promises.unlink(outputFile); @@ -104,6 +120,15 @@ export class ScreenshotController { console.log('Error while uploading screenshot into file storage provider:', error); } + /** + * + */ + try { + await this._gauzyAIService.analyzeImage([data]); + } catch (error) { + console.log(error); + } + try { entity.userId = RequestContext.currentUserId(); entity.file = file.key; @@ -119,6 +144,12 @@ export class ScreenshotController { } } + /** + * + * @param screenshotId + * @param options + * @returns + */ @ApiOperation({ summary: 'Delete record', }) diff --git a/packages/core/src/time-tracking/screenshot/screenshot.module.ts b/packages/core/src/time-tracking/screenshot/screenshot.module.ts index e30aa77ccfb..7a1a89b4ad9 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.module.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.module.ts @@ -1,13 +1,15 @@ import { forwardRef, Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CqrsModule } from '@nestjs/cqrs'; +import { GauzyAIModule } from '@gauzy/integration-ai'; import { TenantModule } from './../../tenant/tenant.module'; import { Screenshot } from './screenshot.entity'; import { ScreenshotController } from './screenshot.controller'; import { ScreenshotService } from './screenshot.service'; -import { CommandHandlers } from './commands/handlers'; import { TimeSlotModule } from './../time-slot/time-slot.module'; import { UserModule } from './../../user/user.module'; +import { IntegrationTenantModule } from './../../integration-tenant/integration-tenant.module'; +import { CommandHandlers } from './commands/handlers'; @Module({ controllers: [ @@ -17,9 +19,11 @@ import { UserModule } from './../../user/user.module'; TypeOrmModule.forFeature([ Screenshot ]), + GauzyAIModule.forRoot(), TenantModule, forwardRef(() => TimeSlotModule), forwardRef(() => UserModule), + IntegrationTenantModule, CqrsModule ], providers: [ diff --git a/packages/plugins/integration-ai/package.json b/packages/plugins/integration-ai/package.json index 43cc44e8a31..bdba3af4a56 100644 --- a/packages/plugins/integration-ai/package.json +++ b/packages/plugins/integration-ai/package.json @@ -33,7 +33,8 @@ "@nestjs/common": "^9.2.1", "@nestjs/config": "^2.2.0", "axios": "^1.5.0", - "chalk": "4.1.2" + "chalk": "4.1.2", + "form-data": "^3.0.0" }, "devDependencies": { "@types/node": "^17.0.33", diff --git a/packages/plugins/integration-ai/src/gauzy-ai.service.ts b/packages/plugins/integration-ai/src/gauzy-ai.service.ts index 3222aab88ad..128e3fdc614 100644 --- a/packages/plugins/integration-ai/src/gauzy-ai.service.ts +++ b/packages/plugins/integration-ai/src/gauzy-ai.service.ts @@ -22,6 +22,7 @@ import { import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; import fetch from 'cross-fetch'; import * as chalk from 'chalk'; +import * as FormData from 'form-data'; import { ApolloClient, ApolloQueryResult, @@ -84,44 +85,43 @@ export class GauzyAIService { } /** - * Send an HTTP request with dynamic configuration. - * - * @param path The URL path for the request. - * @param options Custom Axios request configuration. - * @param method The HTTP method (e.g., GET, POST). - * @returns An Observable that emits the response data or throws an error. - */ + * Send an HTTP request with dynamic configuration. + * + * @param path The URL path for the request. + * @param options Custom Axios request configuration. + * @param method The HTTP method (e.g., GET, POST). + * @returns An Observable that emits the response data or throws an error. + */ private sendRequest( path: string, options: AxiosRequestConfig = {}, method: string = HttpMethodEnum.GET, + defaultHeaders: AxiosRequestHeaders = { + 'Content-Type': 'application/json', // Define default headers + } ): Observable> { - const { - ApiKey, - ApiSecret, - ApiBearerToken, - ApiTenantId, - } = this._requestConfigProvider.getConfig(); - - // Define default headers - const defaultHeaders: AxiosRequestHeaders = { - 'Content-Type': 'application/json', - }; + /** */ + const { ApiKey, ApiSecret, ApiBearerToken, ApiTenantId } = this._requestConfigProvider.getConfig(); - // Add your custom headers here - const headers: AxiosRequestHeaders = { + // Add your custom headers + const customHeaders = (): AxiosRequestHeaders => ({ // Define default headers ...defaultHeaders, // Add your custom headers here 'X-APP-ID': this._configService.get('guazyAI.gauzyAiApiKey'), 'X-API-KEY': this._configService.get('guazyAI.gauzyAiApiSecret'), + /** */ ...(ApiKey ? { 'X-APP-ID': ApiKey } : {}), ...(ApiSecret ? { 'X-API-KEY': ApiSecret } : {}), + /** */ ...(ApiTenantId ? { 'Tenant-Id': ApiTenantId } : {}), ...(ApiBearerToken ? { 'Authorization': ApiBearerToken } : {}), - }; + }); + + /** */ + const headers: AxiosRequestHeaders = customHeaders(); console.log('Default AxiosRequestConfig Headers: %s', `${JSON.stringify(headers)}`); // Merge the provided options with the default options @@ -140,7 +140,7 @@ export class GauzyAIService { return this._http.request({ ...mergedOptions, url: path, - method: method, + method, headers, }); } catch (error) { @@ -155,6 +155,45 @@ export class GauzyAIService { } } + /** + * Analyze an image/screenshot using Gauzy AI. + * + * @param files - Array of Buffers representing the uploaded images. + * @returns Promise - The analysis result for the image. + */ + public async analyzeImage(files: Buffer[]): Promise { + // Create FormData and append the image data + const form = new FormData(); + + // Append each file to the FormData with a unique key + files.forEach((file: Buffer, index) => { + // Assuming you have an image file or buffer + form.append(`files[${index}]`, file, `image_${index}.jpg`); + }); + + // Set custom headers + const headers = { + ...form.getHeaders(), + 'Content-Length': form.getLengthSync().toString(), + // Add any other headers you need + }; + + // Set request options + const options = { + data: form, // Set the request payload + params: { + ws_key: 'EXAMPLE', + }, + }; + + // Call the sendRequest function with the appropriate parameters + return await firstValueFrom( + this.sendRequest('image/process', options, HttpMethodEnum.POST, headers).pipe( + map((resp: AxiosResponse) => resp.data) + ) + ); + } + /** * Call pre process method to create new employee job application record. * @@ -170,9 +209,8 @@ export class GauzyAIService { // Call the sendRequest function with the appropriate parameters return await firstValueFrom( this.sendRequest('employee/job/application/pre-process', { - method: HttpMethodEnum.POST, // Set the HTTP method to POST data: params, // Set the request payload - }).pipe( + }, HttpMethodEnum.POST).pipe( tap((resp: AxiosResponse) => console.log(resp)), map((resp: AxiosResponse) => resp.data) ) @@ -190,9 +228,7 @@ export class GauzyAIService { ): Promise { // Call the sendRequest function with the appropriate parameters return await firstValueFrom( - this.sendRequest(`employee/job/application/generate-proposal/${employeeJobApplicationId}`, { - method: HttpMethodEnum.POST, // Set the HTTP method to POST - }).pipe( + this.sendRequest(`employee/job/application/generate-proposal/${employeeJobApplicationId}`, {}, HttpMethodEnum.POST).pipe( tap((resp: AxiosResponse) => console.log(resp)), map((resp: AxiosResponse) => resp.data) ) @@ -210,9 +246,7 @@ export class GauzyAIService { ): Promise { // Call the sendRequest function with the appropriate parameters return await firstValueFrom( - this.sendRequest(`employee/job/application/${employeeJobApplicationId}`, { - method: HttpMethodEnum.GET, // Set the HTTP method to GET - }).pipe( + this.sendRequest(`employee/job/application/${employeeJobApplicationId}`).pipe( tap((resp: AxiosResponse) => console.log(resp)), map((resp: AxiosResponse) => resp.data) ) From bfb6ab017d2874bf885366b6c47fa7278a408e87 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:55:03 +0530 Subject: [PATCH 04/12] fix: refactor analyze screenshot/image using Gauzy AI --- .../screenshot/screenshot.controller.ts | 10 ++++++--- .../integration-ai/src/config/gauzy-ai.ts | 7 +++++- .../integration-ai/src/gauzy-ai.service.ts | 22 ++++++++++--------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts index fa514b0b099..e1a0eede02f 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts @@ -52,7 +52,6 @@ export class ScreenshotController { * * @param entity * @param file - * @param _req * @returns */ @ApiOperation({ summary: 'Create start/stop screenshot.' }) @@ -79,7 +78,9 @@ export class ScreenshotController { @Body() entity: Screenshot, @UploadedFileStorage() file: UploadedFile ) { - console.log('Screenshot Http Request', { entity, file }); + const { organizationId } = entity; + const tenantId = RequestContext.currentTenantId() || entity.tenantId; + const user = RequestContext.currentUser(); const provider = new FileStorage().getProvider(); let thumb: UploadedFile; @@ -124,12 +125,15 @@ export class ScreenshotController { * */ try { - await this._gauzyAIService.analyzeImage([data]); + const [analysis] = await this._gauzyAIService.analyzeImage(data, file); + console.log(analysis); } catch (error) { console.log(error); } try { + entity.organizationId = organizationId; + entity.tenantId = tenantId; entity.userId = RequestContext.currentUserId(); entity.file = file.key; entity.thumb = thumb.key; diff --git a/packages/plugins/integration-ai/src/config/gauzy-ai.ts b/packages/plugins/integration-ai/src/config/gauzy-ai.ts index ed3fc834cfc..e981c295904 100644 --- a/packages/plugins/integration-ai/src/config/gauzy-ai.ts +++ b/packages/plugins/integration-ai/src/config/gauzy-ai.ts @@ -1,10 +1,15 @@ import { registerAs } from '@nestjs/config'; +/** + * Gauzy AI Configuration + */ export default registerAs('guazyAI', () => ({ + // GraphQL endpoint for Gauzy AI gauzyAIGraphQLEndpoint: process.env.GAUZY_AI_GRAPHQL_ENDPOINT || null, + // REST endpoint for Gauzy AI gauzyAIRESTEndpoint: process.env.GAUZY_AI_REST_ENDPOINT || null, + // Request timeout for Gauzy AI in milliseconds gauzyAIRequestTimeout: parseInt(process.env.GAUZY_AI_REQUEST_TIMEOUT) || 60 * 5 * 1000, - // Gauzy AI API keys Pair gauzyAiApiKey: process.env.GAUZY_AI_API_KEY || null, gauzyAiApiSecret: process.env.GAUZY_AI_API_SECRET || null diff --git a/packages/plugins/integration-ai/src/gauzy-ai.service.ts b/packages/plugins/integration-ai/src/gauzy-ai.service.ts index 128e3fdc614..28a47deed85 100644 --- a/packages/plugins/integration-ai/src/gauzy-ai.service.ts +++ b/packages/plugins/integration-ai/src/gauzy-ai.service.ts @@ -130,11 +130,13 @@ export class GauzyAIService { // Inside your sendRequest method, use qs.stringify for custom parameter serialization paramsSerializer: (params) => { console.log('Customize the serialization of URL parameters', params); - // Customize the serialization of URL parameters as needed - return qs.stringify(params, { arrayFormat: 'repeat' }); + if (Object.keys(params).length > 0) { + // Customize the serialization of URL parameters as needed + return qs.stringify(params, { arrayFormat: 'repeat' }); + } } }; - console.log('Default AxiosRequestConfig Options: %s', `${JSON.stringify(mergedOptions)}`); + // console.log('Default AxiosRequestConfig Options: %s', `${JSON.stringify(mergedOptions)}`); try { return this._http.request({ @@ -161,14 +163,14 @@ export class GauzyAIService { * @param files - Array of Buffers representing the uploaded images. * @returns Promise - The analysis result for the image. */ - public async analyzeImage(files: Buffer[]): Promise { + public async analyzeImage(stream: Buffer, file: any): Promise { // Create FormData and append the image data const form = new FormData(); - // Append each file to the FormData with a unique key - files.forEach((file: Buffer, index) => { - // Assuming you have an image file or buffer - form.append(`files[${index}]`, file, `image_${index}.jpg`); + // Assuming you have an image file or buffer + form.append(`files`, stream, { + filename: file.filename, + contentType: 'application/octet-stream' }); // Set custom headers @@ -182,8 +184,8 @@ export class GauzyAIService { const options = { data: form, // Set the request payload params: { - ws_key: 'EXAMPLE', - }, + + } }; // Call the sendRequest function with the appropriate parameters From 85fd833776ab1275a1975d71d867b6af919f1d55 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:23:07 +0530 Subject: [PATCH 05/12] feat: screenshot analysis when Gauzy AI integration enabled --- packages/contracts/src/screenshot.model.ts | 2 +- .../integration-tenant.service.ts | 41 ++++++++------ .../gauzy-ai/integration-ai.middleware.ts | 7 ++- .../screenshot/screenshot.controller.ts | 54 ++++++++++++++----- .../screenshot/screenshot.entity.ts | 2 +- .../screenshot/screenshot.service.ts | 23 +++++++- .../screenshot/screenshot.subscriber.ts | 34 ++++++++++-- .../integration-ai/src/gauzy-ai.service.ts | 22 ++++++-- 8 files changed, 144 insertions(+), 41 deletions(-) diff --git a/packages/contracts/src/screenshot.model.ts b/packages/contracts/src/screenshot.model.ts index c619d3777ea..a07079cea5b 100644 --- a/packages/contracts/src/screenshot.model.ts +++ b/packages/contracts/src/screenshot.model.ts @@ -15,7 +15,7 @@ export interface IScreenshot extends IBasePerTenantAndOrganizationEntityModel, I /** Image/Screenshot Analysis Through Gauzy AI */ isWorkRelated?: boolean; description?: string; - apps?: string; + apps?: string | string[]; /** Relations */ timeSlot?: ITimeSlot; timeSlotId?: ITimeSlot['id']; diff --git a/packages/core/src/integration-tenant/integration-tenant.service.ts b/packages/core/src/integration-tenant/integration-tenant.service.ts index d17a6620742..6851c6bb349 100644 --- a/packages/core/src/integration-tenant/integration-tenant.service.ts +++ b/packages/core/src/integration-tenant/integration-tenant.service.ts @@ -98,17 +98,18 @@ export class IntegrationTenantService extends TenantAwareCrudService { try { - const { organizationId, tenantId } = options; + const tenantId = RequestContext.currentTenantId() || input.tenantId; + const { organizationId, name } = input; + return await this.findOneByOptions({ where: { organizationId, tenantId, - name: IntegrationEnum.GAUZY_AI + name, + isActive: true, + isArchived: false, + integration: { + provider: name, + isActive: true, + isArchived: false, + } }, relations: { settings: true diff --git a/packages/core/src/integration/gauzy-ai/integration-ai.middleware.ts b/packages/core/src/integration/gauzy-ai/integration-ai.middleware.ts index 7d176e0e97b..53a92ab9b37 100644 --- a/packages/core/src/integration/gauzy-ai/integration-ai.middleware.ts +++ b/packages/core/src/integration/gauzy-ai/integration-ai.middleware.ts @@ -1,6 +1,7 @@ import { Injectable, NestMiddleware } from '@nestjs/common'; import { Request, Response, NextFunction } from 'express'; import { isNotEmpty } from '@gauzy/common'; +import { IntegrationEnum } from '@gauzy/contracts'; import { RequestConfigProvider } from '@gauzy/integration-ai'; import { arrayToObject } from 'core/utils'; import { IntegrationTenantService } from 'integration-tenant/integration-tenant.service'; @@ -34,7 +35,11 @@ export class IntegrationAIMiddleware implements NestMiddleware { // Check if tenant and organization IDs are not empty if (isNotEmpty(tenantId) && isNotEmpty(organizationId)) { // Fetch integration settings from the service - const { settings = [] } = await this.integrationTenantService.getIntegrationSettings({ tenantId, organizationId }); + const { settings = [] } = await this.integrationTenantService.getIntegrationTenantSettings({ + tenantId, + organizationId, + name: IntegrationEnum.GAUZY_AI + }); // Convert settings array to an object const { apiKey: ApiKey, apiSecret: ApiSecret } = arrayToObject(settings, 'settingsName', 'settingsValue'); diff --git a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts index e1a0eede02f..20c2a1f7b54 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts @@ -21,6 +21,7 @@ import { GauzyAIService } from '@gauzy/integration-ai'; import { FileStorageProviderEnum, IScreenshot, + IntegrationEnum, PermissionsEnum, UploadedFile, } from '@gauzy/contracts'; @@ -43,7 +44,7 @@ import { IntegrationTenantService } from 'integration-tenant/integration-tenant. export class ScreenshotController { constructor( - private readonly screenshotService: ScreenshotService, + private readonly _screenshotService: ScreenshotService, private readonly _gauzyAIService: GauzyAIService, private readonly _integrationTenantService: IntegrationTenantService ) { } @@ -65,10 +66,16 @@ export class ScreenshotController { }) @Post() @UseInterceptors( + // Use the LazyFileInterceptor to handle file uploads LazyFileInterceptor('file', { + // Define storage settings for uploaded files storage: () => { return new FileStorage().storage({ - dest: () => path.join('screenshots', moment().format('YYYY/MM/DD'), RequestContext.currentTenantId() || uuid()), + dest: () => path.join( + 'screenshots', + moment().format('YYYY/MM/DD'), + RequestContext.currentTenantId() || uuid() + ), prefix: 'screenshots', }); }, @@ -78,9 +85,11 @@ export class ScreenshotController { @Body() entity: Screenshot, @UploadedFileStorage() file: UploadedFile ) { + // Extract necessary properties from the request body const { organizationId } = entity; const tenantId = RequestContext.currentTenantId() || entity.tenantId; + // Extract user information from the request context const user = RequestContext.currentUser(); const provider = new FileStorage().getProvider(); let thumb: UploadedFile; @@ -89,10 +98,15 @@ export class ScreenshotController { let data: Buffer; try { + // Process the thumbnail + const fileContent = await provider.getFile(file.key); + const inputFile = await tempFile('screenshot-thumb'); const outputFile = await tempFile('screenshot-thumb'); + await fs.promises.writeFile(inputFile, fileContent); + await new Promise(async (resolve, reject) => { const image = await Jimp.read(inputFile); @@ -121,15 +135,28 @@ export class ScreenshotController { console.log('Error while uploading screenshot into file storage provider:', error); } - /** - * - */ try { - const [analysis] = await this._gauzyAIService.analyzeImage(data, file); - console.log(analysis); - } catch (error) { - console.log(error); - } + // Retrieve integration + const integration = await this._integrationTenantService.getIntegrationByOptions({ + organizationId, + tenantId, + name: IntegrationEnum.GAUZY_AI + }); + + // Check if integration exists + if (!!integration) { + // Analyze image using Gauzy AI service + const [analysis] = await this._gauzyAIService.analyzeImage(data, file); + if (!!analysis.success) { + const [analyzeImage] = analysis.data.analysis; + + entity.isWorkRelated = analyzeImage.work; + entity.description = analyzeImage.description; + /** */ + entity.apps = analyzeImage.apps; + } + } + } catch (error) { } try { entity.organizationId = organizationId; @@ -140,9 +167,10 @@ export class ScreenshotController { entity.storageProvider = provider.name.toUpperCase() as FileStorageProviderEnum; entity.recordedAt = entity.recordedAt ? entity.recordedAt : new Date(); - const screenshot = await this.screenshotService.create(entity); + console.log({ entity }); + const screenshot = await this._screenshotService.create(entity); console.log(`Screenshot created for employee (${user.name})`, screenshot); - return await this.screenshotService.findOneByIdString(screenshot.id); + return await this._screenshotService.findOneByIdString(screenshot.id); } catch (error) { console.log(`Error while creating screenshot for employee (${user.name})`, error); } @@ -172,7 +200,7 @@ export class ScreenshotController { @Param('id', UUIDValidationPipe) screenshotId: IScreenshot['id'], @Query() options: DeleteQueryDTO ): Promise { - return await this.screenshotService.deleteScreenshot( + return await this._screenshotService.deleteScreenshot( screenshotId, options ); diff --git a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts index c9b9ef3723a..a07ff11b723 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.entity.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.entity.ts @@ -108,7 +108,7 @@ export class Screenshot extends TenantOrganizationBaseEntity implements IScreens nullable: true, type: ['sqlite', 'better-sqlite3'].includes(options.type) ? 'text' : 'json' }) - apps?: string; + apps?: string | string[]; /** Additional fields */ fullUrl?: string; diff --git a/packages/core/src/time-tracking/screenshot/screenshot.service.ts b/packages/core/src/time-tracking/screenshot/screenshot.service.ts index bb5ccd7e860..f73220957e2 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.service.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.service.ts @@ -1,17 +1,21 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsWhere, Repository } from 'typeorm'; +import { GauzyAIService, ImageAnalysisResult } from '@gauzy/integration-ai'; import { IScreenshot, PermissionsEnum } from '@gauzy/contracts'; import { RequestContext } from './../../core/context'; import { TenantAwareCrudService } from './../../core/crud'; +import { IntegrationTenantService } from './../../integration-tenant/integration-tenant.service'; import { Screenshot } from './screenshot.entity'; @Injectable() export class ScreenshotService extends TenantAwareCrudService { constructor( - @InjectRepository(Screenshot) - protected readonly screenshotRepository: Repository + @InjectRepository(Screenshot) protected readonly screenshotRepository: Repository, + /** */ + private readonly _integrationTenantService: IntegrationTenantService, + private readonly _gauzyAIService: GauzyAIService, ) { super(screenshotRepository); } @@ -53,4 +57,19 @@ export class ScreenshotService extends TenantAwareCrudService { throw new ForbiddenException(); } } + + /** + * + * @param callback + */ + async analyzeScreenshot( + callback?: (analysis: ImageAnalysisResult[]) => void + ) { + const analysis: ImageAnalysisResult[] = []; + + // Call the callback function if provided + if (callback) { + callback(analysis); + } + } } diff --git a/packages/core/src/time-tracking/screenshot/screenshot.subscriber.ts b/packages/core/src/time-tracking/screenshot/screenshot.subscriber.ts index a997f75aed7..ac298770fe9 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.subscriber.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.subscriber.ts @@ -1,7 +1,9 @@ -import { EntitySubscriberInterface, EventSubscriber, LoadEvent, RemoveEvent } from "typeorm"; +import { DataSourceOptions, EntitySubscriberInterface, EventSubscriber, InsertEvent, LoadEvent, RemoveEvent } from "typeorm"; import { IScreenshot } from "@gauzy/contracts"; import { Screenshot } from "./screenshot.entity"; import { FileStorage } from "./../../core/file-storage"; +import { getConfig } from "@gauzy/config"; +import { isJsObject } from "@gauzy/common"; @EventSubscriber() export class ScreenshotSubscriber implements EntitySubscriberInterface { @@ -13,6 +15,32 @@ export class ScreenshotSubscriber implements EntitySubscriberInterface): void | Promise { + try { + if (event) { + const options: Partial = event.connection.options || getConfig().dbConnectionOptions; + if (['sqlite', 'better-sqlite3'].includes(options.type)) { + const { entity } = event; + try { + if (isJsObject(entity.apps)) { + entity.apps = JSON.stringify(entity.apps); + } + } catch (error) { + console.log('Before Insert Screenshot Activity Error:', error); + entity.apps = JSON.stringify({}); + } + } + } + } catch (error) { + console.log(error); + } + } + /** * Called after entity is loaded from the database. * @@ -37,7 +65,7 @@ export class ScreenshotSubscriber implements EntitySubscriberInterface): Promise { + async afterRemove(event: RemoveEvent): Promise { try { if (event.entityId) { console.log(`BEFORE SCREENSHOT ENTITY WITH ID ${event.entityId} REMOVED`); @@ -57,4 +85,4 @@ export class ScreenshotSubscriber implements EntitySubscriberInterface; + message?: string; + }; +} + @Injectable() export class GauzyAIService { private readonly _logger = new Logger(GauzyAIService.name); @@ -163,7 +177,7 @@ export class GauzyAIService { * @param files - Array of Buffers representing the uploaded images. * @returns Promise - The analysis result for the image. */ - public async analyzeImage(stream: Buffer, file: any): Promise { + public async analyzeImage(stream: Buffer, file: any): Promise { // Create FormData and append the image data const form = new FormData(); @@ -183,14 +197,12 @@ export class GauzyAIService { // Set request options const options = { data: form, // Set the request payload - params: { - - } + params: {} }; // Call the sendRequest function with the appropriate parameters return await firstValueFrom( - this.sendRequest('image/process', options, HttpMethodEnum.POST, headers).pipe( + this.sendRequest('image/process', options, HttpMethodEnum.POST, headers).pipe( map((resp: AxiosResponse) => resp.data) ) ); From 8bfae8aa0500e299c09c4c3300eaa26759ee8495 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:24:06 +0530 Subject: [PATCH 06/12] fix: comment unnecessary console.log --- packages/plugins/integration-ai/src/gauzy-ai.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/plugins/integration-ai/src/gauzy-ai.service.ts b/packages/plugins/integration-ai/src/gauzy-ai.service.ts index e5773407777..91ec3c29af2 100644 --- a/packages/plugins/integration-ai/src/gauzy-ai.service.ts +++ b/packages/plugins/integration-ai/src/gauzy-ai.service.ts @@ -136,14 +136,14 @@ export class GauzyAIService { /** */ const headers: AxiosRequestHeaders = customHeaders(); - console.log('Default AxiosRequestConfig Headers: %s', `${JSON.stringify(headers)}`); + // console.log('Default AxiosRequestConfig Headers: %s', `${JSON.stringify(headers)}`); // Merge the provided options with the default options const mergedOptions: AxiosRequestConfig = { ...options, // Inside your sendRequest method, use qs.stringify for custom parameter serialization paramsSerializer: (params) => { - console.log('Customize the serialization of URL parameters', params); + // console.log('Customize the serialization of URL parameters', params); if (Object.keys(params).length > 0) { // Customize the serialization of URL parameters as needed return qs.stringify(params, { arrayFormat: 'repeat' }); From 1e89b6437919da22fdef7bdecb6747ae4a9adb4e Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:34:43 +0530 Subject: [PATCH 07/12] fix(deepscan): removed unnecessary import --- .../core/src/integration-tenant/integration-tenant.service.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/integration-tenant/integration-tenant.service.ts b/packages/core/src/integration-tenant/integration-tenant.service.ts index 6851c6bb349..bdbdfff4614 100644 --- a/packages/core/src/integration-tenant/integration-tenant.service.ts +++ b/packages/core/src/integration-tenant/integration-tenant.service.ts @@ -2,14 +2,12 @@ import { BadRequestException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindManyOptions, IsNull, Not, Repository } from 'typeorm'; import { - IBasePerTenantAndOrganizationEntityModel, IIntegrationEntitySetting, IIntegrationSetting, IIntegrationTenant, IIntegrationTenantCreateInput, IIntegrationTenantFindInput, - IPagination, - IntegrationEnum + IPagination } from '@gauzy/contracts'; import { RequestContext } from 'core/context'; import { TenantAwareCrudService } from 'core/crud'; From f68f10f44b313e2938715975648ccc8d30577f4b Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Wed, 29 Nov 2023 12:56:12 +0530 Subject: [PATCH 08/12] chore(cspell): added more keywords :) --- .cspell.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.cspell.json b/.cspell.json index 51cb39a345d..0672abff46e 100644 --- a/.cspell.json +++ b/.cspell.json @@ -399,7 +399,11 @@ "gridcell", "scrollgrid", "r_liteprofile", - "r_emailaddress" + "r_emailaddress", + "Signoz", + "OTEL", + "OTLP", + "opentelemetry" ], "useGitignore": true, "ignorePaths": [ From 1894cb855eb36448c83a39ca4a8cd718a7547a27 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:29:35 +0530 Subject: [PATCH 09/12] fix: screenshot analysis asynchronous API call --- .../screenshot/screenshot.controller.ts | 72 ++++++++++--------- .../screenshot/screenshot.service.ts | 45 +++++++++--- 2 files changed, 75 insertions(+), 42 deletions(-) diff --git a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts index 20c2a1f7b54..ed51754254f 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts @@ -17,11 +17,10 @@ import * as moment from 'moment'; import * as fs from 'fs'; import { v4 as uuid } from 'uuid'; import * as Jimp from 'jimp'; -import { GauzyAIService } from '@gauzy/integration-ai'; +import { ImageAnalysisResult } from '@gauzy/integration-ai'; import { FileStorageProviderEnum, IScreenshot, - IntegrationEnum, PermissionsEnum, UploadedFile, } from '@gauzy/contracts'; @@ -35,7 +34,6 @@ import { Permissions } from './../../shared/decorators'; import { PermissionGuard, TenantPermissionGuard } from './../../shared/guards'; import { UUIDValidationPipe } from './../../shared/pipes'; import { DeleteQueryDTO } from './../../shared/dto'; -import { IntegrationTenantService } from 'integration-tenant/integration-tenant.service'; @ApiTags('Screenshot') @UseGuards(TenantPermissionGuard, PermissionGuard) @@ -44,9 +42,7 @@ import { IntegrationTenantService } from 'integration-tenant/integration-tenant. export class ScreenshotController { constructor( - private readonly _screenshotService: ScreenshotService, - private readonly _gauzyAIService: GauzyAIService, - private readonly _integrationTenantService: IntegrationTenantService + private readonly _screenshotService: ScreenshotService ) { } /** @@ -91,6 +87,8 @@ export class ScreenshotController { // Extract user information from the request context const user = RequestContext.currentUser(); + + // Initialize file storage provider and process thumbnail const provider = new FileStorage().getProvider(); let thumb: UploadedFile; @@ -98,15 +96,17 @@ export class ScreenshotController { let data: Buffer; try { - // Process the thumbnail - + // Retrieve file content from the file storage provider const fileContent = await provider.getFile(file.key); + // Create temporary files for input and output of thumbnail processing const inputFile = await tempFile('screenshot-thumb'); const outputFile = await tempFile('screenshot-thumb'); + // Write the file content to the input temporary file await fs.promises.writeFile(inputFile, fileContent); + // Resize the image using Jimp library await new Promise(async (resolve, reject) => { const image = await Jimp.read(inputFile); @@ -114,6 +114,7 @@ export class ScreenshotController { image.resize(250, Jimp.AUTO); try { + // Write the resized image to the output temporary file await image.writeAsync(outputFile); resolve(image); } catch (error) { @@ -121,44 +122,27 @@ export class ScreenshotController { } }); + // Read the resized image data from the output temporary file data = await fs.promises.readFile(outputFile); + // Remove the temporary input and output files await fs.promises.unlink(inputFile); await fs.promises.unlink(outputFile); + // Define thumbnail file name and directory const thumbName = `thumb-${file.filename}`; const thumbDir = path.dirname(file.key); + // Upload the thumbnail data to the file storage provider thumb = await provider.putFile(data, path.join(thumbDir, thumbName)); console.log(`Screenshot thumb created for employee (${user.name})`, thumb); } catch (error) { - console.log('Error while uploading screenshot into file storage provider:', error); + // Log error and throw an exception if thumbnail processing fails + console.log('Error while processing screenshot thumbnail:', error); } try { - // Retrieve integration - const integration = await this._integrationTenantService.getIntegrationByOptions({ - organizationId, - tenantId, - name: IntegrationEnum.GAUZY_AI - }); - - // Check if integration exists - if (!!integration) { - // Analyze image using Gauzy AI service - const [analysis] = await this._gauzyAIService.analyzeImage(data, file); - if (!!analysis.success) { - const [analyzeImage] = analysis.data.analysis; - - entity.isWorkRelated = analyzeImage.work; - entity.description = analyzeImage.description; - /** */ - entity.apps = analyzeImage.apps; - } - } - } catch (error) { } - - try { + // Populate entity properties for the screenshot entity.organizationId = organizationId; entity.tenantId = tenantId; entity.userId = RequestContext.currentUserId(); @@ -167,8 +151,30 @@ export class ScreenshotController { entity.storageProvider = provider.name.toUpperCase() as FileStorageProviderEnum; entity.recordedAt = entity.recordedAt ? entity.recordedAt : new Date(); - console.log({ entity }); + // Create the screenshot entity in the database const screenshot = await this._screenshotService.create(entity); + + // Analyze image using Gauzy AI service + this._screenshotService.analyzeScreenshot( + screenshot, + data, + file, + async (result: ImageAnalysisResult['data']['analysis']) => { + const [analysis] = result; + /** */ + const isWorkRelated = analysis.work; + const description = analysis.description || ''; + const apps = analysis.apps || []; + + /** */ + await this._screenshotService.update(screenshot.id, { + isWorkRelated, + description, + apps + }); + } + ); + console.log(`Screenshot created for employee (${user.name})`, screenshot); return await this._screenshotService.findOneByIdString(screenshot.id); } catch (error) { diff --git a/packages/core/src/time-tracking/screenshot/screenshot.service.ts b/packages/core/src/time-tracking/screenshot/screenshot.service.ts index f73220957e2..e48fc7973a2 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.service.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.service.ts @@ -2,7 +2,7 @@ import { ForbiddenException, Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { FindOptionsWhere, Repository } from 'typeorm'; import { GauzyAIService, ImageAnalysisResult } from '@gauzy/integration-ai'; -import { IScreenshot, PermissionsEnum } from '@gauzy/contracts'; +import { IScreenshot, IntegrationEnum, PermissionsEnum, UploadedFile } from '@gauzy/contracts'; import { RequestContext } from './../../core/context'; import { TenantAwareCrudService } from './../../core/crud'; import { IntegrationTenantService } from './../../integration-tenant/integration-tenant.service'; @@ -59,17 +59,44 @@ export class ScreenshotService extends TenantAwareCrudService { } /** - * - * @param callback + * Analyze a screenshot using Gauzy AI service. + * @param input - The input options for the screenshot. + * @param data - The screenshot data. + * @param file - The screenshot file. + * @param callback - Optional callback function to handle the analysis result. + * @returns Promise */ async analyzeScreenshot( - callback?: (analysis: ImageAnalysisResult[]) => void - ) { - const analysis: ImageAnalysisResult[] = []; + input: IScreenshot, + data: Buffer, + file: UploadedFile, + callback?: (analysis: ImageAnalysisResult['data']['analysis']) => void + ): Promise { + try { + const { organizationId } = input; + const tenantId = RequestContext.currentTenantId() || input.tenantId; + + // Retrieve integration + const integration = await this._integrationTenantService.getIntegrationByOptions({ + organizationId, + tenantId, + name: IntegrationEnum.GAUZY_AI + }); - // Call the callback function if provided - if (callback) { - callback(analysis); + // Check if integration exists + if (integration) { + // Analyze image using Gauzy AI service + const [analysis] = await this._gauzyAIService.analyzeImage(data, file); + + if (analysis.success && callback) { + // Call the callback function if provided + callback(analysis.data.analysis); + } + + return analysis; + } + } catch (error) { + // If needed, consider throwing or handling the error appropriately. } } } From 890270cf31676a81ee1c2ce28ed9c59622017129 Mon Sep 17 00:00:00 2001 From: RAHUL RATHORE <41804588+rahul-rocket@users.noreply.github.com> Date: Wed, 29 Nov 2023 13:34:20 +0530 Subject: [PATCH 10/12] fix: if result will be available then update screenshot record --- .../screenshot/screenshot.controller.ts | 26 ++++++++++--------- .../screenshot/screenshot.service.ts | 2 +- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts index ed51754254f..65e2b352604 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.controller.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.controller.ts @@ -160,18 +160,20 @@ export class ScreenshotController { data, file, async (result: ImageAnalysisResult['data']['analysis']) => { - const [analysis] = result; - /** */ - const isWorkRelated = analysis.work; - const description = analysis.description || ''; - const apps = analysis.apps || []; - - /** */ - await this._screenshotService.update(screenshot.id, { - isWorkRelated, - description, - apps - }); + if (result) { + const [analysis] = result; + /** */ + const isWorkRelated = analysis.work; + const description = analysis.description || ''; + const apps = analysis.apps || []; + + /** */ + await this._screenshotService.update(screenshot.id, { + isWorkRelated, + description, + apps + }); + } } ); diff --git a/packages/core/src/time-tracking/screenshot/screenshot.service.ts b/packages/core/src/time-tracking/screenshot/screenshot.service.ts index e48fc7973a2..32d4c40398c 100644 --- a/packages/core/src/time-tracking/screenshot/screenshot.service.ts +++ b/packages/core/src/time-tracking/screenshot/screenshot.service.ts @@ -84,7 +84,7 @@ export class ScreenshotService extends TenantAwareCrudService { }); // Check if integration exists - if (integration) { + if (!!integration) { // Analyze image using Gauzy AI service const [analysis] = await this._gauzyAIService.analyzeImage(data, file); From 90a2e80e7db6d729206d1e0af97ee944601c8397 Mon Sep 17 00:00:00 2001 From: Ruslan Konviser Date: Wed, 29 Nov 2023 10:56:35 +0100 Subject: [PATCH 11/12] feat: more improvements to OTEL / SigNoz integration --- .deploy/api/Dockerfile | 4 +++ .deploy/k8s/k8s-manifest.civo.demo.yaml | 4 +++ .deploy/k8s/k8s-manifest.civo.prod.yaml | 4 +++ .deploy/k8s/k8s-manifest.civo.stage.yaml | 4 +++ .deploy/k8s/k8s-manifest.cw.demo.yaml | 4 +++ .deploy/k8s/k8s-manifest.cw.prod.yaml | 4 +++ .deploy/k8s/k8s-manifest.cw.stage.yaml | 4 +++ .deploy/k8s/k8s-manifest.demo.yaml | 4 +++ .deploy/k8s/k8s-manifest.prod.yaml | 4 +++ .deploy/k8s/k8s-manifest.stage.yaml | 4 +++ .env.compose | 2 ++ .env.docker | 2 ++ .env.local | 2 ++ .env.sample | 2 ++ .github/workflows/deploy-civo-demo.yml | 2 ++ .github/workflows/deploy-civo-prod.yml | 2 ++ .github/workflows/deploy-civo-stage.yml | 2 ++ .github/workflows/deploy-cw-demo.yml | 2 ++ .github/workflows/deploy-cw-prod.yml | 2 ++ .github/workflows/deploy-cw-stage.yml | 2 ++ .github/workflows/deploy-do-demo.yml | 2 ++ .github/workflows/deploy-do-prod.yml | 2 ++ .github/workflows/deploy-do-stage.yml | 2 ++ docker-compose.demo.yml | 2 ++ docker-compose.yml | 2 ++ packages/core/package.json | 4 ++- .../core/src/bootstrap/bootstrap.module.ts | 13 ++++++---- packages/core/src/bootstrap/index.ts | 11 +++++--- packages/core/src/bootstrap/tracer.ts | 25 +++++++++++++------ 29 files changed, 107 insertions(+), 16 deletions(-) diff --git a/.deploy/api/Dockerfile b/.deploy/api/Dockerfile index b0621303916..7a53754c0e6 100644 --- a/.deploy/api/Dockerfile +++ b/.deploy/api/Dockerfile @@ -84,7 +84,9 @@ ARG APP_EMAIL_CONFIRMATION_URL ARG APP_MAGIC_SIGN_URL ARG COMPANY_LINK ARG COMPANY_NAME +ARG OTEL_ENABLED ARG OTEL_EXPORTER_OTLP_HEADERS +ARG OTEL_EXPORTER_OTLP_TRACES_ENDPOINT FROM node:18-alpine3.17 AS dependencies @@ -333,6 +335,8 @@ ENV APP_EMAIL_CONFIRMATION_URL=${APP_EMAIL_CONFIRMATION_URL} ENV APP_MAGIC_SIGN_URL=${APP_MAGIC_SIGN_URL} ENV COMPANY_LINK=${COMPANY_LINK} ENV COMPANY_NAME=${COMPANY_NAME} +ENV OTEL_ENABLED=${OTEL_ENABLED} +ENV OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT} ENV OTEL_EXPORTER_OTLP_HEADERS=${OTEL_EXPORTER_OTLP_HEADERS} EXPOSE ${API_PORT} diff --git a/.deploy/k8s/k8s-manifest.civo.demo.yaml b/.deploy/k8s/k8s-manifest.civo.demo.yaml index d6b7fd21b05..51db159fa41 100644 --- a/.deploy/k8s/k8s-manifest.civo.demo.yaml +++ b/.deploy/k8s/k8s-manifest.civo.demo.yaml @@ -71,6 +71,10 @@ spec: value: 'refreshSecretKey' - name: JWT_REFRESH_TOKEN_EXPIRATION_TIME value: '86400' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' ports: diff --git a/.deploy/k8s/k8s-manifest.civo.prod.yaml b/.deploy/k8s/k8s-manifest.civo.prod.yaml index 17585a99a85..81c5c9249c9 100644 --- a/.deploy/k8s/k8s-manifest.civo.prod.yaml +++ b/.deploy/k8s/k8s-manifest.civo.prod.yaml @@ -173,6 +173,10 @@ spec: value: '$JITSU_SERVER_URL' - name: JITSU_SERVER_WRITE_KEY value: '$JITSU_SERVER_WRITE_KEY' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' - name: GAUZY_GITHUB_CLIENT_ID diff --git a/.deploy/k8s/k8s-manifest.civo.stage.yaml b/.deploy/k8s/k8s-manifest.civo.stage.yaml index ba531cb2bf7..c183d414117 100644 --- a/.deploy/k8s/k8s-manifest.civo.stage.yaml +++ b/.deploy/k8s/k8s-manifest.civo.stage.yaml @@ -167,6 +167,10 @@ spec: value: '$JITSU_SERVER_URL' - name: JITSU_SERVER_WRITE_KEY value: '$JITSU_SERVER_WRITE_KEY' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' - name: GAUZY_GITHUB_CLIENT_ID diff --git a/.deploy/k8s/k8s-manifest.cw.demo.yaml b/.deploy/k8s/k8s-manifest.cw.demo.yaml index 5a95211552f..d0b180e2e43 100644 --- a/.deploy/k8s/k8s-manifest.cw.demo.yaml +++ b/.deploy/k8s/k8s-manifest.cw.demo.yaml @@ -86,6 +86,10 @@ spec: value: 'refreshSecretKey' - name: JWT_REFRESH_TOKEN_EXPIRATION_TIME value: '86400' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' ports: diff --git a/.deploy/k8s/k8s-manifest.cw.prod.yaml b/.deploy/k8s/k8s-manifest.cw.prod.yaml index 13a4298d6be..b3a3dd39515 100644 --- a/.deploy/k8s/k8s-manifest.cw.prod.yaml +++ b/.deploy/k8s/k8s-manifest.cw.prod.yaml @@ -199,6 +199,10 @@ spec: value: '$JITSU_SERVER_URL' - name: JITSU_SERVER_WRITE_KEY value: '$JITSU_SERVER_WRITE_KEY' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' - name: GAUZY_GITHUB_CLIENT_ID diff --git a/.deploy/k8s/k8s-manifest.cw.stage.yaml b/.deploy/k8s/k8s-manifest.cw.stage.yaml index bd7215d2251..869ea9fd29e 100644 --- a/.deploy/k8s/k8s-manifest.cw.stage.yaml +++ b/.deploy/k8s/k8s-manifest.cw.stage.yaml @@ -193,6 +193,10 @@ spec: value: '$JITSU_SERVER_URL' - name: JITSU_SERVER_WRITE_KEY value: '$JITSU_SERVER_WRITE_KEY' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' - name: GAUZY_GITHUB_CLIENT_ID diff --git a/.deploy/k8s/k8s-manifest.demo.yaml b/.deploy/k8s/k8s-manifest.demo.yaml index 2c9678b7591..f537d7a70c2 100644 --- a/.deploy/k8s/k8s-manifest.demo.yaml +++ b/.deploy/k8s/k8s-manifest.demo.yaml @@ -89,6 +89,10 @@ spec: value: 'refreshSecretKey' - name: JWT_REFRESH_TOKEN_EXPIRATION_TIME value: '86400' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' ports: diff --git a/.deploy/k8s/k8s-manifest.prod.yaml b/.deploy/k8s/k8s-manifest.prod.yaml index 752fc908055..683e4aab6a8 100644 --- a/.deploy/k8s/k8s-manifest.prod.yaml +++ b/.deploy/k8s/k8s-manifest.prod.yaml @@ -191,6 +191,10 @@ spec: value: '$JITSU_SERVER_URL' - name: JITSU_SERVER_WRITE_KEY value: '$JITSU_SERVER_WRITE_KEY' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' - name: GAUZY_GITHUB_CLIENT_ID diff --git a/.deploy/k8s/k8s-manifest.stage.yaml b/.deploy/k8s/k8s-manifest.stage.yaml index 302f9fbc5b2..54aa2a6bba9 100644 --- a/.deploy/k8s/k8s-manifest.stage.yaml +++ b/.deploy/k8s/k8s-manifest.stage.yaml @@ -185,6 +185,10 @@ spec: value: '$JITSU_SERVER_URL' - name: JITSU_SERVER_WRITE_KEY value: '$JITSU_SERVER_WRITE_KEY' + - name: OTEL_ENABLED + value: '$OTEL_ENABLED' + - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT + value: '$OTEL_EXPORTER_OTLP_TRACES_ENDPOINT' - name: OTEL_EXPORTER_OTLP_HEADERS value: '$OTEL_EXPORTER_OTLP_HEADERS' - name: GAUZY_GITHUB_CLIENT_ID diff --git a/.env.compose b/.env.compose index feaea94d317..b7e1cf99958 100644 --- a/.env.compose +++ b/.env.compose @@ -360,6 +360,8 @@ JITSU_SERVER_ECHO_EVENTS= # Signoz Configuration OTEL_EXPORTER_OTLP_HEADERS= +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT= +OTEL_ENABLED=false # Platform Logo resource URL (SVG is Recommended) PLATFORM_LOGO='assets/images/logos/logo_Gauzy.svg' diff --git a/.env.docker b/.env.docker index 97905bc321a..e13f850d486 100644 --- a/.env.docker +++ b/.env.docker @@ -345,6 +345,8 @@ JITSU_SERVER_ECHO_EVENTS= # Signoz Configuration OTEL_EXPORTER_OTLP_HEADERS= +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT= +OTEL_ENABLED=false # Platform Logo resource URL (SVG is Recommended) PLATFORM_LOGO='assets/images/logos/logo_Gauzy.svg' diff --git a/.env.local b/.env.local index b2b47c44879..42d22bee9f7 100644 --- a/.env.local +++ b/.env.local @@ -332,6 +332,8 @@ JITSU_SERVER_ECHO_EVENTS= # Signoz Configuration OTEL_EXPORTER_OTLP_HEADERS= +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT= +OTEL_ENABLED=false # Platform Logo resource URL (SVG is Recommended) PLATFORM_LOGO='assets/images/logos/logo_Gauzy.svg' diff --git a/.env.sample b/.env.sample index a2891dbb385..2426d8ec8e8 100644 --- a/.env.sample +++ b/.env.sample @@ -353,6 +353,8 @@ JITSU_SERVER_ECHO_EVENTS= # Signoz Configuration OTEL_EXPORTER_OTLP_HEADERS= +OTEL_EXPORTER_OTLP_TRACES_ENDPOINT= +OTEL_ENABLED=false # Platform Logo resource URL (SVG is Recommended) PLATFORM_LOGO='assets/images/logos/logo_Gauzy.svg' diff --git a/.github/workflows/deploy-civo-demo.yml b/.github/workflows/deploy-civo-demo.yml index 65b9abb1c0c..3517ffd79e4 100644 --- a/.github/workflows/deploy-civo-demo.yml +++ b/.github/workflows/deploy-civo-demo.yml @@ -42,6 +42,8 @@ jobs: SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' # we need this step because for now we just use :latest tag diff --git a/.github/workflows/deploy-civo-prod.yml b/.github/workflows/deploy-civo-prod.yml index 55185fd91ef..c5ca69a73a2 100644 --- a/.github/workflows/deploy-civo-prod.yml +++ b/.github/workflows/deploy-civo-prod.yml @@ -102,6 +102,8 @@ jobs: PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' diff --git a/.github/workflows/deploy-civo-stage.yml b/.github/workflows/deploy-civo-stage.yml index 73ad88dfa59..4cc2b470929 100644 --- a/.github/workflows/deploy-civo-stage.yml +++ b/.github/workflows/deploy-civo-stage.yml @@ -103,6 +103,8 @@ jobs: PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' diff --git a/.github/workflows/deploy-cw-demo.yml b/.github/workflows/deploy-cw-demo.yml index b7816863796..14dbcf079f7 100644 --- a/.github/workflows/deploy-cw-demo.yml +++ b/.github/workflows/deploy-cw-demo.yml @@ -42,6 +42,8 @@ jobs: SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' # we need this step because for now we just use :latest tag diff --git a/.github/workflows/deploy-cw-prod.yml b/.github/workflows/deploy-cw-prod.yml index b06178aeccc..05e07332bcc 100644 --- a/.github/workflows/deploy-cw-prod.yml +++ b/.github/workflows/deploy-cw-prod.yml @@ -102,6 +102,8 @@ jobs: PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' diff --git a/.github/workflows/deploy-cw-stage.yml b/.github/workflows/deploy-cw-stage.yml index 98ed42b7a0d..7ddbdf8eb4c 100644 --- a/.github/workflows/deploy-cw-stage.yml +++ b/.github/workflows/deploy-cw-stage.yml @@ -103,6 +103,8 @@ jobs: PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' diff --git a/.github/workflows/deploy-do-demo.yml b/.github/workflows/deploy-do-demo.yml index 6909e6da912..c91b2845a12 100644 --- a/.github/workflows/deploy-do-demo.yml +++ b/.github/workflows/deploy-do-demo.yml @@ -38,6 +38,8 @@ jobs: SENTRY_TRACES_SAMPLE_RATE: '${{ secrets.SENTRY_TRACES_SAMPLE_RATE }}' SENTRY_HTTP_TRACING_ENABLED: '${{ secrets.SENTRY_HTTP_TRACING_ENABLED }}' SENTRY_POSTGRES_TRACKING_ENABLED: '${{ secrets.SENTRY_POSTGRES_TRACKING_ENABLED }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' # we need this step because for now we just use :latest tag diff --git a/.github/workflows/deploy-do-prod.yml b/.github/workflows/deploy-do-prod.yml index 8567581f984..e924bed81fa 100644 --- a/.github/workflows/deploy-do-prod.yml +++ b/.github/workflows/deploy-do-prod.yml @@ -97,6 +97,8 @@ jobs: PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' diff --git a/.github/workflows/deploy-do-stage.yml b/.github/workflows/deploy-do-stage.yml index 44eb14d620d..72a9371601c 100644 --- a/.github/workflows/deploy-do-stage.yml +++ b/.github/workflows/deploy-do-stage.yml @@ -98,6 +98,8 @@ jobs: PM2_PUBLIC_KEY: '${{ secrets.PM2_PUBLIC_KEY }}' JITSU_SERVER_URL: '${{ secrets.JITSU_SERVER_URL }}' JITSU_SERVER_WRITE_KEY: '${{ secrets.JITSU_SERVER_WRITE_KEY }}' + OTEL_ENABLED: '${{ secrets.OTEL_ENABLED }}' + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: '${{ secrets.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT }}' OTEL_EXPORTER_OTLP_HEADERS: '${{ secrets.OTEL_EXPORTER_OTLP_HEADERS }}' GAUZY_GITHUB_CLIENT_ID: '${{ secrets.GAUZY_GITHUB_CLIENT_ID }}' GAUZY_GITHUB_CLIENT_SECRET: '${{ secrets.GAUZY_GITHUB_CLIENT_SECRET }}' diff --git a/docker-compose.demo.yml b/docker-compose.demo.yml index fb4b9baf570..3e90cbb6785 100644 --- a/docker-compose.demo.yml +++ b/docker-compose.demo.yml @@ -220,7 +220,9 @@ services: SENTRY_POSTGRES_TRACKING_ENABLED: ${SENTRY_POSTGRES_TRACKING_ENABLED:-} JITSU_SERVER_URL: ${JITSU_SERVER_URL:-} JITSU_SERVER_WRITE_KEY: ${JITSU_SERVER_WRITE_KEY:-} + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-} OTEL_EXPORTER_OTLP_HEADERS: ${OTEL_EXPORTER_OTLP_HEADERS:-} + OTEL_ENABLED: ${OTEL_ENABLED:-} GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} GAUZY_GITHUB_CLIENT_SECRET: ${GAUZY_GITHUB_CLIENT_SECRET:-} GAUZY_GITHUB_WEBHOOK_URL: ${GAUZY_GITHUB_WEBHOOK_URL:-} diff --git a/docker-compose.yml b/docker-compose.yml index c7359d8956f..9d36f48dad9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -226,7 +226,9 @@ services: SENTRY_HTTP_TRACING_ENABLED: ${SENTRY_HTTP_TRACING_ENABLED:-} SENTRY_POSTGRES_TRACKING_ENABLED: ${SENTRY_POSTGRES_TRACKING_ENABLED:-} JITSU_SERVER_URL: ${JITSU_SERVER_URL:-} + OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: ${OTEL_EXPORTER_OTLP_TRACES_ENDPOINT:-} OTEL_EXPORTER_OTLP_HEADERS: ${OTEL_EXPORTER_OTLP_HEADERS:-} + OTEL_ENABLED: ${OTEL_ENABLED:-} JITSU_SERVER_WRITE_KEY: ${JITSU_SERVER_WRITE_KEY:-} GAUZY_GITHUB_CLIENT_ID: ${GAUZY_GITHUB_CLIENT_ID:-} GAUZY_GITHUB_CLIENT_SECRET: ${GAUZY_GITHUB_CLIENT_SECRET:-} diff --git a/packages/core/package.json b/packages/core/package.json index bab4768ac53..4d51e10aeac 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -163,7 +163,9 @@ "@opentelemetry/api": "^1.7.0", "@opentelemetry/sdk-node": "^0.45.1", "@opentelemetry/auto-instrumentations-node": "^0.40.1", - "@opentelemetry/exporter-trace-otlp-http": "^0.45.1" + "@opentelemetry/exporter-trace-otlp-http": "^0.45.1", + "@opentelemetry/resources": "^1.18.1", + "@opentelemetry/semantic-conventions": "^1.18.1" }, "devDependencies": { "@nestjs/cli": "^9.1.5", diff --git a/packages/core/src/bootstrap/bootstrap.module.ts b/packages/core/src/bootstrap/bootstrap.module.ts index 2ea97be957b..5eb320bef29 100644 --- a/packages/core/src/bootstrap/bootstrap.module.ts +++ b/packages/core/src/bootstrap/bootstrap.module.ts @@ -1,3 +1,4 @@ +import tracer from './tracer'; import { MiddlewareConsumer, Module, NestModule, OnApplicationShutdown } from '@nestjs/common'; import { ConfigModule, getConfig } from '@gauzy/config'; import { PluginModule } from '@gauzy/plugin'; @@ -5,7 +6,6 @@ import { AppModule } from './../app.module'; import { HealthIndicatorModule } from '../health-indicator'; import { Logger, LoggerModule } from '../logger'; import { SharedModule } from './../shared/shared.module'; -import tracer from './tracer'; @Module({ imports: [ @@ -31,10 +31,13 @@ export class BootstrapModule implements NestModule, OnApplicationShutdown { if (signal === 'SIGTERM') { Logger.log('SIGTERM shutting down. Please wait...'); - tracer - .shutdown() - .then(() => console.log('Tracing terminated')) - .catch((error) => console.log('Error terminating tracing', error)); + if (process.env.OTEL_ENABLED) { + try { + await tracer.shutdown(); + } catch (error) { + console.log('Error terminating tracing', error); + } + } } } } diff --git a/packages/core/src/bootstrap/index.ts b/packages/core/src/bootstrap/index.ts index 6f55894d3c9..50ce9a824db 100644 --- a/packages/core/src/bootstrap/index.ts +++ b/packages/core/src/bootstrap/index.ts @@ -1,4 +1,5 @@ // import * as csurf from 'csurf'; +import tracer from './tracer'; import { ConflictException, INestApplication, Type } from '@nestjs/common'; import { NestFactory, Reflector } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; @@ -20,11 +21,15 @@ import { AppService } from '../app.service'; import { AppModule } from '../app.module'; import { AuthGuard } from './../shared/guards'; import { SharedModule } from './../shared/shared.module'; -import tracer from './tracer'; export async function bootstrap(pluginConfig?: Partial): Promise { - // Start tracing using Signoz first - await tracer.start(); + if (process.env.OTEL_ENABLED) { + // Start tracing using Signoz first + await tracer.start(); + console.log('Tracing started'); + } else { + console.log('Tracing not enabled'); + } const config = await registerPluginConfig(pluginConfig); diff --git a/packages/core/src/bootstrap/tracer.ts b/packages/core/src/bootstrap/tracer.ts index aa723919c5c..20303ddd5a1 100644 --- a/packages/core/src/bootstrap/tracer.ts +++ b/packages/core/src/bootstrap/tracer.ts @@ -3,18 +3,27 @@ import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node'; import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http'; import { Resource } from '@opentelemetry/resources'; -import * as opentelemetry from '@opentelemetry/sdk-node'; +import { NodeSDK } from '@opentelemetry/sdk-node'; import { SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; +// Important for Tracing: +// 1. Define env var - OTEL_EXPORTER_OTLP_HEADERS="signoz-access-token=" +// 2. Set OTEL_ENABLED=true +// Read docs: https://signoz.io/docs/instrumentation/nestjs/ + +// Enable logging for debugging +// import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api'; +// diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.DEBUG); + // Configure the SDK to export telemetry data to the console // Enable all auto-instrumentations from the meta package const exporterOptions = { - // TODO: use env var for Signoz URL - url: 'https://ingest.us.signoz.cloud:443/v1/traces' + url: process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT || 'https://ingest.us.signoz.cloud:443/v1/traces' }; const traceExporter = new OTLPTraceExporter(exporterOptions); -const sdk = new opentelemetry.NodeSDK({ + +const sdk = new NodeSDK({ traceExporter, instrumentations: [getNodeAutoInstrumentations()], resource: new Resource({ @@ -22,8 +31,10 @@ const sdk = new opentelemetry.NodeSDK({ }) }); -// initialize the SDK and register with the OpenTelemetry API -// this enables the API to record telemetry -sdk.start(); +if (process.env.OTEL_ENABLED) { + // initialize the SDK and register with the OpenTelemetry API + // this enables the API to record telemetry + sdk.start(); +} export default sdk; From f4dcbfcaf1c78d11cc8c17e96f17d46f3ec4a542 Mon Sep 17 00:00:00 2001 From: coderNadir <78637183+coderNadir@users.noreply.github.com> Date: Wed, 29 Nov 2023 10:59:10 +0100 Subject: [PATCH 12/12] Style success/failed sign in workspace components (#7205) * feat: #7202 style success/failed sign in workspace components * feat: #7202 add translation * feat: #7202 remove commented code for development purpose --- .../magic-login-workspace.component.html | 19 +++++---- .../magic-login-workspace.component.scss | 41 +++++++++++++------ apps/gauzy/src/assets/i18n/ar.json | 7 +++- apps/gauzy/src/assets/i18n/bg.json | 7 +++- apps/gauzy/src/assets/i18n/de.json | 7 +++- apps/gauzy/src/assets/i18n/en.json | 7 +++- apps/gauzy/src/assets/i18n/es.json | 7 +++- apps/gauzy/src/assets/i18n/fr.json | 7 +++- apps/gauzy/src/assets/i18n/he.json | 7 +++- apps/gauzy/src/assets/i18n/it.json | 7 +++- apps/gauzy/src/assets/i18n/nl.json | 7 +++- apps/gauzy/src/assets/i18n/pl.json | 7 +++- apps/gauzy/src/assets/i18n/pt.json | 7 +++- apps/gauzy/src/assets/i18n/ru.json | 7 +++- apps/gauzy/src/assets/i18n/zh.json | 7 +++- 15 files changed, 118 insertions(+), 33 deletions(-) diff --git a/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.html b/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.html index ba3b531603c..63fb8dbbeab 100644 --- a/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.html +++ b/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.html @@ -12,20 +12,25 @@ -
+
+ -
+
-

Error

-

The magic code or link you provided has expired or is invalid. Please initiate the process again.

+

{{ "WORKSPACES.FAIL_SIGNIN_TITLE" | translate }}

+

{{ "WORKSPACES.FAIL_SIGNIN_SUB_TITLE" | translate }}

-
+
-

Success

-

Congratulations! You've successfully signed in to the workspace. Please hold on for a moment while we prepare your workspace. We appreciate your patience and thank you for choosing us!

+

{{ "WORKSPACES.SUCCESS_SIGNIN_TITLE" | translate }}

+

{{ "WORKSPACES.SUCCESS_SIGNIN_SUB_TITLE" | translate }}

+ +
+

{{ "WORKSPACES.THANKING_TEXT" | translate }}

+
diff --git a/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.scss b/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.scss index 1786069a758..c4d860d3c1b 100644 --- a/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.scss +++ b/apps/gauzy/src/app/auth/magic-login-workspace/magic-login-workspace.component.scss @@ -1,27 +1,42 @@ +@import "themes"; + +.ever-logo-svg { + margin-top: 2rem; + align-self: center; +} + .message-container { display: flex; - align-items: center; - padding: 20px; - border-radius: 8px; + flex-direction: column; + justify-content: space-between; + background: nb-theme(gauzy-card-2); + border-radius: nb-theme(border-radius); + box-sizing: border-box; + padding: 30px; + width: 50vw; + height: 75vh; margin: 10px 0; } -.error { - background-color: #FFD6D6; - border: 1px solid #FF4040; + +.error .title { color: #FF4040; } -.success { - background-color: #D6FFD6; - border: 1px solid #40FF40; - color: #40FF40; +.title { + font-weight: 600; + font-size: 1.1rem; +} +.sub-title { + font-size: .8rem; + color: var(--text-hint-color); +} +.thanking-text { + text-align: center; + font-size: .8rem; } .icon { font-size: 24px; margin-right: 15px; } -.text { - flex-grow: 1; -} h3 { margin-bottom: 10px; } diff --git a/apps/gauzy/src/assets/i18n/ar.json b/apps/gauzy/src/assets/i18n/ar.json index cd90f99a7fc..7a892f51a24 100644 --- a/apps/gauzy/src/assets/i18n/ar.json +++ b/apps/gauzy/src/assets/i18n/ar.json @@ -4502,7 +4502,12 @@ "SING_ANOTHER_WORKSPACE": "تسجيل الدخول إلى مساحة عمل أخرى", "CREATE_NEW_WORKSPACE": "أنشئ مساحة عمل جديدة", "FIND_WORKSPACE": "ابحث عن مساحة عمل" - } + }, + "SUCCESS_SIGNIN_TITLE": ".تهانينا! لقد قمت بتسجيل الدخول بنجاح إلى مساحة العمل الخاصة بك", + "SUCCESS_SIGNIN_SUB_TITLE": "...يرجى أن تكون صبورًا بينما نقوم بإعداد مساحة العمل", + "FAIL_SIGNIN_TITLE": ".خطأ! رمز السحر أو الرابط الذي قدمته منتهي الصلاحية أو غير صالح", + "FAIL_SIGNIN_SUB_TITLE": ".يرجى بدء العملية من جديد", + "THANKING_TEXT": ".شكرًا لاختيارك لنا" }, "NO_IMAGE": { "ADD_DROP": "أضف أو أزل الصورة", diff --git a/apps/gauzy/src/assets/i18n/bg.json b/apps/gauzy/src/assets/i18n/bg.json index 1a1a65e10dc..f3eea21944a 100644 --- a/apps/gauzy/src/assets/i18n/bg.json +++ b/apps/gauzy/src/assets/i18n/bg.json @@ -4428,7 +4428,12 @@ "SING_ANOTHER_WORKSPACE": "Sign in another workspace", "CREATE_NEW_WORKSPACE": "Create a new workspace", "FIND_WORKSPACE": "Find workspace" - } + }, + "SUCCESS_SIGNIN_TITLE": "Поздравления! Влезли сте успешно в работното си пространство.", + "SUCCESS_SIGNIN_SUB_TITLE": "Моля, бъдете търпеливи, докато подготвяме работното пространство...", + "FAIL_SIGNIN_TITLE": "Грешка! Магическият код или връзката, която предоставихте, е изтекъл или невалиден.", + "FAIL_SIGNIN_SUB_TITLE": "Моля, инициирайте процеса отново.", + "THANKING_TEXT": "Благодарим ви, че ни избрахте." }, "NO_IMAGE": { "ADD_DROP": "Add or Drop Image", diff --git a/apps/gauzy/src/assets/i18n/de.json b/apps/gauzy/src/assets/i18n/de.json index 08c7c6bf99b..495ac5665dd 100644 --- a/apps/gauzy/src/assets/i18n/de.json +++ b/apps/gauzy/src/assets/i18n/de.json @@ -4499,7 +4499,12 @@ "SING_ANOTHER_WORKSPACE": "Melden Sie sich in einem anderen Arbeitsbereich an", "CREATE_NEW_WORKSPACE": "Erstelle einen neuen Arbeitsbereich", "FIND_WORKSPACE": "Finde Arbeitsplatz" - } + }, + "SUCCESS_SIGNIN_TITLE": "Herzlichen Glückwunsch! Sie haben sich erfolgreich in Ihren Arbeitsbereich eingeloggt.", + "SUCCESS_SIGNIN_SUB_TITLE": "Bitte haben Sie Geduld, während wir den Arbeitsbereich vorbereiten...", + "FAIL_SIGNIN_TITLE": "Fehler! Der magische Code oder Link, den Sie bereitgestellt haben, ist abgelaufen oder ungültig.", + "FAIL_SIGNIN_SUB_TITLE": "Bitte starten Sie den Prozess erneut.", + "THANKING_TEXT": "Vielen Dank, dass Sie uns gewählt haben." }, "NO_IMAGE": { "ADD_DROP": "Bild hinzufügen oder entfernen", diff --git a/apps/gauzy/src/assets/i18n/en.json b/apps/gauzy/src/assets/i18n/en.json index 0345576fde9..f8db85e0350 100644 --- a/apps/gauzy/src/assets/i18n/en.json +++ b/apps/gauzy/src/assets/i18n/en.json @@ -4584,7 +4584,12 @@ "SING_ANOTHER_WORKSPACE": "Sign in another workspace", "CREATE_NEW_WORKSPACE": "Create a new workspace", "FIND_WORKSPACE": "Find workspace" - } + }, + "SUCCESS_SIGNIN_TITLE": "Congrats! you’ve successfully signed in to your workspace.", + "SUCCESS_SIGNIN_SUB_TITLE": "Please be patient while we are preparing the workspace...", + "FAIL_SIGNIN_TITLE": "Error! the magic code or link you provided is expired or invalid.", + "FAIL_SIGNIN_SUB_TITLE": "Please initiate the process again.", + "THANKING_TEXT": "Thank you for choosing us." }, "NO_IMAGE": { "ADD_DROP": "Add or Drop Image", diff --git a/apps/gauzy/src/assets/i18n/es.json b/apps/gauzy/src/assets/i18n/es.json index 764b931b58e..8c29ec4e674 100644 --- a/apps/gauzy/src/assets/i18n/es.json +++ b/apps/gauzy/src/assets/i18n/es.json @@ -4505,7 +4505,12 @@ "SING_ANOTHER_WORKSPACE": "Iniciar sesión en otro espacio de trabajo", "CREATE_NEW_WORKSPACE": "Crear un nuevo espacio de trabajo", "FIND_WORKSPACE": "Encuentra espacio de trabajo." - } + }, + "SUCCESS_SIGNIN_TITLE": "¡Felicidades! Has iniciado sesión con éxito en tu espacio de trabajo.", + "SUCCESS_SIGNIN_SUB_TITLE": "Por favor, ten paciencia mientras preparamos el espacio de trabajo...", + "FAIL_SIGNIN_TITLE": "¡Error! El código mágico o el enlace que proporcionaste ha expirado o no es válido.", + "FAIL_SIGNIN_SUB_TITLE": "Por favor, inicia el proceso de nuevo.", + "THANKING_TEXT": "Gracias por elegirnos." }, "NO_IMAGE": { "ADD_DROP": "Agregar o Eliminar Imagen", diff --git a/apps/gauzy/src/assets/i18n/fr.json b/apps/gauzy/src/assets/i18n/fr.json index 12d095120f9..45678a6c719 100644 --- a/apps/gauzy/src/assets/i18n/fr.json +++ b/apps/gauzy/src/assets/i18n/fr.json @@ -4504,7 +4504,12 @@ "SING_ANOTHER_WORKSPACE": "Connectez-vous à un autre espace de travail.", "CREATE_NEW_WORKSPACE": "Créez un nouvel espace de travail.", "FIND_WORKSPACE": "Trouver un espace de travail" - } + }, + "SUCCESS_SIGNIN_TITLE": "Félicitations ! Vous avez réussi à vous connecter à votre espace de travail.", + "SUCCESS_SIGNIN_SUB_TITLE": "Veuillez patienter pendant que nous préparons l'espace de travail...", + "FAIL_SIGNIN_TITLE": "Erreur ! Le code magique ou le lien que vous avez fourni a expiré ou n'est pas valide.", + "FAIL_SIGNIN_SUB_TITLE": "Veuillez initier le processus à nouveau.", + "THANKING_TEXT": "Merci de nous avoir choisis." }, "NO_IMAGE": { "ADD_DROP": "Ajouter ou supprimer une image", diff --git a/apps/gauzy/src/assets/i18n/he.json b/apps/gauzy/src/assets/i18n/he.json index 273d9f1e1f8..46d042315cd 100644 --- a/apps/gauzy/src/assets/i18n/he.json +++ b/apps/gauzy/src/assets/i18n/he.json @@ -4426,7 +4426,12 @@ "SING_ANOTHER_WORKSPACE": "Sign in another workspace", "CREATE_NEW_WORKSPACE": "Create a new workspace", "FIND_WORKSPACE": "Find workspace" - } + }, + "SUCCESS_SIGNIN_TITLE": ".מזל טוב! נכנסת למערכת בהצלחה", + "SUCCESS_SIGNIN_SUB_TITLE": "...אנא התאזרי בסבלנות בזמן שאנו מכינים את המערכת", + "FAIL_SIGNIN_TITLE": ".שגיאה! קוד הקסם או הקישור שסיפקת פג או אינו תקף", + "FAIL_SIGNIN_SUB_TITLE": ".אנא התחל/י את התהליך מחדש", + "THANKING_TEXT": ".תודה על בחירתך בנו" }, "NO_IMAGE": { "ADD_DROP": "Add or Drop Image", diff --git a/apps/gauzy/src/assets/i18n/it.json b/apps/gauzy/src/assets/i18n/it.json index c3a706a90ed..dcb5f566aa5 100644 --- a/apps/gauzy/src/assets/i18n/it.json +++ b/apps/gauzy/src/assets/i18n/it.json @@ -4503,7 +4503,12 @@ "SING_ANOTHER_WORKSPACE": "Accedi a un altro spazio di lavoro", "CREATE_NEW_WORKSPACE": "Crea un nuovo spazio di lavoro", "FIND_WORKSPACE": "Trova uno spazio di lavoro" - } + }, + "SUCCESS_SIGNIN_TITLE": "Congratulazioni! Hai effettuato l'accesso con successo al tuo spazio di lavoro.", + "SUCCESS_SIGNIN_SUB_TITLE": "Per favore, sii paziente mentre prepariamo lo spazio di lavoro...", + "FAIL_SIGNIN_TITLE": "Errore! Il codice magico o il link che hai fornito è scaduto o non è valido.", + "FAIL_SIGNIN_SUB_TITLE": "Per favore, inizia il processo di nuovo.", + "THANKING_TEXT": "Grazie per averci scelto." }, "NO_IMAGE": { "ADD_DROP": "Aggiungi o Rimuovi Immagine", diff --git a/apps/gauzy/src/assets/i18n/nl.json b/apps/gauzy/src/assets/i18n/nl.json index 5c4d6df48aa..5080ae749df 100644 --- a/apps/gauzy/src/assets/i18n/nl.json +++ b/apps/gauzy/src/assets/i18n/nl.json @@ -4503,7 +4503,12 @@ "SING_ANOTHER_WORKSPACE": "Meld je aan bij een andere werkruimte", "CREATE_NEW_WORKSPACE": "Maak een nieuwe werkruimte", "FIND_WORKSPACE": "Vind een werkruimte" - } + }, + "SUCCESS_SIGNIN_TITLE": "Gefeliciteerd! Je bent succesvol ingelogd op je werkruimte.", + "SUCCESS_SIGNIN_SUB_TITLE": "Wees geduldig terwijl we de werkruimte voorbereiden...", + "FAIL_SIGNIN_TITLE": "Fout! De magische code of de link die je hebt opgegeven is verlopen of ongeldig.", + "FAIL_SIGNIN_SUB_TITLE": "Start het proces opnieuw a.u.b.", + "THANKING_TEXT": "Bedankt dat je voor ons hebt gekozen." }, "NO_IMAGE": { "ADD_DROP": "Afbeelding toevoegen of verwijderen", diff --git a/apps/gauzy/src/assets/i18n/pl.json b/apps/gauzy/src/assets/i18n/pl.json index 2e0b9cb6215..b7c867da3a5 100644 --- a/apps/gauzy/src/assets/i18n/pl.json +++ b/apps/gauzy/src/assets/i18n/pl.json @@ -4503,7 +4503,12 @@ "SING_ANOTHER_WORKSPACE": "Zaloguj się do innego miejsca pracy", "CREATE_NEW_WORKSPACE": "Utwórz nową przestrzeń roboczą", "FIND_WORKSPACE": "Znajdź miejsce do pracy" - } + }, + "SUCCESS_SIGNIN_TITLE": "Gratulacje! Pomyślnie zalogowałeś się do swojej przestrzeni roboczej.", + "SUCCESS_SIGNIN_SUB_TITLE": "Proszę o cierpliwość podczas przygotowywania przestrzeni roboczej...", + "FAIL_SIGNIN_TITLE": "Błąd! Magiczny kod lub link, który podałeś, jest przeterminowany lub nieprawidłowy.", + "FAIL_SIGNIN_SUB_TITLE": "Proszę rozpocznij proces ponownie.", + "THANKING_TEXT": "Dziękujemy, że nas wybrałeś." }, "NO_IMAGE": { "ADD_DROP": "Dodaj lub usuń obraz", diff --git a/apps/gauzy/src/assets/i18n/pt.json b/apps/gauzy/src/assets/i18n/pt.json index c0b29a12570..02a330fd194 100644 --- a/apps/gauzy/src/assets/i18n/pt.json +++ b/apps/gauzy/src/assets/i18n/pt.json @@ -4503,7 +4503,12 @@ "SING_ANOTHER_WORKSPACE": "Entrar em outro espaço de trabalho", "CREATE_NEW_WORKSPACE": "Criar um novo espaço de trabalho", "FIND_WORKSPACE": "Encontrar espaço de trabalho" - } + }, + "SUCCESS_SIGNIN_TITLE": "Parabéns! Você fez login com sucesso em seu espaço de trabalho.", + "SUCCESS_SIGNIN_SUB_TITLE": "Por favor, seja paciente enquanto preparamos o espaço de trabalho...", + "FAIL_SIGNIN_TITLE": "Erro! O código mágico ou o link fornecido expirou ou é inválido.", + "FAIL_SIGNIN_SUB_TITLE": "Por favor, inicie o processo novamente.", + "THANKING_TEXT": "Obrigado por nos escolher." }, "NO_IMAGE": { "ADD_DROP": "Adicionar ou Remover Imagem", diff --git a/apps/gauzy/src/assets/i18n/ru.json b/apps/gauzy/src/assets/i18n/ru.json index 10e4f147aa5..7ea01e7b035 100644 --- a/apps/gauzy/src/assets/i18n/ru.json +++ b/apps/gauzy/src/assets/i18n/ru.json @@ -4427,7 +4427,12 @@ "SING_ANOTHER_WORKSPACE": "Войти в другую рабочую область", "CREATE_NEW_WORKSPACE": "Создать новую рабочую область", "FIND_WORKSPACE": "Найти рабочую область" - } + }, + "SUCCESS_SIGNIN_TITLE": "Поздравляю! Вы успешно вошли в свое рабочее пространство.", + "SUCCESS_SIGNIN_SUB_TITLE": "Пожалуйста, будьте терпеливы, пока мы готовим рабочее пространство...", + "FAIL_SIGNIN_TITLE": "Ошибка! Магический код или ссылка, которую вы предоставили, устарели или недействительны.", + "FAIL_SIGNIN_SUB_TITLE": "Пожалуйста, начните процесс заново.", + "THANKING_TEXT": "Спасибо, что выбрали нас." }, "NO_IMAGE": { "ADD_DROP": "Добавить или выложить изображение", diff --git a/apps/gauzy/src/assets/i18n/zh.json b/apps/gauzy/src/assets/i18n/zh.json index 019371abc4f..afe6330cb47 100644 --- a/apps/gauzy/src/assets/i18n/zh.json +++ b/apps/gauzy/src/assets/i18n/zh.json @@ -4503,7 +4503,12 @@ "SING_ANOTHER_WORKSPACE": "在另一个工作区登录", "CREATE_NEW_WORKSPACE": "创建一个新的工作空间", "FIND_WORKSPACE": "寻找工作空间" - } + }, + "SUCCESS_SIGNIN_TITLE": "恭喜!您已成功登录到您的工作区。", + "SUCCESS_SIGNIN_SUB_TITLE": "请耐心等待,我们正在准备工作区...", + "FAIL_SIGNIN_TITLE": "错误!您提供的魔法代码或链接已过期或无效。", + "FAIL_SIGNIN_SUB_TITLE": "请重新启动流程。", + "THANKING_TEXT": "感谢您选择我们。" }, "NO_IMAGE": { "ADD_DROP": "添加或删除图像",