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

Add Github Connector #568

Merged
merged 9 commits into from
Aug 7, 2024
4 changes: 3 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,9 @@ FRONT_TICKETING_CLOUD_CLIENT_SECRET=
# Gitlab
GITLAB_TICKETING_CLOUD_CLIENT_ID=
GITLAB_TICKETING_CLOUD_CLIENT_SECRET=

# Github
GITHUB_TICKETING_CLOUD_CLIENT_ID=
GITHUB_TICKETING_CLOUD_CLIENT_SECRET=
Comment on lines +98 to +100
Copy link
Contributor

Choose a reason for hiding this comment

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

Tip

Codebase Verification

Ensure the new GitHub ticketing environment variables are documented.

The GITHUB_TICKETING_CLOUD_CLIENT_ID and GITHUB_TICKETING_CLOUD_CLIENT_SECRET variables are listed in docs/open-source/self_hosting/envVariables.mdx, but they lack descriptions and usage information. Please update the documentation to include detailed information on these variables.

  • docs/open-source/self_hosting/envVariables.mdx
Analysis chain

Ensure the new GitHub ticketing environment variables are documented.

It is important to document these new environment variables in the project's documentation to guide developers on their usage.

Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Ensure the new GitHub ticketing environment variables are documented.

# Test: Search for documentation of the new variables. Expect: Documentation exists.
rg --type markdown 'GITHUB_TICKETING_CLOUD_CLIENT_ID|GITHUB_TICKETING_CLOUD_CLIENT_SECRET'

Length of output: 92


Script:

#!/bin/bash
# Description: Ensure the new GitHub ticketing environment variables are documented.

# Test: Search for documentation of the new variables in text files.
rg --type text 'GITHUB_TICKETING_CLOUD_CLIENT_ID|GITHUB_TICKETING_CLOUD_CLIENT_SECRET'

# Test: Search for documentation of the new variables in YAML files.
rg --type yaml 'GITHUB_TICKETING_CLOUD_CLIENT_ID|GITHUB_TICKETING_CLOUD_CLIENT_SECRET'

# Test: Search for documentation of the new variables in common documentation directories.
rg 'GITHUB_TICKETING_CLOUD_CLIENT_ID|GITHUB_TICKETING_CLOUD_CLIENT_SECRET' docs/ documentation/

Length of output: 1175


Script:

#!/bin/bash
# Extract lines containing the new GitHub ticketing environment variables from the envVariables.mdx file.
rg -A 5 'GITHUB_TICKETING_CLOUD_CLIENT_ID|GITHUB_TICKETING_CLOUD_CLIENT_SECRET' docs/open-source/self_hosting/envVariables.mdx

Length of output: 494

