Skip to content

Commit b8b04c6

Browse files
Merge pull request #88 from linear/gui/usp-7518-add-trigger-and-create-for-customerneed
Add support for `CustomerNeed` entity (Create, Trigger)
2 parents 57ff386 + cf2289b commit b8b04c6

File tree

4 files changed

+276
-1
lines changed

4 files changed

+276
-1
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "linear-zapier",
3-
"version": "4.4.7",
3+
"version": "4.5.0",
44
"description": "Linear's Zapier integration",
55
"main": "index.js",
66
"license": "MIT",

src/creates/createCustomerNeed.ts

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import { Bundle, ZObject } from "zapier-platform-core";
2+
import { fetchFromLinear } from "../fetchFromLinear";
3+
import { omitBy } from "lodash";
4+
5+
interface CustomerNeedCreateResponse {
6+
data?: { customerNeedCreate: { need: { id: string; customerId?: string, issueId?: string, attachmentId?: string }; success: boolean } };
7+
errors?: {
8+
message: string;
9+
extensions?: {
10+
userPresentableMessage?: string;
11+
};
12+
}[];
13+
}
14+
15+
const createCustomerNeedRequest = async (z: ZObject, bundle: Bundle) => {
16+
const variables = omitBy(
17+
{
18+
customerId: bundle.inputData.customerId,
19+
customerExternalId: bundle.inputData.customerExternalId,
20+
issueId: bundle.inputData.issueId,
21+
attachmentId: bundle.inputData.attachmentId,
22+
attachmentUrl: bundle.inputData.attachmentUrl,
23+
body: bundle.inputData.body,
24+
priority: bundle.inputData.priority,
25+
},
26+
(v) => v === undefined
27+
);
28+
29+
if(variables.attachmentId && variables.attachmentUrl) {
30+
throw new Error ("Cannot specify both attachmentId and attachmentUrl");
31+
} else if (variables.customerId && variables.customerExternalId) {
32+
throw new Error ("Cannot specify both customerId and customerExternalId");
33+
}
34+
35+
const query = `
36+
mutation ZapierCustomerNeedCreate(
37+
$customerId: String,
38+
$customerExternalId: String,
39+
$issueId: String,
40+
$attachmentId: String,
41+
$attachmentUrl: String,
42+
$body: String,
43+
$priority: Float,
44+
) {
45+
customerNeedCreate(input: {
46+
customerId: $customerId,
47+
customerExternalId: $customerExternalId,
48+
issueId: $issueId,
49+
attachmentId: $attachmentId,
50+
attachmentUrl: $attachmentUrl,
51+
body: $body,
52+
priority: $priority,
53+
}) {
54+
need {
55+
id
56+
customer {
57+
id
58+
}
59+
issue {
60+
id
61+
}
62+
attachment {
63+
id
64+
}
65+
}
66+
success
67+
}
68+
}`;
69+
70+
const response = await fetchFromLinear(z, bundle, query, variables);
71+
const data = response.json as CustomerNeedCreateResponse;
72+
73+
if (data.errors && data.errors.length) {
74+
const error = data.errors[0];
75+
throw new z.errors.Error(
76+
(error.extensions && error.extensions.userPresentableMessage) || error.message,
77+
"invalid_input",
78+
400
79+
);
80+
}
81+
82+
if (data.data && data.data.customerNeedCreate && data.data.customerNeedCreate.success) {
83+
return data.data.customerNeedCreate.need;
84+
} else {
85+
const error = data.errors ? data.errors[0].message : "Something went wrong";
86+
throw new z.errors.Error("Failed to create a customer need", error, 400);
87+
}
88+
};
89+
90+
export const createCustomerNeed = {
91+
key: "createCustomerNeed",
92+
display: {
93+
hidden: false,
94+
description: "Create a new customer need in Linear",
95+
label: "Create Customer Need",
96+
},
97+
noun: "Customer Need",
98+
operation: {
99+
perform: createCustomerNeedRequest,
100+
inputFields: [
101+
{
102+
required: false,
103+
label: "Customer ID",
104+
helpText: "The ID of the customer to create the need for",
105+
key: "customerId",
106+
},
107+
{
108+
required: false,
109+
label: "External Customer ID",
110+
helpText: "The external ID of the customer the need belongs to",
111+
key: "customerExternalId",
112+
},
113+
{
114+
required: false,
115+
label: "Issue ID",
116+
helpText: "The ID of the issue this need is for",
117+
key: "issueId",
118+
type: "text",
119+
},
120+
{
121+
required: false,
122+
label: "Attachment ID",
123+
helpText: "The ID of the attachment this need is associated with",
124+
key: "attachmentId",
125+
type: "text",
126+
},
127+
{
128+
required: false,
129+
label: "Attachment URL",
130+
helpText: "Optional URL for the attachment associated with the customer need",
131+
key: "attachmentUrl",
132+
type: "text",
133+
},
134+
{
135+
required: false,
136+
label: "Body",
137+
helpText: "The content of the need in markdown format.",
138+
key: "body",
139+
type: "text",
140+
},
141+
{
142+
required: false,
143+
label: "Priority",
144+
helpText: "Whether the customer need is important or not. 0 = Not important, 1 = Important.",
145+
key: "priority",
146+
type: "number",
147+
},
148+
],
149+
sample: {
150+
data: {
151+
customerNeedCreate: {
152+
need: { id: "93a02c29-da90-4d06-ab1c-96956e94bcd0", customerId: "6465f500-6626-4253-9073-144535a6c658", issueId: "a8ea3bfa-5420-492a-84e9-ffe49ca5f22a" },
153+
success: true,
154+
},
155+
},
156+
},
157+
},
158+
};

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ import { findProjectByID } from "./searches/project";
3232
import {createCustomer} from "./creates/createCustomer";
3333
import {findCustomerByID} from "./searches/customer";
3434
import {newCustomerInstant, updatedCustomerInstant} from "./triggers/customer";
35+
import {createCustomerNeed} from "./creates/createCustomerNeed";
36+
import {newCustomerNeedInstant, updatedCustomerNeedInstant} from "./triggers/customerNeed";
3537

