Skip to content

Commit

Permalink
Merge pull request #87 from linear/gui/usp-7514-add-trigger-search-an…
Browse files Browse the repository at this point in the history
…d-create-for-customer

Add support for `Customer` entity (Create, Search, Trigger)
  • Loading branch information
guillaumelachaud authored Feb 5, 2025
2 parents c9dbbe7 + f813506 commit 57ff386
Show file tree
Hide file tree
Showing 5 changed files with 341 additions and 0 deletions.
138 changes: 138 additions & 0 deletions src/creates/createCustomer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Bundle, ZObject } from "zapier-platform-core";
import { fetchFromLinear } from "../fetchFromLinear";
import { omitBy } from "lodash";

interface CustomerCreateResponse {
data?: { customerCreate: { customer: { id: string; name: string, domains: string[], externalIds: string[] }; success: boolean } };
errors?: {
message: string;
extensions?: {
userPresentableMessage?: string;
};
}[];
}

const createCustomerRequest = async (z: ZObject, bundle: Bundle) => {
const variables = omitBy(
{
name: bundle.inputData.name,
domains: bundle.inputData.domains || [],
externalIds: bundle.inputData.externalIds || [],
revenue: bundle.inputData.revenue,
size: bundle.inputData.size,
tierId: bundle.inputData.tierId,
},
(v) => v === undefined
);
const query = `
mutation ZapierCustomerCreate(
$name: String!,
$domains: [String!],
$externalIds: [String!],
$revenue: Int,
$size: Int,
$tierId: String,
) {
customerCreate(input: {
name: $name,
domains: $domains,
externalIds: $externalIds,
revenue: $revenue,
size: $size,
tierId: $tierId
}) {
customer {
id
name
domains
externalIds
}
success
}
}`;

const response = await fetchFromLinear(z, bundle, query, variables);
const data = response.json as CustomerCreateResponse;

if (data.errors && data.errors.length) {
const error = data.errors[0];
throw new z.errors.Error(
(error.extensions && error.extensions.userPresentableMessage) || error.message,
"invalid_input",
400
);
}

if (data.data && data.data.customerCreate && data.data.customerCreate.success) {
return data.data.customerCreate.customer;
} else {
const error = data.errors ? data.errors[0].message : "Something went wrong";
throw new z.errors.Error("Failed to create a customer", error, 400);
}
};

export const createCustomer = {
key: "createCustomer",
display: {
hidden: false,
description: "Create a new customer in Linear",
label: "Create Customer",
},
noun: "Customer",
operation: {
perform: createCustomerRequest,
inputFields: [
{
required: true,
label: "Name",
helpText: "The name of the customer",
key: "name",
},
{
required: false,
label: "Domains",
helpText: "The domains associated with this customer",
key: "domains",
type: "text",
list: true,
},
{
required: false,
label: "External IDs",
helpText: "The ids of the customers in external systems",
key: "externalIds",
type: "text",
list: true,
},
{
required: false,
label: "Revenue",
helpText: "The annual revenue generated by the customer",
key: "revenue",
type: "number",
},
{
required: false,
label: "Size",
helpText: "The size of the customer",
key: "size",
type: "number",
},
{
required: false,
label: "Tier ID",
helpText: "The tier of the customer",
key: "tierId",
type: "text",
},
],
sample: {
data: {
customerCreate: {
customer: { id: "068fbd0a-c1d5-448b-af2d-432127520cbd", domains: ["https://www.example.com"], name: "Example customer" },
success: true,
},
},
},
},
};
7 changes: 7 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import { updateIssue } from "./creates/updateIssue";
import { issueTemplates } from "./triggers/issueTemplates";
import { findIssueByID } from "./searches/issue";
import { findProjectByID } from "./searches/project";
import {createCustomer} from "./creates/createCustomer";
import {findCustomerByID} from "./searches/customer";
import {newCustomerInstant, updatedCustomerInstant} from "./triggers/customer";

