Adding new Integrations ✨

Make sure you are inside packages/api/src where the server lives !

You want to add a new 3rd Party not yet supported ? 🧑‍🎤

Ie: Slack, Hubspot, Jira, Shopify …

First choose wisely which category the 3rd party belongs to among these:

  • crm
  • ticketing
  • accounting
  • ats
  • filestorage
  • hris
  • marketingautomation

You can find all categories inside packages/shared/src/categories.ts.

For the sake of the guide, now on we’ll consider adding a 3rd party belonging to the crm category.

Actually an integration is built in 2 parts :

  • the authentication part (oauth, api key, basic etc) which is built by the Panora team
  • the service integration where the mapping with our unified model is created which is what you’ll build in the next steps

Step 2: Build your provider service

You want to map a common object to your new 3rd Party ? 👩‍🎤

Ie: Contact, Ticket, Deal, Company …

For the sake of this guide, let’s map the common object contact under crm category to my3rdParty (in reality it would be a real 3rd party name).

An integration is considered valid when all common objects have been mapped. Then, after the PR is accepted we’ll be able to set active field to true inside CONNECTORS_METADATA

1

Add a new service to map your common object to your 3rd party

Create a new service folder with the name of your 3rd party. Let’s call it my3rdParty.

cd crm/contact/services/my3rdParty

You’ll now create 3 files :

  • index.ts where your service is created and direct interaction with your 3rd party API is handled
  • types.ts where the 3rd party specific API types are defined
  • mappers.ts where the mapping between our unified common model and the 3rd party one is handled
After copying the following code you’ll end up with linting/deps errors. It is fixed by our script at Step 2.
1

Create the index.ts file

It must implement the IContactService interface.

export interface IContactService {
  addContact(
    contactData: DesunifyReturnType,
    linkedUserId: string
  ): Promise<ApiResponse<OriginalContactOutput>>;

  syncContacts(
    linkedUserId: string
  ): Promise<ApiResponse<OriginalContactOutput[]>>;
}
@Injectable()
export class My3rdPartyService implements IContactService {
  constructor(
    private prisma: PrismaService,
    private logger: LoggerService,
    private cryptoService: EncryptionService,
    private registry: ServiceRegistry,
  ) {
    this.logger.setContext(
      CrmObject.contact.toUpperCase() + ':' + My3rdPartyService.name,
    );
    this.registry.registerService('my3rdParty', this);
  }
  async addContact(
    contactData: 3rdPartyContactInput,
    linkedUserId: string,
  ): Promise<ApiResponse<3rdPartyContactOutput>> {}

  async syncContacts(
    linkedUserId: string,
  ): Promise<ApiResponse<3rdPartyContactOutput[]>> {}
}

Check other implementations under /crm/contacts/services to fill the core functions.

2

Create the types.ts file

The keen readers may have noticed 3rdPartyContactInput and 3rdPartyContactOutput. This is where types.ts comes in:
Go to the 3rd party API and insert the correct types asked by the API.

export interface 3rdPartyContact {
  //INSERT THE CORRECT TYPE HERE
}
export type 3rdPartyContactInput = Partial<3rdPartyContact>;
export type 3rdPartyContactOutput = 3rdPartyContactInput;
3

Create the mappers.ts file

Last but not least, inside mappers.ts you have to build the mappings between our unified common object contact and your third party specific type 3rdPartyContact.

It must implement IContactMapper interface.

export interface IContactMapper {
  desunify(
    source: UnifiedContactInput,
    customFieldMappings?: {
      slug: string;
      remote_id: string;
    }[]
  ): DesunifyReturnType;

  unify(
    source: OriginalContactOutput | OriginalContactOutput[],
    customFieldMappings?: {
      slug: string;
      remote_id: string;
    }[]
  ): Promise<UnifiedContactOutput | UnifiedContactOutput[]>;
}
export class My3rdPartyMapper implements IContactMapper {
  desunify(
    source: UnifiedContactInput,
    customFieldMappings?: {
      slug: string;
      remote_id: string;
    }[],
  ): 3rdPartyContactInput {}

  unify(
    source: 3rdPartyContactOutput | 3rdPartyContactOutput[],
    customFieldMappings?: {
      slug: string;
      remote_id: string;
    }[],
  ): Promise<UnifiedContactOutput | UnifiedContactOutput[]> {}
}

Check other implementations under /crm/contacts/services to fill the core functions.

2

Enable your new service

After these 3 files are successfully created and filled, you are ready to fix all dependencies/linting issues that you may have.
To make sure the service is enabled, dependencies and imports must be added.
We built a script that does it in seconds. You can execute the given command from the root directory of Panora.

  docker build -t validate_connectors -f ./packages/api/Dockerfile.validate-connectors .
  docker run -v "$(pwd):/app/" -e VERTICAL=crm -e OBJECT_TYPE=contact validate_connectors    

The script will automatically scan the /crm/contact/services folder and detect any new service folder so all dependencies and imports are updated across the codebase.