Skip to content

Commit

Permalink
Merge pull request #88 from linear/gui/usp-7518-add-trigger-and-creat…
Browse files Browse the repository at this point in the history
…e-for-customerneed

Add support for `CustomerNeed` entity (Create, Trigger)
  • Loading branch information
guillaumelachaud authored Feb 7, 2025
2 parents 57ff386 + cf2289b commit b8b04c6
Show file tree
Hide file tree
Showing 4 changed files with 276 additions and 1 deletion.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "linear-zapier",
"version": "4.4.7",
"version": "4.5.0",
"description": "Linear's Zapier integration",
"main": "index.js",
"license": "MIT",
Expand Down
158 changes: 158 additions & 0 deletions src/creates/createCustomerNeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Bundle, ZObject } from "zapier-platform-core";
import { fetchFromLinear } from "../fetchFromLinear";
import { omitBy } from "lodash";

interface CustomerNeedCreateResponse {
data?: { customerNeedCreate: { need: { id: string; customerId?: string, issueId?: string, attachmentId?: string }; success: boolean } };
errors?: {
message: string;
extensions?: {
userPresentableMessage?: string;
};
}[];
}

const createCustomerNeedRequest = async (z: ZObject, bundle: Bundle) => {
const variables = omitBy(
{
customerId: bundle.inputData.customerId,
customerExternalId: bundle.inputData.customerExternalId,
issueId: bundle.inputData.issueId,
attachmentId: bundle.inputData.attachmentId,
attachmentUrl: bundle.inputData.attachmentUrl,
body: bundle.inputData.body,
priority: bundle.inputData.priority,
},
(v) => v === undefined
);

if(variables.attachmentId && variables.attachmentUrl) {
throw new Error ("Cannot specify both attachmentId and attachmentUrl");
} else if (variables.customerId && variables.customerExternalId) {
throw new Error ("Cannot specify both customerId and customerExternalId");
}

const query = `
mutation ZapierCustomerNeedCreate(
$customerId: String,
$customerExternalId: String,
$issueId: String,
$attachmentId: String,
$attachmentUrl: String,
$body: String,
$priority: Float,
) {
customerNeedCreate(input: {
customerId: $customerId,
customerExternalId: $customerExternalId,
issueId: $issueId,
attachmentId: $attachmentId,
attachmentUrl: $attachmentUrl,
body: $body,
priority: $priority,
}) {
need {
id
customer {
id
}
issue {
id
}
attachment {
id
}
}
success
}
}`;

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

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.customerNeedCreate && data.data.customerNeedCreate.success) {
return data.data.customerNeedCreate.need;
} else {
const error = data.errors ? data.errors[0].message : "Something went wrong";
throw new z.errors.Error("Failed to create a customer need", error, 400);
}
};

export const createCustomerNeed = {
key: "createCustomerNeed",
display: {
hidden: false,
description: "Create a new customer need in Linear",
label: "Create Customer Need",
},
noun: "Customer Need",
operation: {
perform: createCustomerNeedRequest,
inputFields: [
{
required: false,
label: "Customer ID",
helpText: "The ID of the customer to create the need for",
key: "customerId",
},
{
required: false,
label: "External Customer ID",
helpText: "The external ID of the customer the need belongs to",
key: "customerExternalId",
},
{
required: false,
label: "Issue ID",
helpText: "The ID of the issue this need is for",
key: "issueId",
type: "text",
},
{
required: false,
label: "Attachment ID",
helpText: "The ID of the attachment this need is associated with",
key: "attachmentId",
type: "text",
},
{
required: false,
label: "Attachment URL",
helpText: "Optional URL for the attachment associated with the customer need",
key: "attachmentUrl",
type: "text",
},
{
required: false,
label: "Body",
helpText: "The content of the need in markdown format.",
key: "body",
type: "text",
},
{
required: false,
label: "Priority",
helpText: "Whether the customer need is important or not. 0 = Not important, 1 = Important.",
key: "priority",
type: "number",
},
],
sample: {
data: {
customerNeedCreate: {
need: { id: "93a02c29-da90-4d06-ab1c-96956e94bcd0", customerId: "6465f500-6626-4253-9073-144535a6c658", issueId: "a8ea3bfa-5420-492a-84e9-ffe49ca5f22a" },
success: true,
},
},
},
},
};
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import { findProjectByID } from "./searches/project";
import {createCustomer} from "./creates/createCustomer";
import {findCustomerByID} from "./searches/customer";
import {newCustomerInstant, updatedCustomerInstant} from "./triggers/customer";
import {createCustomerNeed} from "./creates/createCustomerNeed";
import {newCustomerNeedInstant, updatedCustomerNeedInstant} from "./triggers/customerNeed";

const handleErrors = (response: HttpResponse, z: ZObject) => {
if (response.request.url !== "https://api.linear.app/graphql") {
Expand Down Expand Up @@ -61,6 +63,7 @@ const App = {
[createProject.key]: createProject,
[updateIssue.key]: updateIssue,
[createCustomer.key]: createCustomer,
[createCustomerNeed.key]: createCustomerNeed,
},
triggers: {
[newIssue.key]: newIssue,
Expand Down Expand Up @@ -92,6 +95,9 @@ const App = {
[updatedProjectInstant.key]: updatedProjectInstant,
[newCustomerInstant.key]: newCustomerInstant,
[updatedCustomerInstant.key]: updatedCustomerInstant,
[newCustomerNeedInstant.key]: newCustomerNeedInstant,
[updatedCustomerNeedInstant.key]: updatedCustomerNeedInstant,

},
searches: {
[findIssueByID.key]: findIssueByID,
Expand Down
111 changes: 111 additions & 0 deletions src/triggers/customerNeed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
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 CustomerNeedCommon {
id: string;
createdAt: Date;
updatedAt: Date;
customerId?: string;
issueId?: string;
attachmentId?: string;
body?: string;
priority?: number;
}

interface CustomerNeedsResponse {
data: {
customerNeeds: {
nodes: CustomerNeedCommon[];
};
};
}

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

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

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

const jsonQuery = {
query: {
__variables: variableSchema,
customerNeeds: {
__args: {
first: 25,
},
nodes: {
id: true,
createdAt: true,
updatedAt: true,
body: true,
priority: true,
customer: {
id: true
},
issue: {
id: true
},
attachment: {
id: true
}
},
},
},
};
const query = jsonToGraphQLQuery(jsonQuery);
const response = await fetchFromLinear(z, bundle, query, variables);
const data = (response.json as CustomerNeedsResponse).data;
return data.customerNeeds.nodes;
};

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

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

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

0 comments on commit b8b04c6

Please sign in to comment.