Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gitlab Connector #421

Merged
merged 17 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 2 additions & 7 deletions apps/client-ts/src/components/Connection/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DataTableColumnHeader } from "./../shared/data-table-column-header"
import React,{ useState } from "react"
import { ClipboardIcon } from '@radix-ui/react-icons'
import { toast } from "sonner"
import { getLogoURL } from "@panora/shared"


function truncateMiddle(str: string, maxLength: number) {
Expand Down Expand Up @@ -112,13 +113,7 @@ export const columns: ColumnDef<Connection>[] = [
return (
<div className="flex space-x-2">
<Badge variant={"outline"} className="p-1 pr-2">
<img src={
provider == "hubspot" ?
`providers/crm/${provider}.jpg` :
provider == "zoho" ?
`providers/crm/${provider}.webp`
: `providers/crm/${provider}.png`
} className="w-5 h-5 rounded-sm mr-2"
<img src={getLogoURL(provider)} className="w-5 h-5 rounded-sm mr-2"
/>
{provider}
</Badge>
Expand Down
9 changes: 2 additions & 7 deletions apps/client-ts/src/components/Events/columns.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Checkbox } from "@/components/ui/checkbox"

import { DataTableColumnHeader } from "../shared/data-table-column-header"
import { Event } from "./data/schema"
import { getLogoURL } from "@panora/shared"

export const columns: ColumnDef<Event>[] = [
{
Expand Down Expand Up @@ -140,13 +141,7 @@ export const columns: ColumnDef<Event>[] = [
<div className="flex w-[100px] items-center">
{row.getValue("integration") ?
<Badge variant={"outline"} className="p-1 pr-2">
<img src={
provider == "hubspot" ?
`providers/crm/${provider}.jpg` :
provider == "zoho" ?
`providers/crm/${provider}.webp`
: `providers/crm/${provider}.png`
} className="w-5 h-5 rounded-sm mr-2"
<img src={getLogoURL(provider)} className="w-5 h-5 rounded-sm mr-2"
/>
{provider}
</Badge>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
ITicketingConnectionService,
} from '../../types';
import { ServiceRegistry } from '../registry.service';
import { AuthStrategy } from '@panora/shared';
import { AuthStrategy, providersConfig } from '@panora/shared';
import { OAuth2AuthData, providerToType } from '@panora/shared';
import { ConnectionsStrategiesService } from '@@core/connections-strategies/connections-strategies.service';

Expand Down Expand Up @@ -67,7 +67,7 @@ export class GitlabConnectionService implements ITicketingConnectionService {
grant_type: 'authorization_code',
});
const res = await axios.post(
`https://api.gitlab.app/oauth/token`,
`https://gitlab.com/oauth/token`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure the URL used for the OAuth token exchange is configurable rather than hardcoded.

-        `https://gitlab.com/oauth/token`,
+        `${providersConfig['ticketing']['gitlab'].urls.apiUrl}/oauth/token`,

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
`https://gitlab.com/oauth/token`,
`${providersConfig['ticketing']['gitlab'].urls.apiUrl}/oauth/token`,

formData.toString(),
{
headers: {
Expand All @@ -80,6 +80,8 @@ export class GitlabConnectionService implements ITicketingConnectionService {
'OAuth credentials : gitlab ticketing ' + JSON.stringify(data),
);

// console.log("Gitlab Credentials : ", data)

let db_res;
const connection_token = uuidv4();

Expand All @@ -105,6 +107,7 @@ export class GitlabConnectionService implements ITicketingConnectionService {
connection_token: connection_token,
provider_slug: 'gitlab',
vertical: 'ticketing',
account_url: providersConfig['ticketing']['gitlab'].urls.apiUrl,
token_type: 'oauth',
access_token: this.cryptoService.encrypt(data.access_token),
refresh_token: this.cryptoService.encrypt(data.refresh_token),
Expand Down Expand Up @@ -140,7 +143,7 @@ export class GitlabConnectionService implements ITicketingConnectionService {
this.type,
)) as OAuth2AuthData;
const res = await axios.post(
`https://api.gitlab.app/oauth/token`,
`https://gitlab.com/oauth/token`,
formData.toString(),
{
headers: {
Expand All @@ -152,6 +155,7 @@ export class GitlabConnectionService implements ITicketingConnectionService {
},
);
const data: GitlabOAuthResponse = res.data;
// console.log("Gitlab Credentials (In refresh) : ", data)
await this.prisma.connections.update({
where: {
id_connection: connectionId,
Expand All @@ -164,9 +168,9 @@ export class GitlabConnectionService implements ITicketingConnectionService {
),
},
});
this.logger.log('OAuth credentials updated : jira ');
this.logger.log('OAuth credentials updated : gitlab ');
} catch (error) {
handleServiceError(error, this.logger, 'jira', Action.oauthRefresh);
handleServiceError(error, this.logger, 'gitlab', Action.oauthRefresh);
}
}
}
38 changes: 31 additions & 7 deletions packages/api/src/@core/utils/types/original/original.ticketing.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@


import {
FrontAccountInput,
FrontAccountOutput,
Expand Down Expand Up @@ -82,7 +84,7 @@ import {
JiraTagInput,
JiraTagOutput,
} from '@ticketing/tag/services/jira/types';
import { JiraCollectionOutput } from '@ticketing/collection/services/jira/types';
import { JiraCollectionOutput, JiraCollectionInput } from '@ticketing/collection/services/jira/types';
import {
ZendeskTicketInput,
ZendeskTicketOutput,
Expand Down Expand Up @@ -111,6 +113,18 @@ import {
ZendeskUserInput,
ZendeskUserOutput,
} from '@ticketing/user/services/zendesk/types';
import {
GitlabCollectionInput,
GitlabCollectionOutput,
} from '@ticketing/collection/services/gitlab/types';
import {
GitlabTicketInput,
GitlabTicketOutput,
} from '@ticketing/ticket/services/gitlab/types';
import {
GitlabCommentInput,
GitlabCommentOutput
} from '@ticketing/comment/services/gitlab/types'

/* INPUT */

Expand All @@ -121,15 +135,17 @@ export type OriginalTicketInput =
| GithubTicketInput
| HubspotTicketInput
| GorgiasTicketInput
| JiraTicketInput;
| JiraTicketInput
| GitlabTicketInput;
//| JiraServiceMgmtTicketInput;

/* comment */
export type OriginalCommentInput =
| ZendeskCommentInput
| FrontCommentInput
| GorgiasCommentInput
| JiraCommentInput;
| JiraCommentInput
| GitlabCommentInput;
//| JiraCommentServiceMgmtInput;
/* user */
export type OriginalUserInput =
Expand All @@ -152,6 +168,7 @@ export type OriginalTagInput =
| FrontTagInput
| GorgiasTagInput
| JiraTagInput;

/* team */
export type OriginalTeamInput =
| ZendeskTeamInput
Expand All @@ -161,7 +178,7 @@ export type OriginalTeamInput =

/* attachment */
export type OriginalAttachmentInput = null;
export type OriginalCollectionInput = null;
export type OriginalCollectionInput = JiraCollectionInput | GitlabCollectionInput;

export type TicketingObjectInput =
| OriginalTicketInput
Expand All @@ -183,14 +200,16 @@ export type OriginalTicketOutput =
| GithubTicketOutput
| HubspotTicketOutput
| GorgiasTicketOutput
| JiraTicketOutput;
| JiraTicketOutput
| GitlabTicketOutput;

/* comment */
export type OriginalCommentOutput =
| ZendeskCommentOutput
| FrontCommentOutput
| GorgiasCommentOutput
| JiraCommentOutput;
| JiraCommentOutput
| GitlabCommentOutput;
/* user */
export type OriginalUserOutput =
| ZendeskUserOutput
Expand Down Expand Up @@ -228,7 +247,8 @@ export type OriginalAttachmentOutput =

/* collection */

export type OriginalCollectionOutput = JiraCollectionOutput;
export type OriginalCollectionOutput = JiraCollectionOutput | GitlabCollectionOutput;


export type TicketingObjectOutput =
| OriginalTicketOutput
Expand All @@ -240,3 +260,7 @@ export type TicketingObjectOutput =
| OriginalContactOutput
| OriginalAccountOutput
| OriginalCollectionOutput;




58 changes: 58 additions & 0 deletions packages/api/src/ticketing/@lib/@utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,4 +103,62 @@ export class Utils {
throw new Error(error);
}
}

async getCollectionUuidFromRemoteId(remote_id: string, remote_platform: string) {
try {
const res = await this.prisma.tcg_collections.findFirst({
where: {
remote_id: remote_id,
remote_platform: remote_platform,
},
});
if (!res) return;
return res.id_tcg_collection;
} catch (error) {
throw new Error(error);
}
}
Comment on lines +107 to +120
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling the case where the result is null more gracefully.

-      if (!res) return;
+      if (!res) throw new Error(`Collection not found for remote ID ${remote_id} and platform ${remote_platform}`);

This change ensures that the function throws an informative error when no collection is found, rather than silently failing.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async getCollectionUuidFromRemoteId(remote_id: string, remote_platform: string) {
try {
const res = await this.prisma.tcg_collections.findFirst({
where: {
remote_id: remote_id,
remote_platform: remote_platform,
},
});
if (!res) return;
return res.id_tcg_collection;
} catch (error) {
throw new Error(error);
}
}
async getCollectionUuidFromRemoteId(remote_id: string, remote_platform: string) {
try {
const res = await this.prisma.tcg_collections.findFirst({
where: {
remote_id: remote_id,
remote_platform: remote_platform,
},
});
if (!res) throw new Error(`Collection not found for remote ID ${remote_id} and platform ${remote_platform}`);
return res.id_tcg_collection;
} catch (error) {
throw new Error(error);
}
}


async getCollectionRemoteIdFromUuid(uuid: string) {
try {
const res = await this.prisma.tcg_collections.findFirst({
where: {
id_tcg_collection: uuid,
},
});
if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`);
return res.remote_id;
} catch (error) {
throw new Error(error);
}
}
Comment on lines +122 to +134
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure consistent error messages for better debugging and user feedback.

-      if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`);
+      if (!res) throw new Error(`Collection not found for uuid ${uuid}`);

This change corrects the error message to reflect the actual entity being queried, which is a collection, not a contact.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async getCollectionRemoteIdFromUuid(uuid: string) {
try {
const res = await this.prisma.tcg_collections.findFirst({
where: {
id_tcg_collection: uuid,
},
});
if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`);
return res.remote_id;
} catch (error) {
throw new Error(error);
}
}
async getCollectionRemoteIdFromUuid(uuid: string) {
try {
const res = await this.prisma.tcg_collections.findFirst({
where: {
id_tcg_collection: uuid,
},
});
if (!res) throw new Error(`Collection not found for uuid ${uuid}`);
return res.remote_id;
} catch (error) {
throw new Error(error);
}
}


async getTicketUuidFromRemoteId(remote_id: string, remote_platform: string) {
try {
const res = await this.prisma.tcg_tickets.findFirst({
where: {
remote_id: remote_id,
remote_platform: remote_platform,
},
});
if (!res) return;
return res.id_tcg_ticket;
} catch (error) {
throw new Error(error);
}
}
Comment on lines +136 to +149
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider handling the case where the result is null more gracefully.

-      if (!res) return;
+      if (!res) throw new Error(`Ticket not found for remote ID ${remote_id} and platform ${remote_platform}`);

This change ensures that the function throws an informative error when no ticket is found, rather than silently failing.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async getTicketUuidFromRemoteId(remote_id: string, remote_platform: string) {
try {
const res = await this.prisma.tcg_tickets.findFirst({
where: {
remote_id: remote_id,
remote_platform: remote_platform,
},
});
if (!res) return;
return res.id_tcg_ticket;
} catch (error) {
throw new Error(error);
}
}
async getTicketUuidFromRemoteId(remote_id: string, remote_platform: string) {
try {
const res = await this.prisma.tcg_tickets.findFirst({
where: {
remote_id: remote_id,
remote_platform: remote_platform,
},
});
if (!res) throw new Error(`Ticket not found for remote ID ${remote_id} and platform ${remote_platform}`);
return res.id_tcg_ticket;
} catch (error) {
throw new Error(error);
}
}


async getTicketRemoteIdFromUuid(uuid: string) {
try {
const res = await this.prisma.tcg_tickets.findFirst({
where: {
id_tcg_ticket: uuid,
},
});
if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`);
return res.remote_id;
} catch (error) {
throw new Error(error);
}
}
Comment on lines +151 to +163
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure consistent error messages for better debugging and user feedback.

-      if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`);
+      if (!res) throw new Error(`Ticket not found for uuid ${uuid}`);

This change corrects the error message to reflect the actual entity being queried, which is a ticket, not a contact.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
async getTicketRemoteIdFromUuid(uuid: string) {
try {
const res = await this.prisma.tcg_tickets.findFirst({
where: {
id_tcg_ticket: uuid,
},
});
if (!res) throw new Error(`tcg_contact not found for uuid ${uuid}`);
return res.remote_id;
} catch (error) {
throw new Error(error);
}
}
async getTicketRemoteIdFromUuid(uuid: string) {
try {
const res = await this.prisma.tcg_tickets.findFirst({
where: {
id_tcg_ticket: uuid,
},
});
if (!res) throw new Error(`Ticket not found for uuid ${uuid}`);
return res.remote_id;
} catch (error) {
throw new Error(error);
}
}

}
2 changes: 2 additions & 0 deletions packages/api/src/ticketing/collection/collection.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { GitlabService } from './services/gitlab';
import { Module } from '@nestjs/common';
import { CollectionController } from './collection.controller';
import { SyncService } from './sync/sync.service';
Expand Down Expand Up @@ -32,6 +33,7 @@ import { JiraService } from './services/jira';
ServiceRegistry,
/* PROVIDERS SERVICES */
JiraService,
GitlabService,
],
exports: [
SyncService,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class CollectionService {
remote_data?: boolean,
): Promise<UnifiedCollectionOutput[]> {
try {
console.log("In collection service : ", integrationId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logging statement in getCollections method provides useful debugging information. Ensure that such logs are appropriately gated (e.g., only in debug mode) to avoid verbose logging in production.

- console.log("In collection service : ", integrationId)
+ if (process.env.DEBUG_MODE === 'true') {
+   console.log("In collection service : ", integrationId);
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
console.log("In collection service : ", integrationId)
if (process.env.DEBUG_MODE === 'true') {
console.log("In collection service : ", integrationId);
}

const collections = await this.prisma.tcg_collections.findMany({
where: {
remote_platform: integrationId.toLowerCase(),
Expand Down
90 changes: 90 additions & 0 deletions packages/api/src/ticketing/collection/services/gitlab/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Injectable } from '@nestjs/common';
import { LoggerService } from '@@core/logger/logger.service';
import { PrismaService } from '@@core/prisma/prisma.service';
import { EncryptionService } from '@@core/encryption/encryption.service';
import { TicketingObject } from '@ticketing/@lib/@types';
import { ApiResponse } from '@@core/utils/types';
import axios from 'axios';
import { ActionType, handleServiceError } from '@@core/utils/errors';
import { ServiceRegistry } from '../registry.service';
import { ICollectionService } from '@ticketing/collection/types';
import { GitlabCollectionInput, GitlabCollectionOutput } from './types';
import { DesunifyReturnType } from '@@core/utils/types/desunify.input';

@Injectable()
export class GitlabService implements ICollectionService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
TicketingObject.collection.toUpperCase() + ':' + GitlabService.name,
);
this.registry.registerService('gitlab', this);
}

async syncCollections(
linkedUserId: string,
): Promise<ApiResponse<GitlabCollectionOutput[]>> {
try {
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'gitlab',
vertical: 'ticketing',
},
});

// It fetches all project from gitlab
// const resp = await axios.get(`${connection.account_url}/projects`, {
// headers: {
// 'Content-Type': 'application/json',
// Authorization: `Bearer ${this.cryptoService.decrypt(
// connection.access_token,
// )}`,
// },
// });

const currentUser = await axios.get(`${connection.account_url}/user`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
})

const resp = await axios.get(`${connection.account_url}/users/${currentUser.data.id}/projects`, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
})

this.logger.log(`Synced gitlab collections !`);

// console.log("In index of gitlab", JSON.stringify(resp.data))


return {
data: resp.data,
message: 'Gitlab collections retrieved',
statusCode: 200,
};
} catch (error) {
handleServiceError(
error,
this.logger,
'Gitlab',
TicketingObject.collection,
ActionType.GET,
);
}
}


}
Loading
Loading