diff --git a/.scripts/configure.ts b/.scripts/configure.ts index e9731c350d7..a6a79ae489b 100644 --- a/.scripts/configure.ts +++ b/.scripts/configure.ts @@ -153,7 +153,6 @@ if (!env.IS_DOCKER) { JITSU_BROWSER_WRITE_KEY: '${env.JITSU_BROWSER_WRITE_KEY}', GAUZY_GITHUB_APP_NAME: '${env.GAUZY_GITHUB_APP_NAME}', - GAUZY_GITHUB_APP_ID: '${env.GAUZY_GITHUB_APP_ID}', GAUZY_GITHUB_CLIENT_ID: '${env.GAUZY_GITHUB_CLIENT_ID}', GAUZY_GITHUB_REDIRECT_URL: '${env.GAUZY_GITHUB_REDIRECT_URL}', GAUZY_GITHUB_POST_INSTALL_URL: '${env.GAUZY_GITHUB_POST_INSTALL_URL}', @@ -260,7 +259,6 @@ if (!env.IS_DOCKER) { JITSU_BROWSER_WRITE_KEY: 'DOCKER_JITSU_BROWSER_WRITE_KEY', GAUZY_GITHUB_APP_NAME: 'DOCKER_GAUZY_GITHUB_APP_NAME', - GAUZY_GITHUB_APP_ID: 'DOCKER_GAUZY_GITHUB_APP_ID', GAUZY_GITHUB_CLIENT_ID: 'DOCKER_GAUZY_GITHUB_CLIENT_ID', GAUZY_GITHUB_REDIRECT_URL: 'DOCKER_GAUZY_GITHUB_REDIRECT_URL', GAUZY_GITHUB_POST_INSTALL_URL: 'DOCKER_GAUZY_GITHUB_POST_INSTALL_URL', @@ -290,10 +288,10 @@ if (!isProd) { // we always want first to remove old generated files (one of them is not needed for current build) try { unlinkSync(`./apps/gauzy/src/environments/environment.ts`); -} catch {} +} catch { } try { unlinkSync(`./apps/gauzy/src/environments/environment.prod.ts`); -} catch {} +} catch { } const envFileDest: string = isProd ? 'environment.prod.ts' : 'environment.ts'; const envFileDestOther: string = !isProd diff --git a/.scripts/env.ts b/.scripts/env.ts index c0a59597637..3d4a8bfce88 100644 --- a/.scripts/env.ts +++ b/.scripts/env.ts @@ -71,7 +71,6 @@ export type Env = Readonly<{ JITSU_BROWSER_WRITE_KEY: string; GAUZY_GITHUB_APP_NAME: string; - GAUZY_GITHUB_APP_ID: string; GAUZY_GITHUB_CLIENT_ID: string; GAUZY_GITHUB_REDIRECT_URL: string; GAUZY_GITHUB_POST_INSTALL_URL: string; @@ -145,7 +144,6 @@ export const env: Env = cleanEnv( JITSU_BROWSER_WRITE_KEY: str({ default: '' }), GAUZY_GITHUB_APP_NAME: str({ default: '' }), - GAUZY_GITHUB_APP_ID: str({ default: '' }), GAUZY_GITHUB_CLIENT_ID: str({ default: '' }), GAUZY_GITHUB_REDIRECT_URL: str({ default: '' }), GAUZY_GITHUB_POST_INSTALL_URL: str({ default: '' }), diff --git a/apps/gauzy/src/app/@core/services/integration/integration-map.service.ts b/apps/gauzy/src/app/@core/services/integration/integration-map.service.ts index 6c48589def7..540cfdcb83e 100644 --- a/apps/gauzy/src/app/@core/services/integration/integration-map.service.ts +++ b/apps/gauzy/src/app/@core/services/integration/integration-map.service.ts @@ -1,9 +1,5 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; -import { Observable } from 'rxjs/internal/Observable'; -import { IIntegrationMap, IIntegrationMapSyncRepository, IIntegrationSyncedRepositoryFindInput } from '@gauzy/contracts'; -import { toParams } from '@gauzy/common-angular'; -import { API_PREFIX } from '../../constants'; @Injectable({ providedIn: 'root' @@ -13,27 +9,4 @@ export class IntegrationMapService { constructor( private readonly _http: HttpClient ) { } - - /** - * Synchronize a GitHub repository. - * @param input The synchronization input data. - * @returns An Observable of the synchronized IntegrationMap. - */ - syncGithubRepository(input: IIntegrationMapSyncRepository): Observable { - const url = `${API_PREFIX}/integration-map/github/repository-sync`; - return this._http.post(url, input); - } - - /** - * Get synced GitHub repositories based on input parameters - * - * @param input - Input parameters for the query - * @returns An Observable of type IIntegrationMap - */ - getSyncedGithubRepository(input: IIntegrationSyncedRepositoryFindInput): Observable { - const url = `${API_PREFIX}/integration-map/github/repository-sync`; - return this._http.get(url, { - params: toParams(input) - }); - } } diff --git a/apps/gauzy/src/app/@core/services/organization-projects.service.ts b/apps/gauzy/src/app/@core/services/organization-projects.service.ts index 174af86ab36..98cac0d61c1 100644 --- a/apps/gauzy/src/app/@core/services/organization-projects.service.ts +++ b/apps/gauzy/src/app/@core/services/organization-projects.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; +import { Observable, firstValueFrom, take } from 'rxjs'; import { IOrganizationProjectCreateInput, IOrganizationProject, @@ -7,9 +8,9 @@ import { IEditEntityByMemberInput, IPagination, IEmployee, - IOrganizationProjectUpdateInput + IOrganizationProjectUpdateInput, + IOrganizationProjectSetting } from '@gauzy/contracts'; -import { Observable, firstValueFrom, take } from 'rxjs'; import { toParams } from '@gauzy/common-angular'; import { API_PREFIX } from '../constants/app.constants'; @@ -20,14 +21,14 @@ export class OrganizationProjectsService { private readonly API_URL = `${API_PREFIX}/organization-projects`; constructor( - private readonly http: HttpClient + private readonly _http: HttpClient ) { } create( body: IOrganizationProjectCreateInput ): Promise { return firstValueFrom( - this.http.post(this.API_URL, body) + this._http.post(this.API_URL, body) ); } @@ -35,7 +36,7 @@ export class OrganizationProjectsService { body: Partial ): Promise { return firstValueFrom( - this.http.put(`${this.API_URL}/${body.id}`, body) + this._http.put(`${this.API_URL}/${body.id}`, body) ); } @@ -44,7 +45,7 @@ export class OrganizationProjectsService { where?: IOrganizationProjectsFindInput ): Promise { return firstValueFrom( - this.http.get(`${this.API_URL}/employee/${id}`, { + this._http.get(`${this.API_URL}/employee/${id}`, { params: toParams({ ...where }) }) ); @@ -55,14 +56,14 @@ export class OrganizationProjectsService { where?: IOrganizationProjectsFindInput ): Promise> { return firstValueFrom( - this.http.get>(`${this.API_URL}`, { + this._http.get>(`${this.API_URL}`, { params: toParams({ where, relations }) }) ); } getById(id: IOrganizationProject['id'], relations: string[] = [],): Observable { - return this.http.get(`${this.API_URL}/${id}`, { + return this._http.get(`${this.API_URL}/${id}`, { params: toParams({ relations }) }); } @@ -71,7 +72,7 @@ export class OrganizationProjectsService { request: IOrganizationProjectsFindInput ): Promise { return firstValueFrom( - this.http.get(`${this.API_URL}/count`, { + this._http.get(`${this.API_URL}/count`, { params: toParams({ ...request }) }) ); @@ -79,7 +80,7 @@ export class OrganizationProjectsService { updateByEmployee(updateInput: IEditEntityByMemberInput): Promise { return firstValueFrom( - this.http + this._http .put(`${this.API_URL}/employee`, updateInput) ); } @@ -89,13 +90,29 @@ export class OrganizationProjectsService { body: IOrganizationProjectUpdateInput ): Promise { return firstValueFrom( - this.http.put(`${this.API_URL}/task-view/${id}`, body).pipe(take(1)) + this._http.put(`${this.API_URL}/task-view/${id}`, body).pipe(take(1)) ); } delete(id: IOrganizationProject['id']): Promise { return firstValueFrom( - this.http.delete(`${this.API_URL}/${id}`) + this._http.delete(`${this.API_URL}/${id}`) ); } + + /** + * Updates the settings for an organization project. + * + * @param id - The unique identifier (ID) of the organization project to update. + * @param input - The updated project settings to apply. + * + * @returns An Observable of type `IOrganizationProject` representing the updated organization project. + */ + updateProjectSetting( + id: IOrganizationProject['id'], + input: IOrganizationProjectSetting + ): Observable { + const url = `${this.API_URL}/setting/${id}`; + return this._http.put(url, input); + } } diff --git a/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.html b/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.html index bce62386911..b3e817057d3 100644 --- a/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.html +++ b/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.html @@ -592,7 +592,7 @@
diff --git a/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.ts b/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.ts index 6d4d5a50201..d592df5c7c7 100644 --- a/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.ts +++ b/apps/gauzy/src/app/@shared/project/project-mutation/project-mutation.component.ts @@ -17,16 +17,15 @@ import { PermissionsEnum, IIntegrationTenant, IGithubRepository, - IIntegrationMapSyncRepository, - IntegrationEntity, - IIntegrationMap + IOrganizationProjectSetting, + HttpStatus } from '@gauzy/contracts'; import { TranslateService } from '@ngx-translate/core'; import { Router } from '@angular/router'; import { uniq } from 'underscore'; import { EMPTY } from 'rxjs'; import { catchError, debounceTime, filter, finalize, tap } from 'rxjs/operators'; -import { distinctUntilChange, parsedInt } from '@gauzy/common-angular'; +import { distinctUntilChange } from '@gauzy/common-angular'; import { CKEditor4 } from 'ckeditor4-angular/ckeditor'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { TranslationBaseComponent } from '../../language-base/translation-base.component'; @@ -34,8 +33,8 @@ import { patterns } from '../../regex/regex-patterns.const'; import { environment as ENV } from '../../../../environments/environment'; import { ErrorHandlingService, - IntegrationMapService, OrganizationContactService, + OrganizationProjectsService, OrganizationTeamsService, Store, ToastrService @@ -54,8 +53,6 @@ import { ckEditorConfig } from "../../ckeditor.config"; export class ProjectMutationComponent extends TranslationBaseComponent implements OnInit { - public parsedInt = parsedInt; - public FormHelpers: typeof FormHelpers = FormHelpers; public OrganizationProjectBudgetTypeEnum = OrganizationProjectBudgetTypeEnum; public TaskListTypeEnum = TaskListTypeEnum; @@ -115,17 +112,6 @@ export class ProjectMutationComponent extends TranslationBaseComponent return form; } - /** - * Represents an integration map or a boolean value. - */ - private _integrationMap: IIntegrationMap | boolean; - get integrationMap(): IIntegrationMap | boolean { - return this._integrationMap; - } - @Input() set integrationMap(value: IIntegrationMap | boolean) { - this._integrationMap = value; - } - /** * Represents an integration tenant or a boolean value. */ @@ -163,7 +149,7 @@ export class ProjectMutationComponent extends TranslationBaseComponent private readonly _errorHandler: ErrorHandlingService, private readonly _organizationTeamService: OrganizationTeamsService, private readonly _organizationContactService: OrganizationContactService, - private readonly _integrationMapService: IntegrationMapService + private readonly _organizationProjectsService: OrganizationProjectsService, ) { super(translateService); } @@ -496,30 +482,33 @@ export class ProjectMutationComponent extends TranslationBaseComponent const { id: organizationId, tenantId } = this.organization; const { id: projectId } = this.project; - const integrationId = this.integration['id']; + const externalRepositoryId = repository.id; /** */ - const request: IIntegrationMapSyncRepository = { + const request: IOrganizationProjectSetting = { organizationId, tenantId, - gauzyId: projectId, - integrationId, - repository, - entity: IntegrationEntity.PROJECT + externalRepositoryId } - // Fetch entity settings by integration ID and handle the result as an observable - this._integrationMapService.syncGithubRepository(request).pipe( + this._organizationProjectsService.updateProjectSetting(projectId, request).pipe( + tap((response: any) => { + if (response['status'] == HttpStatus.BAD_REQUEST) { + throw new Error(`${response['message']}`); + } + }), + tap(() => { + this._toastrService.success('NOTES.ORGANIZATIONS.EDIT_ORGANIZATIONS_PROJECTS.SYNC_REPOSITORY', { + repository: repository.full_name, + project: this.project.name + }); + }), catchError((error) => { this._errorHandler.handleError(error); return EMPTY; }), // Execute the following code block when the observable completes or errors finalize(() => { - this._toastrService.success('NOTES.ORGANIZATIONS.EDIT_ORGANIZATIONS_PROJECTS.SYNC_REPOSITORY', { - repository: repository.full_name, - project: this.project.name - }); // Set the 'loading' flag to false to indicate that data loading is complete this.loading = false; }), diff --git a/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html b/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html index a118b061cf8..ee6cf11db3b 100644 --- a/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html +++ b/apps/gauzy/src/app/pages/integrations/github/components/view/view.component.html @@ -30,7 +30,7 @@
{{ 'INTEGRATIONS.GITHUB_PAGE.NAME' | translate }}
; + public project: IOrganizationProject; + public project$: Observable; public integration$: Observable; public integration: IIntegrationTenant; public contextMenuItems: NbMenuItem[] = []; @@ -84,9 +82,9 @@ export class GithubViewComponent extends TranslationBaseComponent implements Aft private readonly _errorHandlingService: ErrorHandlingService, private readonly _store: Store, private readonly _githubService: GithubService, - private readonly _integrationMapService: IntegrationMapService, private readonly _integrationEntitySettingService: IntegrationEntitySettingService, private readonly _integrationEntitySettingServiceStoreService: IntegrationEntitySettingServiceStoreService, + private readonly _organizationProjectsService: OrganizationProjectsService ) { super(_translateService); } @@ -97,33 +95,23 @@ export class GithubViewComponent extends TranslationBaseComponent implements Aft this._getContextMenuItems(); this._getGithubIntegrationTenant(); - this.integrationMap$ = this._store.selectedProject$.pipe( + this.project$ = this._store.selectedProject$.pipe( debounceTime(100), distinctUntilChange(), filter((project: IOrganizationProject) => !!project), switchMap((project: IOrganizationProject) => { // Ensure there is a valid organization if (!project.id) { - return of(false); // No valid organization, return false + return EMPTY; // No valid organization, return false } - // Extract organization properties - const { id: organizationId, tenantId } = this.organization; - // Extract integration properties - const { id: integrationId } = this.integration; // Extract project properties const { id: projectId } = this.project = project; - return this._integrationMapService.getSyncedGithubRepository({ - organizationId, - tenantId, - integrationId, - gauzyId: projectId, - entity: IntegrationEntity.PROJECT - }).pipe( + return this._organizationProjectsService.getById(projectId).pipe( catchError((error) => { // Handle and log errors this._errorHandlingService.handleError(error); - return of(false); + return EMPTY; }), // Handle component lifecycle to avoid memory leaks untilDestroyed(this), diff --git a/apps/gauzy/src/app/pages/integrations/github/components/wizard/wizard.component.ts b/apps/gauzy/src/app/pages/integrations/github/components/wizard/wizard.component.ts index a4f118a6c51..333f9c7e4fc 100644 --- a/apps/gauzy/src/app/pages/integrations/github/components/wizard/wizard.component.ts +++ b/apps/gauzy/src/app/pages/integrations/github/components/wizard/wizard.component.ts @@ -121,6 +121,7 @@ export class GithubWizardComponent implements AfterViewInit, OnInit, OnDestroy { // Construct the external URL for GitHub authorization with the query parameters const externalUrl = `${GITHUB_AUTHORIZATION_URL}?${queryParams.toString()}`; + console.log('External Github OAuth App URL: %s', externalUrl); // Redirect the user's browser to the GitHub authorization URL // This action starts the GitHub OAuth authorization process @@ -199,6 +200,7 @@ export class GithubWizardComponent implements AfterViewInit, OnInit, OnDestroy { // Construct the external URL for GitHub authorization with the query parameters /** Navigate to the target external URL */ const url = `https://github.com/apps/${environment.GAUZY_GITHUB_APP_NAME}/installations/new?${queryParams.toString()}`; + console.log('External Github App Installation URL: %s', url); /** Navigate to the external URL with query parameters */ this.window = window.open( diff --git a/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.html b/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.html index 4329bdd26f5..b68c966fe78 100644 --- a/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.html +++ b/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.html @@ -12,7 +12,6 @@

diff --git a/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.ts b/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.ts index df54b53d5e1..e764a37c7a2 100644 --- a/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.ts +++ b/apps/gauzy/src/app/pages/projects/components/project-edit/edit.component.ts @@ -1,24 +1,19 @@ import { Component, OnInit, ViewChild } from '@angular/core'; import { ActivatedRoute, Data, Router } from '@angular/router'; -import { catchError, debounceTime, delay, of, switchMap } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { Observable } from 'rxjs/internal/Observable'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { TranslateService } from '@ngx-translate/core'; import { - IIntegrationMap, IIntegrationTenant, IOrganization, IOrganizationProject, - IOrganizationProjectUpdateInput, - IntegrationEntity + IOrganizationProjectUpdateInput } from '@gauzy/contracts'; -import { distinctUntilChange } from '@gauzy/common-angular'; import { ProjectMutationComponent } from './../../../../@shared/project'; import { TranslationBaseComponent } from './../../../../@shared/language-base'; import { ErrorHandlingService, - IntegrationMapService, OrganizationProjectsService, Store, ToastrService @@ -36,7 +31,6 @@ export class ProjectEditMutationComponent extends TranslationBaseComponent imple @ViewChild(ProjectMutationComponent, { static: false }) public _component: ProjectMutationComponent; public loading: boolean; - public integrationMap$: Observable; public integration$: Observable; public project$: Observable; public project: IOrganizationProject; @@ -50,7 +44,6 @@ export class ProjectEditMutationComponent extends TranslationBaseComponent imple private readonly _organizationProjectsService: OrganizationProjectsService, private readonly _toastrService: ToastrService, private readonly _errorHandlingService: ErrorHandlingService, - private readonly _integrationMapService: IntegrationMapService ) { super(translateService); } @@ -59,7 +52,6 @@ export class ProjectEditMutationComponent extends TranslationBaseComponent imple // Call the following methods to initialize component properties this._getEditProject(); this._getGithubIntegrationTenant(); - this._getSyncedGithubRepository(); } /** @@ -86,46 +78,6 @@ export class ProjectEditMutationComponent extends TranslationBaseComponent imple ); } - /** - * Fetches and handles synchronized GitHub repository data. - * This method is not provided in your code but is expected to be present. - */ - private _getSyncedGithubRepository() { - this.integrationMap$ = this._store.selectedOrganization$.pipe( - debounceTime(100), - distinctUntilChange(), - switchMap((organization: IOrganization) => { - // Ensure there is a valid organization - if (!organization) { - return of(false); // No valid organization, return false - } - - // Extract organization properties - const { id: organizationId, tenantId } = this.organization; - - return this._activatedRoute.data.pipe( - delay(1000), // Delay for loading effect - filter(({ integration, project }) => !!integration && !!project), - // Get the 'integration' and 'project' route parameter - switchMap(({ integration, project }) => this._integrationMapService.getSyncedGithubRepository({ - organizationId, - tenantId, - integrationId: integration.id, - gauzyId: project.id, - entity: IntegrationEntity.PROJECT - })), - catchError((error) => { - // Handle and log errors - this._errorHandlingService.handleError(error); - return of(false); - }), - // Handle component lifecycle to avoid memory leaks - untilDestroyed(this), - ); - }) - ); - } - /** * Handles the submission of the project mutation form. * diff --git a/apps/gauzy/src/environments/model.ts b/apps/gauzy/src/environments/model.ts index 41e223c9045..ccf5a61e6b0 100644 --- a/apps/gauzy/src/environments/model.ts +++ b/apps/gauzy/src/environments/model.ts @@ -75,7 +75,6 @@ export interface Environment { /** Github Integration */ GAUZY_GITHUB_APP_NAME: string; - GAUZY_GITHUB_APP_ID: string; GAUZY_GITHUB_CLIENT_ID: string; GAUZY_GITHUB_REDIRECT_URL: string; GAUZY_GITHUB_POST_INSTALL_URL: string; diff --git a/docker-compose.demo.yml b/docker-compose.demo.yml index b9f3deeb0e9..6e9b761d644 100644 --- a/docker-compose.demo.yml +++ b/docker-compose.demo.yml @@ -50,7 +50,7 @@ services: jitsu: container_name: jitsu - image: jitsucom/jitsu + image: jitsucom/jitsu:latest extra_hosts: - 'host.docker.internal:host-gateway' environment: @@ -70,7 +70,7 @@ services: - ./.deploy/jitsu/server/data/logs:/home/eventnative/data/logs - ./.deploy/jitsu/server/data/logs/events:/home/eventnative/data/logs/events - /var/run/docker.sock:/var/run/docker.sock - - jitsu_data:/home/eventnative/data/airbyte + - jitsu_workspace:/home/eventnative/data/airbyte restart: always ports: - '8000:8000' @@ -332,7 +332,7 @@ volumes: minio_data: {} cube_data: {} certificates: {} - jitsu_data: {} + jitsu_workspace: {} networks: overlay: diff --git a/docker-compose.yml b/docker-compose.yml index 1d968a86ccc..70069329abe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -70,7 +70,7 @@ services: - ./.deploy/jitsu/server/data/logs:/home/eventnative/data/logs - ./.deploy/jitsu/server/data/logs/events:/home/eventnative/data/logs/events - /var/run/docker.sock:/var/run/docker.sock - - jitsu_data:/home/eventnative/data/airbyte + - jitsu_workspace:/home/eventnative/data/airbyte restart: always ports: - '8000:8000' @@ -366,7 +366,7 @@ volumes: minio_data: {} cube_data: {} certificates: {} - jitsu_data: {} + jitsu_workspace: {} networks: overlay: diff --git a/packages/contracts/src/organization-projects.model.ts b/packages/contracts/src/organization-projects.model.ts index a1e62b83636..f4e14fb637f 100644 --- a/packages/contracts/src/organization-projects.model.ts +++ b/packages/contracts/src/organization-projects.model.ts @@ -21,7 +21,13 @@ export interface IRelationalOrganizationProject { projectId?: IOrganizationProject['id']; } -export interface IOrganizationProject extends IBaseEntityWithMembers, IRelationalImageAsset, IRelationalOrganizationContact { +export interface IOrganizationProjectSetting extends IBasePerTenantAndOrganizationEntityModel { + externalRepositoryId?: number; + isTasksAutoSync?: boolean; + isTasksAutoSyncOnLabel?: boolean; +} + +export interface IOrganizationProject extends IBaseEntityWithMembers, IRelationalImageAsset, IRelationalOrganizationContact, IOrganizationProjectSetting { name: string; startDate?: Date; endDate?: Date; @@ -99,8 +105,9 @@ export interface IOrganizationProjectCreateInput extends IBasePerTenantAndOrgani taskListType?: TaskListTypeEnum; } -export interface IOrganizationProjectUpdateInput extends IOrganizationProjectCreateInput { +export interface IOrganizationProjectUpdateInput extends IOrganizationProjectCreateInput, IOrganizationProjectSetting { id?: IOrganizationContact['id']; + } export interface IOrganizationProjectStoreState { diff --git a/packages/core/src/core/crud/crud.service.ts b/packages/core/src/core/crud/crud.service.ts index 97a630444bb..18c94944a3b 100644 --- a/packages/core/src/core/crud/crud.service.ts +++ b/packages/core/src/core/crud/crud.service.ts @@ -13,7 +13,7 @@ import { UpdateResult } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; -import * as moment from 'moment'; +// import * as moment from 'moment'; import { of as observableOf, throwError } from 'rxjs'; import { mergeMap } from 'rxjs/operators'; import { IPagination } from '@gauzy/contracts'; @@ -128,7 +128,7 @@ export abstract class CrudService } : {} ), }); - console.log(options, moment().format('DD.MM.YYYY HH:mm:ss')); + // console.log(options, moment().format('DD.MM.YYYY HH:mm:ss')); const [items, total] = await query.getManyAndCount(); return { items, total }; } catch (error) { diff --git a/packages/core/src/database/migrations/1697263211513-AddedIntegrationSettingColumnsToTheOrganizationProjectTable.ts b/packages/core/src/database/migrations/1697263211513-AddedIntegrationSettingColumnsToTheOrganizationProjectTable.ts new file mode 100644 index 00000000000..14fa625fd82 --- /dev/null +++ b/packages/core/src/database/migrations/1697263211513-AddedIntegrationSettingColumnsToTheOrganizationProjectTable.ts @@ -0,0 +1,125 @@ + +import { MigrationInterface, QueryRunner } from "typeorm"; +import * as chalk from "chalk"; + +export class AddedIntegrationSettingColumnsToTheOrganizationProjectTable1697263211513 implements MigrationInterface { + + name = 'AddedIntegrationSettingColumnsToTheOrganizationProjectTable1697263211513'; + + /** + * Up Migration + * + * @param queryRunner + */ + public async up(queryRunner: QueryRunner): Promise { + console.log(chalk.yellow(`AddedIntegrationSettingColumnsToTheOrganizationProjectTable1697263211513 start running!`)); + 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 "organization_project" ADD "externalRepositoryId" integer`); + await queryRunner.query(`ALTER TABLE "organization_project" ADD "isTasksAutoSync" boolean DEFAULT true`); + await queryRunner.query(`ALTER TABLE "organization_project" ADD "isTasksAutoSyncOnLabel" boolean DEFAULT true`); + await queryRunner.query(`CREATE INDEX "IDX_60f6ebb4ab539087ce5f4266ca" ON "organization_project" ("externalRepositoryId") `); + await queryRunner.query(`CREATE INDEX "IDX_75855b44250686f84b7c4bc1f1" ON "organization_project" ("isTasksAutoSync") `); + await queryRunner.query(`CREATE INDEX "IDX_c5c4366237dc2bb176c1503426" ON "organization_project" ("isTasksAutoSyncOnLabel") `); + } + + /** + * PostgresDB Down Migration + * + * @param queryRunner + */ + public async postgresDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "public"."IDX_c5c4366237dc2bb176c1503426"`); + await queryRunner.query(`DROP INDEX "public"."IDX_75855b44250686f84b7c4bc1f1"`); + await queryRunner.query(`DROP INDEX "public"."IDX_60f6ebb4ab539087ce5f4266ca"`); + await queryRunner.query(`ALTER TABLE "organization_project" DROP COLUMN "isTasksAutoSyncOnLabel"`); + await queryRunner.query(`ALTER TABLE "organization_project" DROP COLUMN "isTasksAutoSync"`); + await queryRunner.query(`ALTER TABLE "organization_project" DROP COLUMN "externalRepositoryId"`); + } + + /** + * SqliteDB and BetterSQlite3DB Up Migration + * + * @param queryRunner + */ + public async sqliteUpQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_3590135ac2034d7aa88efa7e52"`); + await queryRunner.query(`DROP INDEX "IDX_18e22d4b569159bb91dec869aa"`); + await queryRunner.query(`DROP INDEX "IDX_bc1e32c13683dbb16ada1c6da1"`); + await queryRunner.query(`DROP INDEX "IDX_c210effeb6314d325bc024d21e"`); + await queryRunner.query(`DROP INDEX "IDX_37215da8dee9503d759adb3538"`); + await queryRunner.query(`DROP INDEX "IDX_9d8afc1e1e64d4b7d48dd2229d"`); + await queryRunner.query(`DROP INDEX "IDX_7cf84e8b5775f349f81a1f3cc4"`); + await queryRunner.query(`DROP INDEX "IDX_063324fdceb51f7086e401ed2c"`); + await queryRunner.query(`CREATE TABLE "temporary_organization_project" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "name" varchar NOT NULL, "startDate" datetime, "endDate" datetime, "billing" varchar, "currency" varchar, "public" boolean, "owner" varchar, "taskListType" varchar NOT NULL DEFAULT ('GRID'), "code" varchar, "description" varchar, "color" varchar, "billable" boolean, "billingFlat" boolean, "openSource" boolean, "projectUrl" varchar, "openSourceProjectUrl" varchar, "budget" integer, "budgetType" text DEFAULT ('cost'), "organizationContactId" varchar, "membersCount" integer DEFAULT (0), "imageUrl" varchar(500), "imageId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), "externalRepositoryId" integer, "isTasksAutoSync" boolean DEFAULT (1), "isTasksAutoSyncOnLabel" boolean DEFAULT (1), CONSTRAINT "FK_bc1e32c13683dbb16ada1c6da14" FOREIGN KEY ("organizationContactId") REFERENCES "organization_contact" ("id") ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT "FK_9d8afc1e1e64d4b7d48dd2229d7" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_7cf84e8b5775f349f81a1f3cc44" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_063324fdceb51f7086e401ed2c9" FOREIGN KEY ("imageId") REFERENCES "image_asset" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "temporary_organization_project"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "startDate", "endDate", "billing", "currency", "public", "owner", "taskListType", "code", "description", "color", "billable", "billingFlat", "openSource", "projectUrl", "openSourceProjectUrl", "budget", "budgetType", "organizationContactId", "membersCount", "imageUrl", "imageId", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "startDate", "endDate", "billing", "currency", "public", "owner", "taskListType", "code", "description", "color", "billable", "billingFlat", "openSource", "projectUrl", "openSourceProjectUrl", "budget", "budgetType", "organizationContactId", "membersCount", "imageUrl", "imageId", "isActive", "isArchived" FROM "organization_project"`); + await queryRunner.query(`DROP TABLE "organization_project"`); + await queryRunner.query(`ALTER TABLE "temporary_organization_project" RENAME TO "organization_project"`); + await queryRunner.query(`CREATE INDEX "IDX_3590135ac2034d7aa88efa7e52" ON "organization_project" ("isArchived") `); + await queryRunner.query(`CREATE INDEX "IDX_18e22d4b569159bb91dec869aa" ON "organization_project" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_bc1e32c13683dbb16ada1c6da1" ON "organization_project" ("organizationContactId") `); + await queryRunner.query(`CREATE INDEX "IDX_c210effeb6314d325bc024d21e" ON "organization_project" ("currency") `); + await queryRunner.query(`CREATE INDEX "IDX_37215da8dee9503d759adb3538" ON "organization_project" ("name") `); + await queryRunner.query(`CREATE INDEX "IDX_9d8afc1e1e64d4b7d48dd2229d" ON "organization_project" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_7cf84e8b5775f349f81a1f3cc4" ON "organization_project" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_063324fdceb51f7086e401ed2c" ON "organization_project" ("imageId") `); + await queryRunner.query(`CREATE INDEX "IDX_60f6ebb4ab539087ce5f4266ca" ON "organization_project" ("externalRepositoryId") `); + await queryRunner.query(`CREATE INDEX "IDX_75855b44250686f84b7c4bc1f1" ON "organization_project" ("isTasksAutoSync") `); + await queryRunner.query(`CREATE INDEX "IDX_c5c4366237dc2bb176c1503426" ON "organization_project" ("isTasksAutoSyncOnLabel") `); + } + + /** + * SqliteDB and BetterSQlite3DB Down Migration + * + * @param queryRunner + */ + public async sqliteDownQueryRunner(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP INDEX "IDX_c5c4366237dc2bb176c1503426"`); + await queryRunner.query(`DROP INDEX "IDX_75855b44250686f84b7c4bc1f1"`); + await queryRunner.query(`DROP INDEX "IDX_60f6ebb4ab539087ce5f4266ca"`); + await queryRunner.query(`DROP INDEX "IDX_063324fdceb51f7086e401ed2c"`); + await queryRunner.query(`DROP INDEX "IDX_7cf84e8b5775f349f81a1f3cc4"`); + await queryRunner.query(`DROP INDEX "IDX_9d8afc1e1e64d4b7d48dd2229d"`); + await queryRunner.query(`DROP INDEX "IDX_37215da8dee9503d759adb3538"`); + await queryRunner.query(`DROP INDEX "IDX_c210effeb6314d325bc024d21e"`); + await queryRunner.query(`DROP INDEX "IDX_bc1e32c13683dbb16ada1c6da1"`); + await queryRunner.query(`DROP INDEX "IDX_18e22d4b569159bb91dec869aa"`); + await queryRunner.query(`DROP INDEX "IDX_3590135ac2034d7aa88efa7e52"`); + await queryRunner.query(`ALTER TABLE "organization_project" RENAME TO "temporary_organization_project"`); + await queryRunner.query(`CREATE TABLE "organization_project" ("id" varchar PRIMARY KEY NOT NULL, "createdAt" datetime NOT NULL DEFAULT (datetime('now')), "updatedAt" datetime NOT NULL DEFAULT (datetime('now')), "tenantId" varchar, "organizationId" varchar, "name" varchar NOT NULL, "startDate" datetime, "endDate" datetime, "billing" varchar, "currency" varchar, "public" boolean, "owner" varchar, "taskListType" varchar NOT NULL DEFAULT ('GRID'), "code" varchar, "description" varchar, "color" varchar, "billable" boolean, "billingFlat" boolean, "openSource" boolean, "projectUrl" varchar, "openSourceProjectUrl" varchar, "budget" integer, "budgetType" text DEFAULT ('cost'), "organizationContactId" varchar, "membersCount" integer DEFAULT (0), "imageUrl" varchar(500), "imageId" varchar, "isActive" boolean DEFAULT (1), "isArchived" boolean DEFAULT (0), CONSTRAINT "FK_bc1e32c13683dbb16ada1c6da14" FOREIGN KEY ("organizationContactId") REFERENCES "organization_contact" ("id") ON DELETE SET NULL ON UPDATE CASCADE, CONSTRAINT "FK_9d8afc1e1e64d4b7d48dd2229d7" FOREIGN KEY ("organizationId") REFERENCES "organization" ("id") ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT "FK_7cf84e8b5775f349f81a1f3cc44" FOREIGN KEY ("tenantId") REFERENCES "tenant" ("id") ON DELETE CASCADE ON UPDATE NO ACTION, CONSTRAINT "FK_063324fdceb51f7086e401ed2c9" FOREIGN KEY ("imageId") REFERENCES "image_asset" ("id") ON DELETE SET NULL ON UPDATE NO ACTION)`); + await queryRunner.query(`INSERT INTO "organization_project"("id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "startDate", "endDate", "billing", "currency", "public", "owner", "taskListType", "code", "description", "color", "billable", "billingFlat", "openSource", "projectUrl", "openSourceProjectUrl", "budget", "budgetType", "organizationContactId", "membersCount", "imageUrl", "imageId", "isActive", "isArchived") SELECT "id", "createdAt", "updatedAt", "tenantId", "organizationId", "name", "startDate", "endDate", "billing", "currency", "public", "owner", "taskListType", "code", "description", "color", "billable", "billingFlat", "openSource", "projectUrl", "openSourceProjectUrl", "budget", "budgetType", "organizationContactId", "membersCount", "imageUrl", "imageId", "isActive", "isArchived" FROM "temporary_organization_project"`); + await queryRunner.query(`DROP TABLE "temporary_organization_project"`); + await queryRunner.query(`CREATE INDEX "IDX_063324fdceb51f7086e401ed2c" ON "organization_project" ("imageId") `); + await queryRunner.query(`CREATE INDEX "IDX_7cf84e8b5775f349f81a1f3cc4" ON "organization_project" ("tenantId") `); + await queryRunner.query(`CREATE INDEX "IDX_9d8afc1e1e64d4b7d48dd2229d" ON "organization_project" ("organizationId") `); + await queryRunner.query(`CREATE INDEX "IDX_37215da8dee9503d759adb3538" ON "organization_project" ("name") `); + await queryRunner.query(`CREATE INDEX "IDX_c210effeb6314d325bc024d21e" ON "organization_project" ("currency") `); + await queryRunner.query(`CREATE INDEX "IDX_bc1e32c13683dbb16ada1c6da1" ON "organization_project" ("organizationContactId") `); + await queryRunner.query(`CREATE INDEX "IDX_18e22d4b569159bb91dec869aa" ON "organization_project" ("isActive") `); + await queryRunner.query(`CREATE INDEX "IDX_3590135ac2034d7aa88efa7e52" ON "organization_project" ("isArchived") `); + } +} diff --git a/packages/core/src/integration-map/commands/handlers/integration-map.sync-issue.handler.ts b/packages/core/src/integration-map/commands/handlers/integration-map.sync-issue.handler.ts index 29b6f8f9de4..7cecd971e58 100644 --- a/packages/core/src/integration-map/commands/handlers/integration-map.sync-issue.handler.ts +++ b/packages/core/src/integration-map/commands/handlers/integration-map.sync-issue.handler.ts @@ -39,6 +39,7 @@ export class IntegrationMapSyncIssueHandler implements ICommandHandler OrganizationProjectModule), forwardRef(() => IntegrationModule), - forwardRef(() => IntegrationTenantModule), - forwardRef(() => IntegrationMapModule), + forwardRef(() => IntegrationTenantModule) ], controllers: [ GitHubAuthorizationController, diff --git a/packages/core/src/organization-project/commands/handlers/index.ts b/packages/core/src/organization-project/commands/handlers/index.ts index ed956eee74b..537b0b4df01 100644 --- a/packages/core/src/organization-project/commands/handlers/index.ts +++ b/packages/core/src/organization-project/commands/handlers/index.ts @@ -1,9 +1,11 @@ -import { OrganizationProjectEditByEmployeeHandler } from './organization-project.edit-by-employee.handler'; import { OrganizationProjectCreateHandler } from './organization-project.create.handler'; +import { OrganizationProjectEditByEmployeeHandler } from './organization-project.edit-by-employee.handler'; +import { OrganizationProjectSettingUpdateHandler } from './organization-project-setting.update.handler'; import { OrganizationProjectUpdateHandler } from './organization-project.update.handler'; export const CommandHandlers = [ - OrganizationProjectEditByEmployeeHandler, OrganizationProjectCreateHandler, - OrganizationProjectUpdateHandler + OrganizationProjectEditByEmployeeHandler, + OrganizationProjectSettingUpdateHandler, + OrganizationProjectUpdateHandler, ]; diff --git a/packages/core/src/organization-project/commands/handlers/organization-project-setting.update.handler.ts b/packages/core/src/organization-project/commands/handlers/organization-project-setting.update.handler.ts new file mode 100644 index 00000000000..aa709e67a43 --- /dev/null +++ b/packages/core/src/organization-project/commands/handlers/organization-project-setting.update.handler.ts @@ -0,0 +1,34 @@ +import { HttpException, HttpStatus, Logger } from '@nestjs/common'; +import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; +import { UpdateResult } from 'typeorm'; +import { IOrganizationProjectSetting } from '@gauzy/contracts'; +import { OrganizationProjectService } from '../../organization-project.service'; +import { OrganizationProjectSettingUpdateCommand } from '../organization-project-setting.update.command'; + +@CommandHandler(OrganizationProjectSettingUpdateCommand) +export class OrganizationProjectSettingUpdateHandler implements ICommandHandler { + private readonly logger = new Logger('OrganizationProjectSettingUpdateHandler'); + + constructor( + private readonly _organizationProjectService: OrganizationProjectService + ) { } + + /** + * Execute an organization project setting update command. + * + * @param command - An `OrganizationProjectSettingUpdateCommand` object containing the update details. + * @returns A promise that resolves to an `UpdateResult` object representing the result of the update operation. + */ + public async execute( + command: OrganizationProjectSettingUpdateCommand + ): Promise { + try { + const { id, input } = command; + return await this._organizationProjectService.update(id, input); + } catch (error) { + // Handle errors and return an appropriate error response + this.logger.error('Failed to update project integration settings', error.message); + throw new HttpException(`Failed to update project integration settings: ${error.message}`, HttpStatus.BAD_REQUEST); + } + } +} diff --git a/packages/core/src/organization-project/commands/index.ts b/packages/core/src/organization-project/commands/index.ts index ae987bdca5c..2153583fe80 100644 --- a/packages/core/src/organization-project/commands/index.ts +++ b/packages/core/src/organization-project/commands/index.ts @@ -1,3 +1,4 @@ +export * from './organization-project-setting.update.command'; export * from './organization-project.create.command'; export * from './organization-project.edit-by-employee.command'; -export * from './organization-project.update.command'; \ No newline at end of file +export * from './organization-project.update.command'; diff --git a/packages/core/src/organization-project/commands/organization-project-setting.update.command.ts b/packages/core/src/organization-project/commands/organization-project-setting.update.command.ts new file mode 100644 index 00000000000..f2f9b8167e1 --- /dev/null +++ b/packages/core/src/organization-project/commands/organization-project-setting.update.command.ts @@ -0,0 +1,11 @@ +import { ICommand } from '@nestjs/cqrs'; +import { IOrganizationProject, IOrganizationProjectSetting } from '@gauzy/contracts'; + +export class OrganizationProjectSettingUpdateCommand implements ICommand { + static readonly type = '[Organization Project Setting] Update'; + + constructor( + public readonly id: IOrganizationProject['id'], + public readonly input: IOrganizationProjectSetting + ) { } +} diff --git a/packages/core/src/organization-project/dto/index.ts b/packages/core/src/organization-project/dto/index.ts index 27ef2ba1703..150db7b4278 100644 --- a/packages/core/src/organization-project/dto/index.ts +++ b/packages/core/src/organization-project/dto/index.ts @@ -1,3 +1,4 @@ export * from './create-organization-project.dto'; export * from './update-organization-project.dto'; -export * from './update-task-mode.dto'; \ No newline at end of file +export * from './update-task-mode.dto'; +export * from './update-project-setting.dto'; diff --git a/packages/core/src/organization-project/dto/update-project-setting.dto.ts b/packages/core/src/organization-project/dto/update-project-setting.dto.ts new file mode 100644 index 00000000000..a340eace696 --- /dev/null +++ b/packages/core/src/organization-project/dto/update-project-setting.dto.ts @@ -0,0 +1,25 @@ +import { IOrganizationProjectSetting } from "@gauzy/contracts"; +import { ApiPropertyOptional } from "@nestjs/swagger"; +import { TenantOrganizationBaseDTO } from "../../core/dto"; +import { IsBoolean, IsNumber, IsOptional } from "class-validator"; + +export class UpdateProjectSettingDTO extends TenantOrganizationBaseDTO implements IOrganizationProjectSetting { + + // External repository ID property + @ApiPropertyOptional({ type: Number }) + @IsOptional() + @IsNumber() + readonly externalRepositoryId: number; + + // Auto-sync tasks property + @ApiPropertyOptional({ type: Boolean }) + @IsOptional() + @IsBoolean() + readonly isTasksAutoSync: boolean; + + // Auto-sync on label property + @ApiPropertyOptional({ type: Boolean }) + @IsOptional() + @IsBoolean() + readonly isTasksAutoSyncOnLabel: boolean; +} diff --git a/packages/core/src/organization-project/organization-project.controller.ts b/packages/core/src/organization-project/organization-project.controller.ts index 2ff091cea71..a0209b23a53 100644 --- a/packages/core/src/organization-project/organization-project.controller.ts +++ b/packages/core/src/organization-project/organization-project.controller.ts @@ -20,6 +20,7 @@ import { IEditEntityByMemberInput, IEmployee, IOrganizationProject, + IOrganizationProjectSetting, IPagination, PermissionsEnum } from '@gauzy/contracts'; @@ -27,6 +28,7 @@ import { CrudController, PaginationParams } from './../core/crud'; import { OrganizationProjectCreateCommand, OrganizationProjectEditByEmployeeCommand, + OrganizationProjectSettingUpdateCommand, OrganizationProjectUpdateCommand } from './commands'; import { OrganizationProject } from './organization-project.entity'; @@ -36,7 +38,12 @@ import { Permissions } from './../shared/decorators'; import { CountQueryDTO, RelationsQueryDTO } from './../shared/dto'; import { UUIDValidationPipe } from './../shared/pipes'; import { TenantOrganizationBaseDTO } from './../core/dto'; -import { CreateOrganizationProjectDTO, UpdateOrganizationProjectDTO, UpdateTaskModeDTO } from './dto'; +import { + CreateOrganizationProjectDTO, + UpdateOrganizationProjectDTO, + UpdateProjectSettingDTO, + UpdateTaskModeDTO +} from './dto'; @ApiTags('OrganizationProject') @UseGuards(TenantPermissionGuard, PermissionGuard) @@ -148,7 +155,26 @@ export class OrganizationProjectController extends CrudController { return await this.commandBus.execute( - new OrganizationProjectUpdateCommand({ id, ...entity }) + new OrganizationProjectUpdateCommand({ ...entity, id }) + ); + } + + + /** + * Update organization project settings by ID. + * + * @param id - The ID of the organization project to update settings for. + * @param entity - An object containing the updated project settings. + * @returns A promise that resolves to an `IOrganizationProject` object representing the updated project settings. + */ + @Put('/setting/:id') + @UsePipes(new ValidationPipe({ whitelist: true })) + async updateProjectSetting( + @Param('id', UUIDValidationPipe) id: IOrganizationProject['id'], + @Body() entity: UpdateProjectSettingDTO + ): Promise { + return await this.commandBus.execute( + new OrganizationProjectSettingUpdateCommand(id, entity) ); } diff --git a/packages/core/src/organization-project/organization-project.entity.ts b/packages/core/src/organization-project/organization-project.entity.ts index 0c0210e9997..df0479412d0 100644 --- a/packages/core/src/organization-project/organization-project.entity.ts +++ b/packages/core/src/organization-project/organization-project.entity.ts @@ -10,7 +10,7 @@ import { JoinTable, } from 'typeorm'; import { ApiPropertyOptional } from '@nestjs/swagger'; -import { IsOptional, IsUUID } from 'class-validator'; +import { IsBoolean, IsOptional, IsUUID } from 'class-validator'; import { CurrenciesEnum, IActivity, @@ -126,6 +126,30 @@ export class OrganizationProject extends TenantOrganizationBaseEntity implements @Column({ length: 500, nullable: true }) imageUrl?: string; + /** + * Project Integration Setting + */ + @ApiPropertyOptional({ type: () => Number }) + @IsOptional() + @IsBoolean() + @Index() + @Column({ nullable: true }) + externalRepositoryId?: number; + + @ApiPropertyOptional({ type: () => Boolean }) + @IsOptional() + @IsBoolean() + @Index() + @Column({ default: true, nullable: true }) + isTasksAutoSync?: boolean; + + @ApiPropertyOptional({ type: () => Boolean }) + @IsOptional() + @IsBoolean() + @Index() + @Column({ default: true, nullable: true }) + isTasksAutoSyncOnLabel?: boolean; + /* |-------------------------------------------------------------------------- | @ManyToOne diff --git a/packages/core/src/reports/report.service.ts b/packages/core/src/reports/report.service.ts index 48d089f7531..7f1fa1d11b7 100644 --- a/packages/core/src/reports/report.service.ts +++ b/packages/core/src/reports/report.service.ts @@ -38,7 +38,7 @@ export class ReportService extends CrudService { const { items, total } = await super.findAll(filter); const menuItems = await this.getMenuItems(filter); - + const orgMenuItems = indexBy(menuItems, 'id'); const mapItems = items.map((item) => { @@ -68,13 +68,10 @@ export class ReportService extends CrudService { public async getMenuItems( options: GetReportMenuItemsInput ): Promise { - - const start = new Date(); - const { organizationId } = options; const tenantId = RequestContext.currentTenantId() || options.tenantId; - const res = await this.repository.find({ + return await this.repository.find({ join: { alias: this.alias, innerJoin: { @@ -89,14 +86,6 @@ export class ReportService extends CrudService { } } }); - - const end = new Date(); - const time = (end.getTime() - start.getTime()) / 1000; - - this.logger.log(`getMenuItems took ${time} seconds`); - console.log(`getMenuItems took ${time} seconds`); - - return res; } async updateReportMenu( diff --git a/packages/core/src/tasks/commands/handlers/task-create.handler.ts b/packages/core/src/tasks/commands/handlers/task-create.handler.ts index 007be999218..356f625ed23 100644 --- a/packages/core/src/tasks/commands/handlers/task-create.handler.ts +++ b/packages/core/src/tasks/commands/handlers/task-create.handler.ts @@ -1,5 +1,5 @@ import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpException, HttpStatus, Logger } from '@nestjs/common'; import { ITask } from '@gauzy/contracts'; import { RequestContext } from './../../../core/context'; import { TaskCreateCommand } from './../task-create.command'; @@ -8,6 +8,8 @@ import { TaskService } from '../../task.service'; @CommandHandler(TaskCreateCommand) export class TaskCreateHandler implements ICommandHandler { + private readonly logger = new Logger('TaskCreateHandler'); + constructor( private readonly _taskService: TaskService, private readonly _organizationProjectService: OrganizationProjectService @@ -29,6 +31,7 @@ export class TaskCreateHandler implements ICommandHandler { const taskPrefix = project ? project.name.substring(0, 3) : null; const maxNumber = await this._taskService.getMaxTaskNumberByProject({ + tenantId, organizationId, projectId, }); @@ -41,7 +44,7 @@ export class TaskCreateHandler implements ICommandHandler { organizationId, }); } catch (error) { - console.log('Error while creating task %s', error?.message); + this.logger.error(`Error while creating task: ${error.message}`, error.message); throw new HttpException({ message: error?.message, error }, HttpStatus.BAD_REQUEST); } } diff --git a/packages/core/src/tasks/commands/handlers/task-update.handler.ts b/packages/core/src/tasks/commands/handlers/task-update.handler.ts index 2fbe9601130..5fae7d87e53 100644 --- a/packages/core/src/tasks/commands/handlers/task-update.handler.ts +++ b/packages/core/src/tasks/commands/handlers/task-update.handler.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpException, HttpStatus, Logger } from '@nestjs/common'; import { CommandHandler, ICommandHandler } from '@nestjs/cqrs'; import { ITask, ITaskUpdateInput } from '@gauzy/contracts'; import { RequestContext } from 'core/context'; @@ -7,6 +7,8 @@ import { TaskUpdateCommand } from '../task-update.command'; @CommandHandler(TaskUpdateCommand) export class TaskUpdateHandler implements ICommandHandler { + private readonly logger = new Logger('TaskUpdateHandler'); + constructor( private readonly _taskService: TaskService ) { } @@ -51,7 +53,7 @@ export class TaskUpdateHandler implements ICommandHandler { id }); } catch (error) { - console.error('Error while creating/updating task:', error?.message); + this.logger.error(`Error while updating task: ${error.message}`, error.message); throw new HttpException({ message: error?.message, error }, HttpStatus.BAD_REQUEST); } } diff --git a/packages/core/src/tasks/task.service.ts b/packages/core/src/tasks/task.service.ts index 22a32acb2a2..b399a3bac40 100644 --- a/packages/core/src/tasks/task.service.ts +++ b/packages/core/src/tasks/task.service.ts @@ -1,4 +1,4 @@ -import { Injectable, BadRequestException } from '@nestjs/common'; +import { Injectable, BadRequestException, HttpStatus, HttpException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { IsNull, @@ -497,7 +497,7 @@ export class TaskService extends TenantAwareCrudService { const { maxTaskNumber } = await query.getRawOne(); return maxTaskNumber; } catch (error) { - throw new BadRequestException(error); + throw new HttpException({ message: error?.message, error }, HttpStatus.BAD_REQUEST); } } diff --git a/packages/plugins/integration-github/src/octokit.service.ts b/packages/plugins/integration-github/src/octokit.service.ts index ddb6844a46c..9026f50bb4a 100644 --- a/packages/plugins/integration-github/src/octokit.service.ts +++ b/packages/plugins/integration-github/src/octokit.service.ts @@ -2,6 +2,7 @@ import { Inject, Injectable, Logger } from '@nestjs/common'; import * as chalk from 'chalk'; import { App } from 'octokit'; import { ResponseHeaders as OctokitResponseHeaders } from "@octokit/types"; +import { parseConfig } from './probot.helpers'; import { ModuleProviders, ProbotConfig } from './probot.types'; const GITHUB_API_VERSION = process.env.GAUZY_GITHUB_API_VERSION || '2022-11-28'; // Define a default version @@ -26,13 +27,14 @@ export class OctokitService { /** */ try { if (this.config.appId && this.config.privateKey) { + const config = parseConfig(this.config); this.app = new App({ - appId: this.config.appId, - privateKey: this.config.privateKey, - clientId: this.config.clientId, - clientSecret: this.config.clientSecret, + appId: config.appId, + privateKey: config.privateKey, + clientId: config.clientId, + clientSecret: config.clientSecret, }); - console.log(chalk.green(`Octokit App successfully initialized.`)); + console.log(chalk.magenta(`Octokit App Configuration ${JSON.stringify(config)}`)); } else { console.error(chalk.red(`Octokit App initialization failed: Missing appId or privateKey.`)); } diff --git a/packages/plugins/integration-github/src/probot.helpers.ts b/packages/plugins/integration-github/src/probot.helpers.ts index da3dde942db..9bf046b0ea6 100644 --- a/packages/plugins/integration-github/src/probot.helpers.ts +++ b/packages/plugins/integration-github/src/probot.helpers.ts @@ -3,6 +3,7 @@ import { Probot } from 'probot'; import SmeeClient from 'smee-client'; import { Octokit } from '@octokit/rest'; import { createAppAuth } from '@octokit/auth-app'; +import * as chalk from 'chalk'; import { OctokitConfig, ProbotConfig } from './probot.types'; const GITHUB_API_URL = 'https://api.github.com'; @@ -30,6 +31,7 @@ export const parseConfig = (config: ProbotConfig): Record => ({ */ export const createProbot = (config: ProbotConfig): Probot => { const parsedConfig = parseConfig(config); + console.log(chalk.magenta(`Probot Configuration ${JSON.stringify(parsedConfig)}`)); return new Probot({ ...parsedConfig, // Spread the parsed configuration properties });