Skip to content

Commit

Permalink
[connectors] Implement permission setting in Zendesk (#8213)
Browse files Browse the repository at this point in the history
* feat: implement permission setting

* feat: remove the permission field from the ZendeskBrand model

* feat: add distinct permissions within a brand for the help center and the tickets

* feat: add a migration script

* feat: implement distinct permissions within a brand for the help center and the tickets

* feat: add ticket nodes in retrieveSelectedNodes

* remove the catch-all try catch block

* feat: remove error throwing on permission setting
  • Loading branch information
aubin-tchoi authored Oct 24, 2024
1 parent abfd44f commit 27d5345
Show file tree
Hide file tree
Showing 8 changed files with 540 additions and 48 deletions.
7 changes: 7 additions & 0 deletions connectors/migrations/db/migration_28.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
-- Migration created on Oct 23, 2024
ALTER TABLE "zendesk_brands"
DROP COLUMN "permission";
ALTER TABLE "zendesk_brands"
ADD COLUMN "helpCenterPermission" VARCHAR(255) NOT NULL DEFAULT 'none';
ALTER TABLE "zendesk_brands"
ADD COLUMN "ticketsPermission" VARCHAR(255) NOT NULL DEFAULT 'none';
157 changes: 152 additions & 5 deletions connectors/src/connectors/zendesk/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,34 @@ import { Ok } from "@dust-tt/types";

import type { ConnectorManagerError } from "@connectors/connectors/interface";
import { BaseConnectorManager } from "@connectors/connectors/interface";
import { retrieveZendeskBrandPermissions } from "@connectors/connectors/zendesk/lib/brand_permissions";
import { retrieveZendeskHelpCenterPermissions } from "@connectors/connectors/zendesk/lib/help_center_permissions";
import {
allowSyncZendeskBrand,
retrieveZendeskBrandPermissions,
revokeSyncZendeskBrand,
} from "@connectors/connectors/zendesk/lib/brand_permissions";
import {
allowSyncZendeskCategory,
allowSyncZendeskHelpCenter,
retrieveZendeskHelpCenterPermissions,
revokeSyncZendeskCategory,
revokeSyncZendeskHelpCenter,
} from "@connectors/connectors/zendesk/lib/help_center_permissions";
import {
getBrandIdFromHelpCenterId,
getBrandIdFromInternalId,
getBrandIdFromTicketsId,
getCategoryIdFromInternalId,
} from "@connectors/connectors/zendesk/lib/id_conversions";
import { retrieveSelectedNodes } from "@connectors/connectors/zendesk/lib/permissions";
import { retrieveZendeskTicketPermissions } from "@connectors/connectors/zendesk/lib/ticket_permissions";
import {
allowSyncZendeskTickets,
retrieveZendeskTicketPermissions,
revokeSyncZendeskTickets,
} from "@connectors/connectors/zendesk/lib/ticket_permissions";
import { getZendeskAccessToken } from "@connectors/connectors/zendesk/lib/zendesk_access_token";
import logger from "@connectors/logger/logger";
import { ConnectorResource } from "@connectors/resources/connector_resource";
import { ZendeskConfigurationResource } from "@connectors/resources/zendesk_resources";
import type { DataSourceConfig } from "@connectors/types/data_source_config";

export class ZendeskConnectorManager extends BaseConnectorManager<null> {
Expand Down Expand Up @@ -123,8 +144,134 @@ export class ZendeskConnectorManager extends BaseConnectorManager<null> {
}: {
permissions: Record<string, ConnectorPermission>;
}): Promise<Result<void, Error>> {
logger.info({ permissions }, "Setting permissions");
throw new Error("Method not implemented.");
const connector = await ConnectorResource.fetchById(this.connectorId);
if (!connector) {
logger.error(
{ connectorId: this.connectorId },
"[Zendesk] Connector not found."
);
return new Err(new Error("Connector not found"));
}

const connectionId = connector.connectionId;
const zendeskConfiguration = await ZendeskConfigurationResource.fetchById(
this.connectorId
);
if (!zendeskConfiguration) {
logger.error(
{ connectorId: this.connectorId },
"[Zendesk] ZendeskConfiguration not found. Cannot set permissions."
);
return new Err(new Error("ZendeskConfiguration not found"));
}
const subdomain = zendeskConfiguration.subdomain;

const toBeSignaledBrandIds = new Set<number>();
const toBeSignaledTicketsIds = new Set<number>();
const toBeSignaledHelpCenterIds = new Set<number>();
const toBeSignaledCategoryIds = new Set<number>();
for (const [id, permission] of Object.entries(permissions)) {
if (permission !== "none" && permission !== "read") {
return new Err(
new Error(
`Invalid permission ${permission} for connector ${this.connectorId}`
)
);
}

const brandId = getBrandIdFromInternalId(this.connectorId, id);
const brandHelpCenterId = getBrandIdFromHelpCenterId(
this.connectorId,
id
);
const brandTicketsId = getBrandIdFromTicketsId(this.connectorId, id);
const categoryId = getCategoryIdFromInternalId(this.connectorId, id);

if (brandId) {
toBeSignaledBrandIds.add(brandId);
if (permission === "none") {
await revokeSyncZendeskBrand({
connectorId: this.connectorId,
brandId,
});
}
if (permission === "read") {
await allowSyncZendeskBrand({
subdomain,
connectorId: this.connectorId,
connectionId,
brandId,
});
}
} else if (brandHelpCenterId) {
if (permission === "none") {
const revokedCollection = await revokeSyncZendeskHelpCenter({
connectorId: this.connectorId,
brandId: brandHelpCenterId,
});
if (revokedCollection) {
toBeSignaledHelpCenterIds.add(revokedCollection.brandId);
}
}
if (permission === "read") {
const newBrand = await allowSyncZendeskHelpCenter({
connectorId: this.connectorId,
connectionId,
brandId: brandHelpCenterId,
subdomain,
});
if (newBrand) {
toBeSignaledHelpCenterIds.add(newBrand.brandId);
}
}
} else if (brandTicketsId) {
if (permission === "none") {
const revokedCollection = await revokeSyncZendeskTickets({
connectorId: this.connectorId,
brandId: brandTicketsId,
});
if (revokedCollection) {
toBeSignaledTicketsIds.add(revokedCollection.brandId);
}
}
if (permission === "read") {
const newBrand = await allowSyncZendeskTickets({
connectorId: this.connectorId,
connectionId,
brandId: brandTicketsId,
subdomain,
});
if (newBrand) {
toBeSignaledTicketsIds.add(newBrand.brandId);
}
}
} else if (categoryId) {
if (permission === "none") {
const revokedCategory = await revokeSyncZendeskCategory({
connectorId: this.connectorId,
categoryId,
});
if (revokedCategory) {
toBeSignaledCategoryIds.add(revokedCategory.categoryId);
}
}
if (permission === "read") {
const newCategory = await allowSyncZendeskCategory({
subdomain,
connectorId: this.connectorId,
connectionId,
categoryId,
});
if (newCategory) {
toBeSignaledCategoryIds.add(newCategory.categoryId);
}
}
}
}

/// Launch a sync workflow here

return new Ok(undefined);
}

async retrieveBatchContentNodes({
Expand Down
52 changes: 35 additions & 17 deletions connectors/src/connectors/zendesk/lib/brand_permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import type {
ModelId,
} from "@dust-tt/types";

import { allowSyncZendeskHelpCenter } from "@connectors/connectors/zendesk/lib/help_center_permissions";
import { getBrandInternalId } from "@connectors/connectors/zendesk/lib/id_conversions";
import { allowSyncZendeskTickets } from "@connectors/connectors/zendesk/lib/ticket_permissions";
import { getZendeskAccessToken } from "@connectors/connectors/zendesk/lib/zendesk_access_token";
import { createZendeskClient } from "@connectors/connectors/zendesk/lib/zendesk_api";
import logger from "@connectors/logger/logger";
Expand All @@ -15,25 +17,26 @@ import { ZendeskBrandResource } from "@connectors/resources/zendesk_resources";
/**
* Mark a brand as permission "read" and all children (help center and tickets) if specified.
*/
export async function allowSyncBrand({
export async function allowSyncZendeskBrand({
subdomain,
connectorId,
connectionId,
brandId,
withChildren = false,
}: {
subdomain: string;
connectorId: ModelId;
connectionId: string;
brandId: number;
withChildren?: boolean;
}): Promise<ZendeskBrandResource> {
}): Promise<ZendeskBrandResource | null> {
let brand = await ZendeskBrandResource.fetchByBrandId({
connectorId,
brandId,
});
if (brand?.permission === "none") {
await brand.update({ permission: "read" });
if (brand?.helpCenterPermission === "none") {
await brand.update({ helpCenterPermission: "read" });
}
if (brand?.ticketsPermission === "none") {
await brand.update({ ticketsPermission: "read" });
}

const token = await getZendeskAccessToken(connectionId);
Expand All @@ -50,28 +53,38 @@ export async function allowSyncBrand({
connectorId: connectorId,
brandId: fetchedBrand.id,
name: fetchedBrand.name || "Brand",
permission: "read",
helpCenterPermission: "read",
ticketsPermission: "read",
hasHelpCenter: fetchedBrand.has_help_center,
url: fetchedBrand.url,
},
});
} else {
logger.error({ brandId }, "[Zendesk] Brand could not be fetched.");
throw new Error("Brand could not be fetched.");
return null;
}
}

if (withChildren) {
throw new Error("withChildren not implemented yet.");
}
await allowSyncZendeskHelpCenter({
subdomain,
connectorId,
connectionId,
brandId,
});
await allowSyncZendeskTickets({
subdomain,
connectorId,
connectionId,
brandId,
});

return brand;
}

/**
* Mark a help center as permission "none" and all children (collections & articles).
* Mark a help center as permission "none" and all children (collections and articles).
*/
export async function revokeSyncBrand({
export async function revokeSyncZendeskBrand({
connectorId,
brandId,
}: {
Expand Down Expand Up @@ -114,11 +127,12 @@ export async function retrieveZendeskBrandPermissions({
const isRootLevel = !parentInternalId;
let nodes: ContentNode[] = [];

// At the root level, we show one node for each brand that has a help center.
// At the root level, we show one node for each brand.
if (isRootLevel) {
if (isReadPermissionsOnly) {
const brandsInDatabase =
await ZendeskBrandResource.fetchBrandsWithHelpCenter({ connectorId });
const brandsInDatabase = await ZendeskBrandResource.fetchAllReadOnly({
connectorId,
});
nodes = brandsInDatabase.map((brand) => ({
provider: connector.type,
internalId: getBrandInternalId(connectorId, brand.brandId),
Expand All @@ -127,7 +141,11 @@ export async function retrieveZendeskBrandPermissions({
title: brand.name,
sourceUrl: brand.url,
expandable: true,
permission: brand.permission,
permission:
brand.helpCenterPermission === "read" &&
brand.ticketsPermission === "read"
? "read"
: "none",
dustDocumentId: null,
lastUpdatedAt: brand.updatedAt.getTime(),
}));
Expand Down
Loading

0 comments on commit 27d5345

Please sign in to comment.