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

Feat: Linear Integration (Ticketing) #612

Merged
merged 17 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from 15 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
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ GITLAB_TICKETING_CLOUD_CLIENT_SECRET=
# Github
GITHUB_TICKETING_CLOUD_CLIENT_ID=
GITHUB_TICKETING_CLOUD_CLIENT_SECRET=
# Linear
LINEAR_TICKETING_CLOUD_CLIENT_ID=
LINEAR_TICKETING_CLOUD_CLIENT_SECRET=

# ================================================
# File Storage
# ================================================
Expand Down
1 change: 1 addition & 0 deletions packages/api/scripts/init.sql
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ CREATE TABLE connector_sets
ecom_shopify boolean NULL,
ecom_amazon boolean NULL,
ecom_squarespace boolean NULL,
tcg_linear boolean NULL,
CONSTRAINT PK_project_connector PRIMARY KEY ( id_connector_set )
);

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, 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 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, tcg_linear) VALUES
('1709da40-17f7-4d3a-93a0-96dc5da6ddd7', TRUE, 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, TRUE),
('aed0f856-f802-4a79-8640-66d441581a99', TRUE, 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,15 @@
import { LinearTicketInput, LinearTicketOutput } from '@ticketing/ticket/services/linear/types';

import { LinearCommentInput, LinearCommentOutput } from '@ticketing/comment/services/linear/types';

import { LinearCollectionInput, LinearCollectionOutput } from '@ticketing/collection/services/linear/types';

import { LinearTagInput, LinearTagOutput } from '@ticketing/tag/services/linear/types';

import { LinearTeamInput, LinearTeamOutput } from '@ticketing/team/services/linear/types';

import { LinearUserInput, LinearUserOutput } from '@ticketing/user/services/linear/types';

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

import { GithubCommentInput, GithubCommentOutput } from '@ticketing/comment/services/github/types';
Expand All @@ -9,6 +21,7 @@ import { GithubTeamInput, GithubTeamOutput } from '@ticketing/team/services/gith
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 {
Expand Down Expand Up @@ -144,7 +157,7 @@ export type OriginalTicketInput =
| FrontTicketInput
| GorgiasTicketInput
| JiraTicketInput
| GitlabTicketInput | GithubTicketInput;
| GitlabTicketInput | GithubTicketInput | LinearTicketInput;
//| JiraServiceMgmtTicketInput;

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

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

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

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

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

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

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

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

export type TicketingObjectOutput =
| OriginalTicketOutput
Expand Down
16 changes: 15 additions & 1 deletion packages/api/src/ticketing/@lib/@utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as fs from 'fs';

@Injectable()
export class Utils {
constructor(private readonly prisma: PrismaService) {}
constructor(private readonly prisma: PrismaService) { }

async fetchFileStreamFromURL(file_url: string) {
return fs.createReadStream(file_url);
Expand All @@ -27,6 +27,20 @@ export class Utils {
}
}

async getTeamRemoteIdFromUuid(uuid: string) {
try {
const res = await this.prisma.tcg_teams.findFirst({
where: {
id_tcg_team: uuid,
},
});
if (!res) return undefined;
return res.remote_id;
} catch (error) {
throw error;
}
}
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved

async getRemoteIdFromTagName(name: string, connection_id: string) {
try {
const res = await this.prisma.tcg_tags.findFirst({
Expand Down
6 changes: 5 additions & 1 deletion packages/api/src/ticketing/collection/collection.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LinearCollectionMapper } from './services/linear/mappers';
import { LinearService } from './services/linear';
import { GithubCollectionMapper } from './services/github/mappers';
import { GithubService } from './services/github';
import { EncryptionService } from '@@core/@core-services/encryption/encryption.service';
Expand Down Expand Up @@ -39,7 +41,9 @@ import { IngestDataService } from '@@core/@core-services/unification/ingest-data
GitlabCollectionMapper,
GithubService,
GithubCollectionMapper,
LinearService,
LinearCollectionMapper,
],
exports: [SyncService, ServiceRegistry, WebhookService],
})
export class CollectionModule {}
export class CollectionModule { }
65 changes: 65 additions & 0 deletions packages/api/src/ticketing/collection/services/linear/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
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 { LinearCollectionOutput, LinearCollectionInput } from './types';
import { SyncParam } from '@@core/utils/types/interface';

@Injectable()
export class LinearService implements ICollectionService {
constructor(
private prisma: PrismaService,
private logger: LoggerService,
private cryptoService: EncryptionService,
private registry: ServiceRegistry,
) {
this.logger.setContext(
TicketingObject.collection.toUpperCase() + ':' + LinearService.name,
);
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
this.registry.registerService('linear', this);
}

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

const connection = await this.prisma.connections.findFirst({
where: {
id_linked_user: linkedUserId,
provider_slug: 'linear',
vertical: 'ticketing',
},
});

const projectQuery = {
"query": "query { projects { nodes { id, name, description } }}"
};

let resp = await axios.post(
`${connection.account_url}`,
projectQuery, {
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.cryptoService.decrypt(
connection.access_token,
)}`,
},
});
this.logger.log(`Synced linear collections !`);

return {
data: resp.data.data.projects.nodes,
message: 'Linear collections retrieved',
statusCode: 200,
};
} catch (error) {
throw error;
}
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
}
}
69 changes: 69 additions & 0 deletions packages/api/src/ticketing/collection/services/linear/mappers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { ICollectionMapper } from '@ticketing/collection/types';
import { LinearCollectionInput, LinearCollectionOutput } 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 LinearCollectionMapper implements ICollectionMapper {
constructor(private mappersRegistry: MappersRegistry, private utils: Utils) {
this.mappersRegistry.registerService(
'ticketing',
'collection',
'linear',
this,
);
}
desunify(
source: UnifiedTicketingCollectionInput,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): LinearCollectionInput {
return;
}
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved

unify(
source: LinearCollectionOutput | LinearCollectionOutput[],
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: LinearCollectionOutput,
connectionId: string,
customFieldMappings?: {
slug: string;
remote_id: string;
}[],
): UnifiedTicketingCollectionOutput {
const unifiedCollection: UnifiedTicketingCollectionOutput = {
remote_id: collection.id,
remote_data: collection,
name: collection.name,
description: collection.description,
collection_type: 'PROJECT',
};

return unifiedCollection;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
interface LinearCollection {
id: string
name: string
description: string
}

export type LinearCollectionInput = Partial<LinearCollection>;
export type LinearCollectionOutput = LinearCollectionInput;
ganimtron-10 marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 5 additions & 1 deletion packages/api/src/ticketing/comment/comment.module.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { LinearCommentMapper } from './services/linear/mappers';
import { LinearService } from './services/linear';
import { GithubCommentMapper } from './services/github/mappers';
import { GithubService } from './services/github';
import { LoggerService } from '@@core/@core-services/logger/logger.service';
Expand Down Expand Up @@ -45,7 +47,9 @@ import { SyncService } from './sync/sync.service';
GitlabCommentMapper,
GithubService,
GithubCommentMapper,
LinearService,
LinearCommentMapper,
],
exports: [SyncService, ServiceRegistry, WebhookService],
})
export class CommentModule {}
export class CommentModule { }
Loading
Loading