3638
const handleErrors = (response: HttpResponse, z: ZObject) => {
3739
if (response.request.url !== "https://api.linear.app/graphql") {
@@ -61,6 +63,7 @@ const App = {
6163
[createProject.key]: createProject,
6264
[updateIssue.key]: updateIssue,
6365
[createCustomer.key]: createCustomer,
66+
[createCustomerNeed.key]: createCustomerNeed,
6467
},
6568
triggers: {
6669
[newIssue.key]: newIssue,
@@ -92,6 +95,9 @@ const App = {
9295
[updatedProjectInstant.key]: updatedProjectInstant,
9396
[newCustomerInstant.key]: newCustomerInstant,
9497
[updatedCustomerInstant.key]: updatedCustomerInstant,
98+
[newCustomerNeedInstant.key]: newCustomerNeedInstant,
99+
[updatedCustomerNeedInstant.key]: updatedCustomerNeedInstant,
100+
95101
},
96102
searches: {
97103
[findIssueByID.key]: findIssueByID,

src/triggers/customerNeed.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import {Bundle, ZObject} from "zapier-platform-core";
2+
import sample from "../samples/customer.json";
3+
import {getWebhookData, unsubscribeHook} from "../handleWebhook";
4+
import {jsonToGraphQLQuery} from "json-to-graphql-query";
5+
import {fetchFromLinear} from "../fetchFromLinear";
6+
7+
export interface CustomerNeedCommon {
8+
id: string;
9+
createdAt: Date;
10+
updatedAt: Date;
11+
customerId?: string;
12+
issueId?: string;
13+
attachmentId?: string;
14+
body?: string;
15+
priority?: number;
16+
}
17+
18+
interface CustomerNeedsResponse {
19+
data: {
20+
customerNeeds: {
21+
nodes: CustomerNeedCommon[];
22+
};
23+
};
24+
}
25+
26+
const subscribeHook = (eventType: "create" | "update") => async (z: ZObject, bundle: Bundle) => {
27+
const data = {
28+
url: bundle.targetUrl,
29+
};
30+
const webhookType = eventType === "create" ? "createCustomerNeed" : "updateCustomerNeed";
31+
32+
return z
33+
.request({
34+
url: `https://client-api.linear.app/connect/zapier/subscribe/${webhookType}`,
35+
method: "POST",
36+
body: data,
37+
})
38+
.then((response) => response.data);
39+
};
40+
41+
const getCustomerNeedsList =
42+
() =>
43+
async (z: ZObject, bundle: Bundle): Promise<CustomerNeedCommon[]> => {
44+
const variables: Record<string, string> = {};
45+
const variableSchema: Record<string, string> = {};
46+
47+
const jsonQuery = {
48+
query: {
49+
__variables: variableSchema,
50+
customerNeeds: {
51+
__args: {
52+
first: 25,
53+
},
54+
nodes: {
55+
id: true,
56+
createdAt: true,
57+
updatedAt: true,
58+
body: true,
59+
priority: true,
60+
customer: {
61+
id: true
62+
},
63+
issue: {
64+
id: true
65+
},
66+
attachment: {
67+
id: true
68+
}
69+
},
70+
},
71+
},
72+
};
73+
const query = jsonToGraphQLQuery(jsonQuery);
74+
const response = await fetchFromLinear(z, bundle, query, variables);
75+
const data = (response.json as CustomerNeedsResponse).data;
76+
return data.customerNeeds.nodes;
77+
};
78+
79+
const operationBase = {
80+
type: "hook",
81+
perform: getWebhookData,
82+
performUnsubscribe: unsubscribeHook,
83+
performList: getCustomerNeedsList(),
84+
sample,
85+
};
86+
87+
export const newCustomerNeedInstant = {
88+
noun: "Customer Need",
89+
key: "newCustomerNeedInstant",
90+
display: {
91+
label: "New Customer Need",
92+
description: "Triggers when a new customer request is created.",
93+
},
94+
operation: {
95+
...operationBase,
96+
performSubscribe: subscribeHook("create"),
97+
},
98+
};
99+
100+
export const updatedCustomerNeedInstant = {
101+
noun: "Customer Need",
102+
key: "updatedCustomerNeedInstant",
103+
display: {
104+
label: "Updated Customer Need",
105+
description: "Triggers when a customer request is updated.",
106+
},
107+
operation: {
108+
...operationBase,
109+
performSubscribe: subscribeHook("update"),
110+
},
111+
};

0 commit comments

Comments
 (0)