From 715911afa022b608043d80527b883373be89e42d Mon Sep 17 00:00:00 2001 From: nael Date: Wed, 17 Jul 2024 22:26:49 +0200 Subject: [PATCH] :white_check_mark: Ticket & Comment unified write --- packages/api/package.json | 1 + .../@token-refresh/refresh.service.ts | 17 +- .../ticketing/services/front/front.service.ts | 35 ++-- .../ticketing/comment/services/front/index.ts | 15 +- .../comment/services/gitlab/index.ts | 22 +- .../comment/services/gorgias/index.ts | 16 +- .../ticketing/comment/services/jira/index.ts | 15 +- .../comment/services/jira/mappers.ts | 17 +- .../comment/services/zendesk/index.ts | 17 +- .../ticketing/comment/types/model.unified.ts | 5 +- .../ticketing/ticket/services/github/index.ts | 96 --------- .../ticket/services/github/mappers.ts | 144 ------------- .../ticketing/ticket/services/github/types.ts | 195 ------------------ .../ticketing/ticket/services/gitlab/index.ts | 22 +- .../ticket/services/gitlab/mappers.ts | 16 ++ .../ticketing/ticket/services/gitlab/types.ts | 1 + .../ticketing/ticket/services/jira/index.ts | 23 ++- .../ticketing/ticket/services/jira/mappers.ts | 24 ++- .../ticketing/ticket/types/model.unified.ts | 2 +- pnpm-lock.yaml | 12 +- 20 files changed, 178 insertions(+), 517 deletions(-) delete mode 100644 packages/api/src/ticketing/ticket/services/github/index.ts delete mode 100644 packages/api/src/ticketing/ticket/services/github/mappers.ts delete mode 100644 packages/api/src/ticketing/ticket/services/github/types.ts diff --git a/packages/api/package.json b/packages/api/package.json index 84f115119..e2a2096a5 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -64,6 +64,7 @@ "passport-jwt": "^4.0.1", "passport-local": "^1.0.0", "pino-pretty": "^10.2.3", + "qs": "^6.12.3", "reflect-metadata": "^0.1.13", "rxjs": "^7.8.1", "stytch": "^10.5.0", diff --git a/packages/api/src/@core/connections/@token-refresh/refresh.service.ts b/packages/api/src/@core/connections/@token-refresh/refresh.service.ts index c5776a8ca..c56b2a069 100644 --- a/packages/api/src/@core/connections/@token-refresh/refresh.service.ts +++ b/packages/api/src/@core/connections/@token-refresh/refresh.service.ts @@ -19,13 +19,14 @@ export class OAuthTokenRefreshService implements OnModuleInit { @Cron(CronExpression.EVERY_HOUR) async handleCron() { - // refresh all tokens that expire in less than 10 hours - const tenHoursFromNow = new Date(); - tenHoursFromNow.setHours(tenHoursFromNow.getHours() + 10); + const now = new Date(); + + const tenHoursFromNow = new Date(now.getTime() + 10 * 60 * 60 * 1000); + const connectionsToRefresh = await this.prisma.connections.findMany({ where: { expiration_timestamp: { - lte: tenHoursFromNow, + lte: tenHoursFromNow.toISOString(), }, }, }); @@ -34,7 +35,8 @@ export class OAuthTokenRefreshService implements OnModuleInit { try { if (connection.refresh_token) { const account_url = - connection.provider_slug == 'zoho' ? connection.account_url : ''; + connection.provider_slug === 'zoho' ? connection.account_url : ''; + await this.categoryConnectionRegistry .getService(connection.vertical.toLowerCase()) .handleTokensRefresh( @@ -46,7 +48,10 @@ export class OAuthTokenRefreshService implements OnModuleInit { ); } } catch (error) { - this.logger.error('failed to refresh token ', error); + this.logger.error( + `Failed to refresh token for connection: ${connection.id_connection}`, + error, + ); } } } diff --git a/packages/api/src/@core/connections/ticketing/services/front/front.service.ts b/packages/api/src/@core/connections/ticketing/services/front/front.service.ts index c5d881974..5a3425ef3 100644 --- a/packages/api/src/@core/connections/ticketing/services/front/front.service.ts +++ b/packages/api/src/@core/connections/ticketing/services/front/front.service.ts @@ -15,6 +15,7 @@ import { OAuth2AuthData, providerToType, } from '@panora/shared'; +import qs from 'qs'; import axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; import { ITicketingConnectionService } from '../../types'; @@ -145,26 +146,30 @@ export class FrontConnectionService implements ITicketingConnectionService { async handleTokenRefresh(opts: RefreshParams) { try { const { connectionId, refreshToken, projectId } = opts; - const formData = new URLSearchParams({ - grant_type: 'refresh_token', - refresh_token: this.cryptoService.decrypt(refreshToken), - }); const CREDENTIALS = (await this.cService.getCredentials( projectId, this.type, )) as OAuth2AuthData; - const res = await axios.post( - `https://app.frontapp.com/oauth/token`, - formData.toString(), - { - headers: { - 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', - Authorization: `Basic ${Buffer.from( - `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, - ).toString('base64')}`, - }, + + const DATA = new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: this.cryptoService.decrypt(refreshToken), + }).toString(); + + const config = { + method: 'post', + url: 'https://app.frontapp.com/oauth/token', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + Authorization: `Basic ${Buffer.from( + `${CREDENTIALS.CLIENT_ID}:${CREDENTIALS.CLIENT_SECRET}`, + ).toString('base64')}`, }, - ); + data: DATA, + }; + + const res = await axios(config); + const data: FrontOAuthResponse = res.data; await this.prisma.connections.update({ where: { diff --git a/packages/api/src/ticketing/comment/services/front/index.ts b/packages/api/src/ticketing/comment/services/front/index.ts index de3d88345..8a07c3ec7 100644 --- a/packages/api/src/ticketing/comment/services/front/index.ts +++ b/packages/api/src/ticketing/comment/services/front/index.ts @@ -1,17 +1,16 @@ -import { Injectable } from '@nestjs/common'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; -import { ICommentService } from '@ticketing/comment/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { Injectable } from '@nestjs/common'; import { TicketingObject } from '@ticketing/@lib/@types'; -import { FrontCommentInput, FrontCommentOutput } from './types'; -import { ServiceRegistry } from '../registry.service'; import { Utils } from '@ticketing/@lib/@utils'; -import { SyncParam } from '@@core/utils/types/interface'; +import { ICommentService } from '@ticketing/comment/types'; +import axios from 'axios'; import * as FormData from 'form-data'; +import { ServiceRegistry } from '../registry.service'; +import { FrontCommentInput, FrontCommentOutput } from './types'; @Injectable() export class FrontService implements ICommentService { diff --git a/packages/api/src/ticketing/comment/services/gitlab/index.ts b/packages/api/src/ticketing/comment/services/gitlab/index.ts index 759f8dc27..7be0afe6b 100644 --- a/packages/api/src/ticketing/comment/services/gitlab/index.ts +++ b/packages/api/src/ticketing/comment/services/gitlab/index.ts @@ -1,18 +1,16 @@ -import { Injectable } from '@nestjs/common'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; -import { ICommentService } from '@ticketing/comment/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { Injectable } from '@nestjs/common'; import { TicketingObject } from '@ticketing/@lib/@types'; -import { GitlabCommentInput, GitlabCommentOutput } from './types'; -import { ServiceRegistry } from '../registry.service'; import { Utils } from '@ticketing/@lib/@utils'; +import { ICommentService } from '@ticketing/comment/types'; +import axios from 'axios'; import * as fs from 'fs'; -import { SyncParam } from '@@core/utils/types/interface'; -import { OriginalCommentOutput } from '@@core/utils/types/original/original.ticketing'; +import { ServiceRegistry } from '../registry.service'; +import { GitlabCommentInput, GitlabCommentOutput } from './types'; @Injectable() export class GitlabService implements ICommentService { @@ -72,10 +70,12 @@ export class GitlabService implements ICommentService { // Assuming you want to modify the comment object here // For now, we'll just add the uploads to the comment - const data = { + const data: any = { ...commentData, - attachments: uploads, }; + if (uploads.length > 0) { + data.attachments = uploads; + } const ticket = await this.prisma.tcg_tickets.findFirst({ where: { diff --git a/packages/api/src/ticketing/comment/services/gorgias/index.ts b/packages/api/src/ticketing/comment/services/gorgias/index.ts index 5af59ce28..a6fe3af1c 100644 --- a/packages/api/src/ticketing/comment/services/gorgias/index.ts +++ b/packages/api/src/ticketing/comment/services/gorgias/index.ts @@ -1,17 +1,15 @@ -import { Injectable } from '@nestjs/common'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; -import { ICommentService } from '@ticketing/comment/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { Injectable } from '@nestjs/common'; import { TicketingObject } from '@ticketing/@lib/@types'; -import { GorgiasCommentInput, GorgiasCommentOutput } from './types'; -import { ServiceRegistry } from '../registry.service'; import { Utils } from '@ticketing/@lib/@utils'; -import * as fs from 'fs'; -import { SyncParam } from '@@core/utils/types/interface'; +import { ICommentService } from '@ticketing/comment/types'; +import axios from 'axios'; +import { ServiceRegistry } from '../registry.service'; +import { GorgiasCommentInput, GorgiasCommentOutput } from './types'; @Injectable() export class GorgiasService implements ICommentService { diff --git a/packages/api/src/ticketing/comment/services/jira/index.ts b/packages/api/src/ticketing/comment/services/jira/index.ts index a364235e1..b8f113903 100644 --- a/packages/api/src/ticketing/comment/services/jira/index.ts +++ b/packages/api/src/ticketing/comment/services/jira/index.ts @@ -1,18 +1,17 @@ -import { Injectable } from '@nestjs/common'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; -import { ICommentService } from '@ticketing/comment/types'; +import { SyncParam } from '@@core/utils/types/interface'; +import { Injectable } from '@nestjs/common'; import { TicketingObject } from '@ticketing/@lib/@types'; -import { JiraCommentInput, JiraCommentOutput } from './types'; -import { ServiceRegistry } from '../registry.service'; import { Utils } from '@ticketing/@lib/@utils'; -import { SyncParam } from '@@core/utils/types/interface'; +import { ICommentService } from '@ticketing/comment/types'; +import axios from 'axios'; import * as FormData from 'form-data'; import * as fs from 'fs'; +import { ServiceRegistry } from '../registry.service'; +import { JiraCommentInput, JiraCommentOutput } from './types'; @Injectable() export class JiraService implements ICommentService { diff --git a/packages/api/src/ticketing/comment/services/jira/mappers.ts b/packages/api/src/ticketing/comment/services/jira/mappers.ts index 88a04d29e..b36e5258a 100644 --- a/packages/api/src/ticketing/comment/services/jira/mappers.ts +++ b/packages/api/src/ticketing/comment/services/jira/mappers.ts @@ -7,6 +7,7 @@ import { UnifiedCommentOutput, } from '@ticketing/comment/types/model.unified'; import { JiraCommentInput, JiraCommentOutput } from './types'; +import { OriginalCommentOutput } from '@@core/utils/types/original/original.ticketing'; @Injectable() export class JiraCommentMapper implements ICommentMapper { constructor(private mappersRegistry: MappersRegistry, private utils: Utils) { @@ -21,7 +22,21 @@ export class JiraCommentMapper implements ICommentMapper { }[], ): Promise { const result: JiraCommentInput = { - body: source.body, + body: { + content: [ + { + content: [ + { + text: source.body, + type: 'text', + }, + ], + type: 'paragraph', + }, + ], + type: 'doc', + version: 1, + }, }; if (source.attachments) { result.attachments = source.attachments as string[]; diff --git a/packages/api/src/ticketing/comment/services/zendesk/index.ts b/packages/api/src/ticketing/comment/services/zendesk/index.ts index 1d07de8ae..96b688da7 100644 --- a/packages/api/src/ticketing/comment/services/zendesk/index.ts +++ b/packages/api/src/ticketing/comment/services/zendesk/index.ts @@ -1,18 +1,17 @@ -import { Injectable } from '@nestjs/common'; +import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; +import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; import { LoggerService } from '@@core/@core-services/logger/logger.service'; import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; -import { ICommentService } from '@ticketing/comment/types'; -import { TicketingObject } from '@ticketing/@lib/@types'; +import { SyncParam } from '@@core/utils/types/interface'; import { OriginalCommentOutput } from '@@core/utils/types/original/original.ticketing'; +import { Injectable } from '@nestjs/common'; +import { TicketingObject } from '@ticketing/@lib/@types'; +import { Utils } from '@ticketing/@lib/@utils'; +import { ICommentService } from '@ticketing/comment/types'; +import axios from 'axios'; import { ServiceRegistry } from '../registry.service'; import { ZendeskCommentInput, ZendeskCommentOutput } from './types'; -import { EnvironmentService } from '@@core/@core-services/environment/environment.service'; -import { SyncParam } from '@@core/utils/types/interface'; -import { Utils } from '@ticketing/@lib/@utils'; @Injectable() export class ZendeskService implements ICommentService { constructor( diff --git a/packages/api/src/ticketing/comment/types/model.unified.ts b/packages/api/src/ticketing/comment/types/model.unified.ts index bb9517abb..7b46eab60 100644 --- a/packages/api/src/ticketing/comment/types/model.unified.ts +++ b/packages/api/src/ticketing/comment/types/model.unified.ts @@ -1,8 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { - UnifiedAttachmentInput, - UnifiedAttachmentOutput, -} from '@ticketing/attachment/types/model.unified'; +import { UnifiedAttachmentOutput } from '@ticketing/attachment/types/model.unified'; import { IsBoolean, IsIn, IsOptional, IsString, IsUUID } from 'class-validator'; export type CommentCreatorType = 'USER' | 'CONTACT'; diff --git a/packages/api/src/ticketing/ticket/services/github/index.ts b/packages/api/src/ticketing/ticket/services/github/index.ts deleted file mode 100644 index a5fd754d8..000000000 --- a/packages/api/src/ticketing/ticket/services/github/index.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { LoggerService } from '@@core/@core-services/logger/logger.service'; -import { PrismaService } from '@@core/@core-services/prisma/prisma.service'; -import { EncryptionService } from '@@core/@core-services/encryption/encryption.service'; -import { TicketingObject } from '@ticketing/@lib/@types'; -import { ITicketService } from '@ticketing/ticket/types'; -import { ApiResponse } from '@@core/utils/types'; -import axios from 'axios'; -import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors'; -import { ServiceRegistry } from '../registry.service'; -import { GithubTicketInput, GithubTicketOutput } from './types'; -import { SyncParam } from '@@core/utils/types/interface'; - -//TODO -@Injectable() -export class GithubService implements ITicketService { - constructor( - private prisma: PrismaService, - private logger: LoggerService, - private cryptoService: EncryptionService, - private registry: ServiceRegistry, - ) { - this.logger.setContext( - TicketingObject.ticket.toUpperCase() + ':' + GithubService.name, - ); - this.registry.registerService('github', this); - } - async addTicket( - ticketData: GithubTicketInput, - linkedUserId: string, - ): Promise> { - try { - const connection = await this.prisma.connections.findFirst({ - where: { - id_linked_user: linkedUserId, - provider_slug: 'github', - vertical: 'ticketing', - }, - }); - const dataBody = ticketData; - const org = ''; - const repo = ''; - const resp = await axios.post( - `${connection.account_url}/repos/${org}/${repo}/issues`, - JSON.stringify(dataBody), - { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, - }, - ); - return { - data: resp.data, - message: 'Github ticket created', - statusCode: 201, - }; - } catch (error) { - throw error; - } - } - async sync(data: SyncParam): Promise> { - try { - const { linkedUserId } = data; - - const connection = await this.prisma.connections.findFirst({ - where: { - id_linked_user: linkedUserId, - provider_slug: 'github', - vertical: 'ticketing', - }, - }); - const owner = ''; - const repo = ''; - const resp = await axios.get(`${connection.account_url}/repos/issues`, { - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${this.cryptoService.decrypt( - connection.access_token, - )}`, - }, - }); - this.logger.log(`Synced github tickets !`); - - return { - data: resp.data, - message: 'Github tickets retrieved', - statusCode: 200, - }; - } catch (error) { - throw error; - } - } -} diff --git a/packages/api/src/ticketing/ticket/services/github/mappers.ts b/packages/api/src/ticketing/ticket/services/github/mappers.ts deleted file mode 100644 index 0d21fc7ff..000000000 --- a/packages/api/src/ticketing/ticket/services/github/mappers.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry'; -import { CoreUnification } from '@@core/@core-services/unification/core-unification.service'; -import { OriginalTagOutput } from '@@core/utils/types/original/original.ticketing'; -import { UnifiedTagOutput } from '@ticketing/tag/types/model.unified'; -import { Injectable } from '@nestjs/common'; -import { TicketingObject } from '@ticketing/@lib/@types'; -import { Utils } from '@ticketing/@lib/@utils'; -import { ITicketMapper } from '@ticketing/ticket/types'; -import { - TicketStatus, - UnifiedTicketInput, - UnifiedTicketOutput, -} from '@ticketing/ticket/types/model.unified'; -import { GithubTicketInput, GithubTicketOutput } from './types'; - -@Injectable() -export class GithubTicketMapper implements ITicketMapper { - constructor( - private mappersRegistry: MappersRegistry, - private utils: Utils, - private coreUnificationService: CoreUnification, - ) { - this.mappersRegistry.registerService('ticketing', 'ticket', 'github', this); - } - - mapToTicketStatus(data: 'open' | 'closed'): TicketStatus { - switch (data) { - case 'open': - return 'OPEN'; - case 'closed': - return 'CLOSED'; - } - } - - async desunify( - source: UnifiedTicketInput, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): Promise { - const result: GithubTicketInput = { - title: source.name, - body: source.description, - assignees: [], - labels: (source.tags as string[]) || [], - }; - - // Assuming that 'assigned_to' contains user UUIDs that need to be converted to GitHub usernames - if (source.assigned_to && source.assigned_to.length > 0) { - for (const assigneeUuid of source.assigned_to) { - const email = await this.utils.getAssigneeMetadataFromUuid( - assigneeUuid, - ); - if (email) { - result.assignees.push(email); - } - } - } - - if (customFieldMappings && source.field_mappings) { - for (const [k, v] of Object.entries(source.field_mappings)) { - const mapping = customFieldMappings.find( - (mapping) => mapping.slug === k, - ); - if (mapping) { - result[mapping.remote_id] = v; - // TODO: Since GitHub API doesn't support arbitrary custom fields directly, - // you might need to handle these mappings in a specific way, - // such as appending them to the body or handling them externally. - } - } - } - - return result; - } - - async unify( - source: GithubTicketOutput | GithubTicketOutput[], - connectionId: string, - customFieldMappings?: { - slug: string; - remote_id: string; - }[], - ): Promise { - // If the source is not an array, convert it to an array for mapping - const sourcesArray = Array.isArray(source) ? source : [source]; - - return Promise.all( - sourcesArray.map(async (ticket) => { - const field_mappings: { [key: string]: any } = {}; - if (customFieldMappings) { - for (const mapping of customFieldMappings) { - field_mappings[mapping.slug] = ticket.labels.find( - (label) => label.name === mapping.remote_id, - )?.description; - } - } - - const opts: any = {}; - - if (ticket.assignees && ticket.assignees.length > 0) { - opts.assigned_to = []; - for (const assignee of ticket.assignees) { - const userUuid = await this.utils.getUserUuidFromRemoteId( - String(assignee.id), - connectionId, - ); - if (userUuid) { - opts.assigned_to.push(userUuid); - } - } - } - - /*TODO: first implement github tags - if (ticket.labels) { - const tags = (await this.coreUnificationService.unify< - OriginalTagOutput[] - >({ - sourceObject: ticket.labels, - targetType: TicketingObject.tag, - providerName: 'github', - vertical: 'ticketing', - connectionId: connectionId, - customFieldMappings: [], - })) as UnifiedTagOutput[]; - opts.tags = tags; - }*/ - - const unifiedTicket: UnifiedTicketOutput = { - remote_id: String(ticket.id), - remote_data: ticket, - name: ticket.title, - description: ticket.body, - status: this.mapToTicketStatus(ticket.state as any), - field_mappings: field_mappings, - ...opts, - }; - - return unifiedTicket; - }), - ); - } -} diff --git a/packages/api/src/ticketing/ticket/services/github/types.ts b/packages/api/src/ticketing/ticket/services/github/types.ts deleted file mode 100644 index de468bb89..000000000 --- a/packages/api/src/ticketing/ticket/services/github/types.ts +++ /dev/null @@ -1,195 +0,0 @@ -export type GithubTicketInput = { - title: string; - body?: string; - assignee?: string | null; - milestone?: string | number | null; - labels?: string[]; - assignees?: string[]; -}; - -export type GithubTicketOutput = { - id: number; - node_id: string; - url: string; - repository_url: string; - labels_url: string; - comments_url: string; - events_url: string; - html_url: string; - number: number; - state: string; - title: string; - body: string; - user: GitHubUser; - labels: GitHubLabel[]; - assignee: GitHubUser; - assignees: GitHubUser[]; - milestone: GitHubMilestone; - locked: boolean; - active_lock_reason: string; - comments: number; - pull_request: GitHubPullRequest; - closed_at: string | null; - created_at: string; - updated_at: string; - repository: GitHubRepository; - author_association: string; -}; - -type GitHubUser = { - login: string; - id: number; - node_id: string; - avatar_url: string; - gravatar_id: string; - url: string; - html_url: string; - followers_url: string; - following_url: string; - gists_url: string; - starred_url: string; - subscriptions_url: string; - organizations_url: string; - repos_url: string; - events_url: string; - received_events_url: string; - type: string; - site_admin: boolean; -}; - -type GitHubLabel = { - id: number; - node_id: string; - url: string; - name: string; - description: string; - color: string; - default: boolean; -}; - -type GitHubMilestone = { - url: string; - html_url: string; - labels_url: string; - id: number; - node_id: string; - number: number; - state: string; - title: string; - description: string; - creator: GitHubUser; - open_issues: number; - closed_issues: number; - created_at: string; - updated_at: string; - closed_at: string | null; - due_on: string | null; -}; - -type GitHubPullRequest = { - url: string; - html_url: string; - diff_url: string; - patch_url: string; -}; - -type GitHubRepository = { - id: number; - node_id: string; - name: string; - full_name: string; - owner: GitHubUser; - private: boolean; - html_url: string; - description: string; - fork: boolean; - url: string; - archive_url: string; - assignees_url: string; - blobs_url: string; - branches_url: string; - collaborators_url: string; - comments_url: string; - commits_url: string; - compare_url: string; - contents_url: string; - contributors_url: string; - deployments_url: string; - downloads_url: string; - events_url: string; - forks_url: string; - git_commits_url: string; - git_refs_url: string; - git_tags_url: string; - git_url: string; - issue_comment_url: string; - issue_events_url: string; - issues_url: string; - keys_url: string; - labels_url: string; - languages_url: string; - merges_url: string; - milestones_url: string; - notifications_url: string; - pulls_url: string; - releases_url: string; - ssh_url: string; - stargazers_url: string; - statuses_url: string; - subscribers_url: string; - subscription_url: string; - tags_url: string; - teams_url: string; - trees_url: string; - clone_url: string; - mirror_url: string | null; - hooks_url: string; - svn_url: string; - homepage: string | null; - language: string | null; - forks_count: number; - stargazers_count: number; - watchers_count: number; - size: number; - default_branch: string; - open_issues_count: number; - is_template: boolean; - topics: string[]; - has_issues: boolean; - has_projects: boolean; - has_wiki: boolean; - has_pages: boolean; - has_downloads: boolean; - archived: boolean; - disabled: boolean; - visibility: string; - pushed_at: string; - created_at: string; - updated_at: string; - permissions: GitHubPermissions; - allow_rebase_merge: boolean; - template_repository: any; // Replace 'any' with the actual structure, if available. - temp_clone_token: string; - allow_squash_merge: boolean; - allow_auto_merge: boolean; - delete_branch_on_merge: boolean; - allow_merge_commit: boolean; - subscribers_count: number; - network_count: number; - license: GitHubLicense; - forks: number; -}; -type GitHubLicense = { - key: string; - name: string; - url: string | null; - spdx_id: string | null; - node_id: string; - html_url: string; -}; - -type GitHubPermissions = { - admin: boolean; - push: boolean; - pull: boolean; -}; diff --git a/packages/api/src/ticketing/ticket/services/gitlab/index.ts b/packages/api/src/ticketing/ticket/services/gitlab/index.ts index 68e1b31ce..6904fc9c0 100644 --- a/packages/api/src/ticketing/ticket/services/gitlab/index.ts +++ b/packages/api/src/ticketing/ticket/services/gitlab/index.ts @@ -36,11 +36,14 @@ export class GitlabService implements ITicketService { vertical: 'ticketing', }, }); - const dataBody = ticketData; + const { comment, ...ticketD } = ticketData; + const DATA = { + ...ticketD, + }; const resp = await axios.post( `${connection.account_url}/projects/${ticketData.project_id}/issues`, - JSON.stringify(dataBody), + JSON.stringify(DATA), { headers: { 'Content-Type': 'application/json', @@ -50,6 +53,21 @@ export class GitlabService implements ITicketService { }, }, ); + //insert comment + if (comment) { + const resp_ = await axios.post( + `${connection.account_url}/projects/${ticketData.project_id}/issues/${resp.data.iid}/notes`, + JSON.stringify(comment), + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + } return { data: resp.data, message: 'Gitlab ticket created', diff --git a/packages/api/src/ticketing/ticket/services/gitlab/mappers.ts b/packages/api/src/ticketing/ticket/services/gitlab/mappers.ts index cfef7093c..908fb3a1e 100644 --- a/packages/api/src/ticketing/ticket/services/gitlab/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/gitlab/mappers.ts @@ -13,6 +13,8 @@ import { import { GitlabTicketInput, GitlabTicketOutput } from './types'; import { GitlabTagOutput } from '@ticketing/tag/services/gitlab/types'; import { IngestDataService } from '@@core/@core-services/unification/ingest-data.service'; +import { UnifiedCommentOutput } from '@ticketing/comment/types/model.unified'; +import { GitlabCommentInput } from '@ticketing/comment/services/gitlab/types'; @Injectable() export class GitlabTicketMapper implements ITicketMapper { @@ -31,6 +33,7 @@ export class GitlabTicketMapper implements ITicketMapper { slug: string; remote_id: string; }[], + connectionId?: string, ): Promise { const remote_project_id = await this.utils.getCollectionRemoteIdFromUuid( source.collections[0] as string, @@ -61,6 +64,19 @@ export class GitlabTicketMapper implements ITicketMapper { result.labels = tags; } + if (source.comment) { + const comment = + (await this.coreUnificationService.desunify({ + sourceObject: source.comment, + targetType: TicketingObject.comment, + providerName: 'gitlab', + vertical: 'ticketing', + connectionId: connectionId, + customFieldMappings: [], + })) as GitlabCommentInput; + result.comment = comment; + } + // TODO - Custom fields mapping // if (customFieldMappings && source.field_mappings) { // result.meta = {}; // Ensure meta exists diff --git a/packages/api/src/ticketing/ticket/services/gitlab/types.ts b/packages/api/src/ticketing/ticket/services/gitlab/types.ts index 2d9af9832..dc77c446a 100644 --- a/packages/api/src/ticketing/ticket/services/gitlab/types.ts +++ b/packages/api/src/ticketing/ticket/services/gitlab/types.ts @@ -33,6 +33,7 @@ interface GitlabTicket { severity: string; _links: Links; task_completion_status: TaskCompletionStatus; + [key: string]: any; } interface Epic { diff --git a/packages/api/src/ticketing/ticket/services/jira/index.ts b/packages/api/src/ticketing/ticket/services/jira/index.ts index ef2ab9576..db984a41b 100644 --- a/packages/api/src/ticketing/ticket/services/jira/index.ts +++ b/packages/api/src/ticketing/ticket/services/jira/index.ts @@ -127,6 +127,13 @@ export class JiraService implements ITicketService { ...baseFields, }; }*/ + const { comment, ...baseFields } = ticketData.fields; + + ticketData = { + fields: { + ...baseFields, + }, + }; const resp = await axios.post( `${connection.account_url}/issue`, JSON.stringify(ticketData), @@ -140,7 +147,21 @@ export class JiraService implements ITicketService { }, ); - // todo: Add comment if someone wants to add one when creation of the ticket + if (comment && comment[0]) { + // Add comment if someone wants to add one when creation of the ticket + const resp_comment = await axios.post( + `${connection.account_url}/issue/${resp.data.id}/comment`, + JSON.stringify(comment[0]), + { + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${this.cryptoService.decrypt( + connection.access_token, + )}`, + }, + }, + ); + } // Process attachments let uploads = []; diff --git a/packages/api/src/ticketing/ticket/services/jira/mappers.ts b/packages/api/src/ticketing/ticket/services/jira/mappers.ts index 747f26206..7fb703797 100644 --- a/packages/api/src/ticketing/ticket/services/jira/mappers.ts +++ b/packages/api/src/ticketing/ticket/services/jira/mappers.ts @@ -14,6 +14,12 @@ import { UnifiedTicketOutput, } from '@ticketing/ticket/types/model.unified'; import { JiraTicketInput, JiraTicketOutput } from './types'; +import { OriginalCommentOutput } from '@@core/utils/types/original/original.ticketing'; +import { UnifiedCommentOutput } from '@ticketing/comment/types/model.unified'; +import { + JiraCommentInput, + JiraCommentOutput, +} from '@ticketing/comment/services/jira/types'; @Injectable() export class JiraTicketMapper implements ITicketMapper { @@ -72,6 +78,7 @@ export class JiraTicketMapper implements ITicketMapper { slug: string; remote_id: string; }[], + connectionId?: string, ): Promise { if (!source.collections || !source.collections[0]) { throw new ReferenceError( @@ -109,18 +116,31 @@ export class JiraTicketMapper implements ITicketMapper { result.fields.labels = source.tags as string[]; } - if (source.priority) { + /*if (source.priority) { result.fields.priority = { name: this.reverseMapToTicketPriority( source.priority as TicketPriority, ), }; - } + }*/ if (source.attachments) { result.attachments = source.attachments as string[]; // dummy assigning we'll insert them in the service func } + if (source.comment) { + const comment = + (await this.coreUnificationService.desunify({ + sourceObject: source.comment, + targetType: TicketingObject.comment, + providerName: 'jira', + vertical: 'ticketing', + connectionId: connectionId, + customFieldMappings: [], + })) as JiraCommentInput; + result.fields.comment = [comment]; + } + // Map custom fields if applicable /*TODO if (customFieldMappings && source.field_mappings) { result.meta = {}; // Ensure meta exists diff --git a/packages/api/src/ticketing/ticket/types/model.unified.ts b/packages/api/src/ticketing/ticket/types/model.unified.ts index fda14e40d..bedb8eeaa 100644 --- a/packages/api/src/ticketing/ticket/types/model.unified.ts +++ b/packages/api/src/ticketing/ticket/types/model.unified.ts @@ -99,7 +99,7 @@ export class UnifiedTicketInput { description: 'The users UUIDs the ticket is assigned to', }) @IsOptional() - assigned_to?: string[]; //UUID of Users objects ? + assigned_to?: string[]; //UUID of Users objects @ApiPropertyOptional({ type: UnifiedCommentInput, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9dbb24f6f..d9388635c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -531,6 +531,9 @@ importers: pino-pretty: specifier: ^10.2.3 version: 10.3.1 + qs: + specifier: ^6.12.3 + version: 6.12.3 reflect-metadata: specifier: ^0.1.13 version: 0.1.14 @@ -8943,7 +8946,7 @@ packages: dezalgo: 1.0.4 hexoid: 1.0.0 once: 1.4.0 - qs: 6.12.0 + qs: 6.12.3 dev: true /forwarded@0.2.0: @@ -12214,12 +12217,11 @@ packages: dependencies: side-channel: 1.0.6 - /qs@6.12.0: - resolution: {integrity: sha512-trVZiI6RMOkO476zLGaBIzszOdFPnCCXHPG9kn0yuS1uz6xdVxPfZdB3vUig9pxPFDM9BRAgz/YUIVQ1/vuiUg==} + /qs@6.12.3: + resolution: {integrity: sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ==} engines: {node: '>=0.6'} dependencies: side-channel: 1.0.6 - dev: true /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -14002,7 +14004,7 @@ packages: formidable: 2.1.2 methods: 1.1.2 mime: 2.6.0 - qs: 6.12.0 + qs: 6.12.3 semver: 7.6.0 transitivePeerDependencies: - supports-color