# ================================================
# File Storage
# ================================================
Expand Down
19 changes: 10 additions & 9 deletions packages/api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ model crm_contacts {
model crm_deals {
id_crm_deal String @id(map: "pk_crm_deal") @db.Uuid
name String
description String?
description String
amount BigInt
created_at DateTime @db.Timestamp(6)
modified_at DateTime @db.Timestamp(6)
Expand Down Expand Up @@ -503,7 +503,6 @@ model tcg_collections {
remote_platform String?
collection_type String?
parent_collection String? @db.Uuid
id_tcg_ticket String? @db.Uuid
created_at DateTime @db.Timestamp(6)
modified_at DateTime @db.Timestamp(6)
id_linked_user String @db.Uuid
Expand All @@ -518,14 +517,14 @@ model tcg_comments {
is_private Boolean?
remote_id String?
remote_platform String?
created_at DateTime? @db.Timestamp(6)
modified_at DateTime? @db.Timestamp(6)
creator_type String?
id_tcg_attachment String[]
id_tcg_ticket String? @db.Uuid
id_tcg_contact String? @db.Uuid
id_tcg_user String? @db.Uuid
id_linked_user String? @db.Uuid
created_at DateTime? @db.Timestamp(6)
modified_at DateTime? @db.Timestamp(6)
id_connection String @db.Uuid
tcg_attachments tcg_attachments[]
tcg_tickets tcg_tickets? @relation(fields: [id_tcg_ticket], references: [id_tcg_ticket], onDelete: NoAction, onUpdate: NoAction, map: "fk_40_1")
Expand Down Expand Up @@ -596,14 +595,14 @@ model tcg_tickets {
collections String[]
completed_at DateTime? @db.Timestamp(6)
priority String?
created_at DateTime @db.Timestamp(6)
modified_at DateTime @db.Timestamp(6)
assigned_to String[]
remote_id String?
remote_platform String?
creator_type String?
id_tcg_user String? @db.Uuid
id_linked_user String? @db.Uuid
created_at DateTime @db.Timestamp(6)
modified_at DateTime @db.Timestamp(6)
id_linked_user String @db.Uuid
id_connection String @db.Uuid
tcg_attachments tcg_attachments[]
tcg_comments tcg_comments[]
Expand All @@ -620,10 +619,10 @@ model tcg_users {
remote_id String?
remote_platform String?
teams String[]
id_linked_user String? @db.Uuid
id_connection String @db.Uuid
created_at DateTime? @db.Timestamp(6)
modified_at DateTime? @db.Timestamp(6)
id_linked_user String? @db.Uuid
id_connection String @db.Uuid
tcg_comments tcg_comments[]
}

Expand Down Expand Up @@ -678,6 +677,7 @@ model connector_sets {
crm_zendesk Boolean?
crm_close Boolean?
fs_box Boolean?
tcg_github Boolean?
projects projects[]
}

Expand Down Expand Up @@ -1640,6 +1640,7 @@ model acc_vendor_credits {
accounting_period String? @db.Uuid
}


model ecom_customers {
id_ecom_customer String @id(map: "pk_ecom_customers") @db.Uuid
remote_id String?
Expand Down
3 changes: 2 additions & 1 deletion packages/api/scripts/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@ CREATE TABLE connector_sets
crm_zendesk boolean NULL,
crm_close boolean NULL,
fs_box boolean NULL,
CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set )
tcg_github boolean NULL,
CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set )
);

-- ************************************** connection_strategies
Expand Down
8 changes: 4 additions & 4 deletions packages/api/scripts/seed.sql
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
INSERT INTO users (id_user, identification_strategy, email, password_hash, first_name, last_name) VALUES
('0ce39030-2901-4c56-8db0-5e326182ec6b', 'b2c','[email protected]', '$2b$10$Y7Q8TWGyGuc5ecdIASbBsuXMo3q/Rs3/cnY.mLZP4tUgfGUOCUBlG', 'local', 'Panora');

INSERT INTO connector_sets (id_connector_set, crm_hubspot, crm_zoho, crm_pipedrive, crm_attio, crm_zendesk, crm_close, tcg_zendesk, tcg_gorgias, tcg_front, tcg_jira, tcg_gitlab, fs_box) VALUES
('1709da40-17f7-4d3a-93a0-96dc5da6ddd7', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('852dfff8-ab63-4530-ae49-e4b2924407f8', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('aed0f856-f802-4a79-8640-66d441581a99', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);
INSERT INTO connector_sets (id_connector_set, crm_hubspot, crm_zoho, crm_pipedrive, crm_attio, crm_zendesk, crm_close, tcg_zendesk, tcg_gorgias, tcg_front, tcg_jira, tcg_gitlab, fs_box, tcg_github) VALUES
('1709da40-17f7-4d3a-93a0-96dc5da6ddd7', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('852dfff8-ab63-4530-ae49-e4b2924407f8', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE),
('aed0f856-f802-4a79-8640-66d441581a99', TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE);

INSERT INTO projects (id_project, name, sync_mode, id_user, id_connector_set) VALUES
('1e468c15-aa57-4448-aa2b-7fed640d1e3d', 'Project 1', 'pull', '0ce39030-2901-4c56-8db0-5e326182ec6b', '1709da40-17f7-4d3a-93a0-96dc5da6ddd7'),
Expand Down
37 changes: 25 additions & 12 deletions packages/api/src/@core/utils/types/original/original.ticketing.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
import { GithubCollectionInput, GithubCollectionOutput } from '@ticketing/collection/services/github/types';
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 import style.

The import statement for GitHub types should be consistent with other import statements.

- import { GithubCollectionInput, GithubCollectionOutput } from '@ticketing/collection/services/github/types';
+ import {
+   GithubCollectionInput,
+   GithubCollectionOutput,
+ } from '@ticketing/collection/services/github/types';
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. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { GithubCollectionInput, GithubCollectionOutput } from '@ticketing/collection/services/github/types';
import {
GithubCollectionInput,
GithubCollectionOutput,
} from '@ticketing/collection/services/github/types';


import { GithubCommentInput, GithubCommentOutput } from '@ticketing/comment/services/github/types';

import { GithubTagInput, GithubTagOutput } from '@ticketing/tag/services/github/types';

import { GithubTeamInput, GithubTeamOutput } from '@ticketing/team/services/github/types';

import { GithubTicketInput, GithubTicketOutput } from '@ticketing/ticket/services/github/types';

import { GithubUserInput, GithubUserOutput } from '@ticketing/user/services/github/types';
import { GitlabUserInput, GitlabUserOutput } from '@ticketing/user/services/gitlab/types';

import {
FrontAccountInput,
FrontAccountOutput,
Expand Down Expand Up @@ -131,7 +144,7 @@ export type OriginalTicketInput =
| FrontTicketInput
| GorgiasTicketInput
| JiraTicketInput
| GitlabTicketInput;
| GitlabTicketInput | GithubTicketInput;
//| JiraServiceMgmtTicketInput;

/* comment */
Expand All @@ -140,14 +153,14 @@ export type OriginalCommentInput =
| FrontCommentInput
| GorgiasCommentInput
| JiraCommentInput
| GitlabCommentInput;
| GitlabCommentInput | GithubCommentInput;
//| JiraCommentServiceMgmtInput;
/* user */
export type OriginalUserInput =
| ZendeskUserInput
| FrontUserInput
| GorgiasUserInput
| JiraUserInput;
| JiraUserInput | GithubUserInput | GitlabUserInput;
//| JiraServiceMgmtUserInput;
/* account */
export type OriginalAccountInput = ZendeskAccountInput | FrontAccountInput;
Expand All @@ -163,20 +176,20 @@ export type OriginalTagInput =
| FrontTagInput
| GorgiasTagInput
| JiraTagInput
| GitlabTagInput;
| GitlabTagInput | GithubTagInput;

/* team */
export type OriginalTeamInput =
| ZendeskTeamInput
| FrontTeamInput
| GorgiasTeamInput
| JiraTeamInput;
| JiraTeamInput | GithubTeamInput;

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

export type TicketingObjectInput =
| OriginalTicketInput
Expand All @@ -197,21 +210,21 @@ export type OriginalTicketOutput =
| FrontTicketOutput
| GorgiasTicketOutput
| JiraTicketOutput
| GitlabTicketOutput;
| GitlabTicketOutput | GithubTicketOutput;

/* comment */
export type OriginalCommentOutput =
| ZendeskCommentOutput
| FrontCommentOutput
| GorgiasCommentOutput
| JiraCommentOutput
| GitlabCommentOutput;
| GitlabCommentOutput | GithubCommentOutput;
/* user */
export type OriginalUserOutput =
| ZendeskUserOutput
| FrontUserOutput
| GorgiasUserOutput
| JiraUserOutput;
| JiraUserOutput | GithubUserOutput | GitlabUserOutput;
/* account */
export type OriginalAccountOutput = ZendeskAccountOutput | FrontAccountOutput;
/* contact */
Expand All @@ -226,14 +239,14 @@ export type OriginalTagOutput =
| FrontTagOutput
| GorgiasTagOutput
| JiraTagOutput
| GitlabTagOutput;
| GitlabTagOutput | GithubTagOutput;

/* team */
export type OriginalTeamOutput =
| ZendeskTeamOutput
| FrontTeamOutput
| GorgiasTeamOutput
| JiraTeamOutput;
| JiraTeamOutput | GithubTeamOutput;

/* attachment */
export type OriginalAttachmentOutput =
Expand All @@ -246,7 +259,7 @@ export type OriginalAttachmentOutput =

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

export type TicketingObjectOutput =
| OriginalTicketOutput
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/ticketing/collection/collection.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { GithubCollectionMapper } from './services/github/mappers';
import { GithubService } from './services/github';
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service';
import { LoggerService } from '@@core/@core-services/logger/logger.service';
import { BullQueueModule } from '@@core/@core-services/queues/queue.module';
Expand Down Expand Up @@ -36,6 +38,8 @@ import { IngestDataService } from '@@core/@core-services/unification/ingest-data
/* PROVIDERS MAPPERS */
JiraCollectionMapper,
GitlabCollectionMapper,
GithubService,
GithubCollectionMapper,
],
exports: [SyncService, ServiceRegistry, WebhookService],
})
Expand Down
61 changes: 61 additions & 0 deletions packages/api/src/ticketing/collection/services/github/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
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 { ApiResponse } from '@@core/utils/types';
import axios from 'axios';
import { ActionType, handle3rdPartyServiceError } from '@@core/utils/errors';
import { ServiceRegistry } from '../registry.service';
import { ICollectionService } from '@ticketing/collection/types';
import { GithubCollectionInput, GithubCollectionOutput } from './types';
import { SyncParam } from '@@core/utils/types/interface';

@Injectable()
export class GithubService implements ICollectionService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
TicketingObject.collection.toUpperCase() + ':' + GithubService.name,
);
this.registry.registerService('github', this);
}
Comment on lines +14 to +26
Copy link
Contributor

Choose a reason for hiding this comment

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

Use template literals for setting logger context.

Template literals are preferred over string concatenation for better readability.

-        this.logger.setContext(
-            TicketingObject.collection.toUpperCase() + ':' + GithubService.name,
-        );
+        this.logger.setContext(`${TicketingObject.collection.toUpperCase()}:${GithubService.name}`);
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. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Injectable()
export class GithubService implements ICollectionService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
TicketingObject.collection.toUpperCase() + ':' + GithubService.name,
);
this.registry.registerService('github', this);
}
@Injectable()
export class GithubService implements ICollectionService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(`${TicketingObject.collection.toUpperCase()}:${GithubService.name}`);
this.registry.registerService('github', this);
}
Tools
Biome