const handleErrors = (response: HttpResponse, z: ZObject) => {
if (response.request.url !== "https://api.linear.app/graphql") {
Expand Down Expand Up @@ -57,6 +60,7 @@ const App = {
[createIssueAttachment.key]: createIssueAttachment,
[createProject.key]: createProject,
[updateIssue.key]: updateIssue,
[createCustomer.key]: createCustomer,
},
triggers: {
[newIssue.key]: newIssue,
Expand Down Expand Up @@ -86,10 +90,13 @@ const App = {
[projectStatus.key]: projectStatus,
[newProjectInstant.key]: newProjectInstant,
[updatedProjectInstant.key]: updatedProjectInstant,
[newCustomerInstant.key]: newCustomerInstant,
[updatedCustomerInstant.key]: updatedCustomerInstant,
},
searches: {
[findIssueByID.key]: findIssueByID,
[findProjectByID.key]: findProjectByID,
[findCustomerByID.key]: findCustomerByID,
},
authentication,
beforeRequest: [addBearerHeader],
Expand Down
16 changes: 16 additions & 0 deletions src/samples/customer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"id": "ccf432b1-6899-4f1e-9d6c-59e21fae2a17",
"name": "Example",
"createdAt": "2024-10-04T22:38:43.396Z",
"updatedAt": "2024-10-04T22:39:43.396Z",
"domains": [
"example.com"
],
"externalIds": ["123456789"],
"revenue": 10000,
"size": 100,
"tier": {
"id": "565dbccb-aab4-4b63-bf55-fc8ea634084b",
"name": "Enterprise"
}
}
67 changes: 67 additions & 0 deletions src/searches/customer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { ZObject, Bundle } from "zapier-platform-core";
import sample from "../samples/customer.json";
import {CustomerCommon} from "../triggers/customer";

interface CustomerResponse {
data: {
customer: CustomerCommon;
};
}

const getCustomer = async (z: ZObject, bundle: Bundle) => {
const response = await z.request({
url: "https://api.linear.app/graphql",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
authorization: bundle.authData.api_key,
},
body: {
query: `
query Customer {
customer(id: "${bundle.inputData.id}") {
id
name
domains
externalIds
createdAt
updatedAt
revenue
size
tier {
id
name
}
}
}`,
},
method: "POST",
});
const data = (response.json as CustomerResponse).data;
const customer = data.customer;

return [customer];
};

export const findCustomerByID = {
key: "customer",
noun: "Customer",

display: {
label: "Find Customer by ID",
hidden: false,
description: "Find a customer by ID",
},

operation: {
perform: getCustomer,
inputFields: [
{
key: "id",
required: true,
label: "Customer ID",
},
],
sample,
},
};
113 changes: 113 additions & 0 deletions src/triggers/customer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {Bundle, ZObject} from "zapier-platform-core";
import sample from "../samples/customer.json";
import {getWebhookData, unsubscribeHook} from "../handleWebhook";
import {jsonToGraphQLQuery} from "json-to-graphql-query";
import {fetchFromLinear} from "../fetchFromLinear";

export interface CustomerCommon {
id: string;
name: string;
domains: string[];
externalIds: string[];
createdAt: Date;
updatedAt: Date;
revenue?: number;
size?: number;
tier?: {
id: string;
name: string;
};
}

interface CustomersResponse {
data: {
customers: {
nodes: CustomerCommon[];
};
};
}

const subscribeHook = (eventType: "create" | "update") => async (z: ZObject, bundle: Bundle) => {
const data = {
url: bundle.targetUrl,
};
const webhookType = eventType === "create" ? "createCustomer" : "updateCustomer";

return z
.request({
url: `https://client-api.linear.app/connect/zapier/subscribe/${webhookType}`,
method: "POST",
body: data,
})
.then((response) => response.data);
};

const getCustomerList =
() =>
async (z: ZObject, bundle: Bundle): Promise<CustomerCommon[]> => {
const variables: Record<string, string> = {};
const variableSchema: Record<string, string> = {};

const jsonQuery = {
query: {
__variables: variableSchema,
customers: {
__args: {
first: 25,
},
nodes: {
id: true,
name: true,
domains: true,
externalIds: true,
createdAt: true,
updatedAt: true,
revenue: true,
size: true,
tier: {
id: true,
name: true,
},
},
},
},
};
const query = jsonToGraphQLQuery(jsonQuery);
const response = await fetchFromLinear(z, bundle, query, variables);
const data = (response.json as CustomersResponse).data;
return data.customers.nodes;
};

const operationBase = {
type: "hook",
perform: getWebhookData,
performUnsubscribe: unsubscribeHook,
performList: getCustomerList(),
sample,
};

export const newCustomerInstant = {
noun: "Customer",
key: "newCustomerInstant",
display: {
label: "New Customer",
description: "Triggers when a new customer is created.",
},
operation: {
...operationBase,
performSubscribe: subscribeHook("create"),
},
};

export const updatedCustomerInstant = {
noun: "Customer",
key: "updatedCustomerInstant",
display: {
label: "Updated Customer",
description: "Triggers when a customer is updated.",
},
operation: {
...operationBase,
performSubscribe: subscribeHook("update"),
},
};

0 comments on commit 57ff386

Please sign in to comment.