Skip to content

Commit

Permalink
Merge pull request #79 from linear/leela/usp-6406-support-templateid-…
Browse files Browse the repository at this point in the history
…input-in-issue-creation-action

Support template ID as an issue creation input
  • Loading branch information
leelasn authored Oct 18, 2024
2 parents 4d7f3d1 + a7cb7d4 commit 7110d58
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 10 deletions.
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.0",
"version": "4.4.1",
"description": "Linear's Zapier integration",
"main": "index.js",
"license": "MIT",
Expand Down
50 changes: 41 additions & 9 deletions src/creates/createIssue.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Bundle, ZObject } from "zapier-platform-core";
import { fetchFromLinear } from "../fetchFromLinear";

interface CreateIssueRequestResponse {
data?: {
Expand All @@ -21,8 +22,8 @@ interface CreateIssueRequestResponse {
}

const createIssueRequest = async (z: ZObject, bundle: Bundle) => {
const priority = bundle.inputData.priority ? parseInt(bundle.inputData.priority) : 0;
const estimate = bundle.inputData.estimate ? parseInt(bundle.inputData.estimate) : null;
const priority = bundle.inputData.priority ? parseInt(bundle.inputData.priority) : undefined;
const estimate = bundle.inputData.estimate ? parseInt(bundle.inputData.estimate) : undefined;

const subscriberIds: string[] = [];
if (bundle.inputData.subscriber_emails) {
Expand Down Expand Up @@ -86,6 +87,7 @@ const createIssueRequest = async (z: ZObject, bundle: Bundle) => {

const variables = {
teamId: bundle.inputData.team_id,
templateId: bundle.inputData.templateId,
title: bundle.inputData.title,
description: bundle.inputData.description,
priority: priority,
Expand All @@ -96,13 +98,14 @@ const createIssueRequest = async (z: ZObject, bundle: Bundle) => {
projectId: bundle.inputData.project_id,
projectMilestoneId: bundle.inputData.project_milestone_id,
dueDate: bundle.inputData.due_date,
labelIds: bundle.inputData.labels || [],
subscriberIds,
labelIds: bundle.inputData.labels || undefined,
subscriberIds: subscriberIds.length > 0 ? subscriberIds : undefined,
};

const query = `
mutation ZapierIssueCreate(
$teamId: String!,
$templateId: String,
$title: String!,
$description: String,
$priority: Int,
Expand All @@ -118,6 +121,7 @@ const createIssueRequest = async (z: ZObject, bundle: Bundle) => {
) {
issueCreate(input: {
teamId: $teamId,
templateId: $templateId,
title: $title,
description: $description,
priority: $priority,
Expand Down Expand Up @@ -189,6 +193,15 @@ export const createIssue = {
perform: createIssueRequest,

inputFields: [
{
label: "Template",
helpText:
"The template to use for the issue. If no team has been selected yet, only workspace templates will be available. If other inputs are provided, they will override the template values.",
key: "templateId",
dynamic: "issueTemplates.id.name",
altersDynamicFields: true,
},
// Even if a template is chosen, we can't set a dynamic field programmatically based on another input: https://github.com/zapier/zapier-platform-cli#customdynamic-fields
{
required: true,
label: "Team",
Expand All @@ -197,11 +210,30 @@ export const createIssue = {
dynamic: "team.id.name",
altersDynamicFields: true,
},
{
required: true,
label: "Title",
helpText: "The title of the issue",
key: "title",
// Populate title with the template's title if applicable
async function (z: ZObject, bundle: Bundle) {
const baseTitle = {
label: "Title",
helpText: "The title of the issue",
key: "title",
required: true,
};
if (bundle.inputData.templateId) {
const query = `
query ZapierTemplate($templateId: String!) {
template(id: $templateId) {
templateData
}
}`;
const response = await fetchFromLinear(z, bundle, query, { templateId: bundle.inputData.templateId });

const template = response.json.data.template;
const templateData = JSON.parse(template.templateData);
if (templateData.title) {
return { ...baseTitle, required: false };
}
}
return baseTitle;
},
{
label: "Description",
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { newProjectInstant } from "./triggers/newProject";
import { createIssueAttachment } from "./creates/createIssueAttachment";
import { createProject } from "./creates/createProject";
import { updateIssue } from "./creates/updateIssue";
import { issueTemplates } from "./triggers/issueTemplates";

const handleErrors = (response: HttpResponse, z: ZObject) => {
if (response.request.url !== "https://api.linear.app/graphql") {
Expand Down Expand Up @@ -71,6 +72,7 @@ const App = {
[updatedProjectUpdate.key]: updatedProjectUpdate,
[updatedProjectUpdateInstant.key]: updatedProjectUpdateInstant,
[team.key]: team,
[issueTemplates.key]: issueTemplates,
[status.key]: status,
[project.key]: project,
[projectWithoutTeam.key]: projectWithoutTeam,
Expand Down
86 changes: 86 additions & 0 deletions src/triggers/issueTemplates.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { ZObject, Bundle } from "zapier-platform-core";
import { fetchFromLinear } from "../fetchFromLinear";
import { omitBy } from "lodash";

interface TemplateResponse {
id: string;
name: string;
}

interface TemplatesResponseTeam {
data: {
team: {
templates: {
nodes: TemplateResponse[];
};
};
};
}

interface TemplatesResponseWorkspace {
data: {
organization: {
templates: {
nodes: TemplateResponse[];
};
};
};
}

const getTemplateList = async (z: ZObject, bundle: Bundle) => {
const teamId = bundle.inputData.teamId || bundle.inputData.team_id;
const cursor = bundle.meta.page ? await z.cursor.get() : undefined;
// We fetch team and workspace templates if a team ID has been specified already, and only workspace templates if not
const query = teamId
? `
query ZapierListTemplates($teamId: String!, $after: String) {
team(id: $teamId) {
templates(first: 50, after: $after, filter: { type: { eq: "issue" } }) {
nodes {
id
name
}
}
}
}`
: `
query ZapierListTemplates($after: String) {
organization {
templates(first: 50, after: $after, filter: { type: { eq: "issue" } }) {
nodes {
id
name
}
}
}
}`;
const variables = omitBy({ teamId, after: cursor }, (v) => v === undefined);

const response = await fetchFromLinear(z, bundle, query, variables);
const data = (response.json as TemplatesResponseTeam | TemplatesResponseWorkspace).data;
const templates = "team" in data ? data.team.templates.nodes : data.organization.templates.nodes;

const nextCursor = templates?.[templates.length - 1]?.id;
if (nextCursor) {
await z.cursor.set(nextCursor);
}

return templates;
};

export const issueTemplates = {
key: "issueTemplates",
noun: "Issue Templates",

display: {
label: "Get issue templates",
hidden: true,
description:
"The only purpose of this trigger is to populate the dropdown list of issue templates in the UI, thus, it's hidden.",
},

operation: {
perform: getTemplateList,
canPaginate: true,
},
};

0 comments on commit 7110d58

Please sign in to comment.