[error] 23-23: Template literals are preferred over string concatenation.

Unsafe fix: Use a template literal.

(lint/style/useTemplate)


async sync(data: SyncParam): Promise<ApiResponse<GithubCollectionOutput[]>> {
try {
const { linkedUserId } = data;

const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'github',
vertical: 'ticketing',
},
});
const resp = await axios.get(
`${connection.account_url}/user/repos`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
this.logger.log(`Synced github collections !`);

return {
data: resp.data,
message: 'Github collections retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
Comment on lines +28 to +59
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove redundant catch clause.

The catch clause that only rethrows the original error is redundant and can be removed.

-        } catch (error) {
-            throw error;
-        }
+        }
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. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async sync(data: SyncParam): Promise<ApiResponse<GithubCollectionOutput[]>> {
try {
const { linkedUserId } = data;
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'github',
vertical: 'ticketing',
},
});
const resp = await axios.get(
`${connection.account_url}/user/repos`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
this.logger.log(`Synced github collections !`);
return {
data: resp.data,
message: 'Github collections retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
async sync(data: SyncParam): Promise<ApiResponse<GithubCollectionOutput[]>> {
try {
const { linkedUserId } = data;
const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'github',
vertical: 'ticketing',
},
});
const resp = await axios.get(
`${connection.account_url}/user/repos`,
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
},
);
this.logger.log(`Synced github collections !`);
return {
data: resp.data,
message: 'Github collections retrieved',
statusCode: 200,
};
}
Tools
Biome

[error] 58-58: The catch clause that only rethrows the original error is redundant.

These unnecessary catch clauses can be confusing. It is recommended to remove them.

(lint/complexity/noUselessCatch)

}
}
71 changes: 71 additions & 0 deletions packages/api/src/ticketing/collection/services/github/mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { ICollectionMapper } from '@ticketing/collection/types';
import { GithubCollectionInput, GithubCollectionOutput } from './types';
import {
UnifiedTicketingCollectionInput,
UnifiedTicketingCollectionOutput,
} from '@ticketing/collection/types/model.unified';
import { MappersRegistry } from '@@core/@core-services/registries/mappers.registry';
import { Injectable } from '@nestjs/common';
import { Utils } from '@ticketing/@lib/@utils';



@Injectable()
export class GithubCollectionMapper implements ICollectionMapper {
constructor(private mappersRegistry: MappersRegistry, private utils: Utils) {
this.mappersRegistry.registerService(
'ticketing',
'collection',
'github',
this,
);
}
desunify(
source: UnifiedTicketingCollectionInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): GithubCollectionInput {
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

Implement the desunify method.

The desunify method is currently not implemented.

-    ): GithubCollectionInput {
-        return;
+    ): GithubCollectionInput {
+        // Implement the desunify logic here
+        return {} as GithubCollectionInput;
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. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return;
): GithubCollectionInput {
// Implement the desunify logic here
return {} as GithubCollectionInput;

}

unify(
source: GithubCollectionOutput | GithubCollectionOutput[],
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): UnifiedTicketingCollectionOutput | UnifiedTicketingCollectionOutput[] {
// If the source is not an array, convert it to an array for mapping
const sourcesArray = Array.isArray(source) ? source : [source];

return sourcesArray.map((collection) =>
this.mapSingleCollectionToUnified(
collection,
connectionId,
customFieldMappings,
),
);
}

private mapSingleCollectionToUnified(
collection: GithubCollectionOutput,
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): UnifiedTicketingCollectionOutput {
const unifiedCollection: UnifiedTicketingCollectionOutput = {
remote_id: String(collection.id),
remote_data: collection,
name: collection.name,
description: collection.description,
collection_type: 'PROJECT',
};

return unifiedCollection;
}
}
Loading
Loading