README: Slack Triggered Jira Ticket Creation

This is a relatively complex Functions, featuring multiple triggers and custom error handling.

This function will create a Jira ticket based on a Slack thread if either

  1. A user reacts to a message with the :jira: emoji
  2. A public channel message starts with “Dear :jira:”

Prerequisites:

  • Jira account with a project that can be configured as a ticket destination
  • A :jira: emoji in your Slack workspace
  • Slack account (can be updated to an alternative chat/notification tool)
  • An OpenAI account and API key

TODOs:

  • Create a new function at function.zapier.com

  • Create a Slack "New Reaction" trigger that triggers when a user adds a :jira: reaction

  • Create a Slack "New Mention" trigger that triggers when a user mentions “Dear :jira:” in a specified channel

  • Copy the contents of the main.py file below and paste it into your new function's main.py file

  • Add the openai package to the packages tab in the right side panel of your new function.

  • Add your OpenAI API key as a secret called OPENAI_API_KEY to the secrets tab in the right side panel of your new function.

  • Update the Zapier actions in your main.py file. Search for TODOs to identify where this is necessary. Note: When you add your account_id functions may duplicate the params. Delete the new params & fill out the existing params.

  • Test by pushing 'Run Function' and when you are ready, deploy!

main.py file

import datetime
import json
import os
import requests
import openai

is_reaction_trigger = "reaction" in zapier.trigger_output["trigger_name"]
is_message_trigger = "message" in zapier.trigger_output["trigger_name"]
formatted_date = ""

# get message depending on the trigger type
def get_message():
  if is_reaction_trigger:
    return zapier.trigger_output["message"]
  else: 
    return zapier.trigger_output
    
# send a message to the triggering Slack thread   
def send_slack_message(message):
  trigger_message = get_message()
  thread_ts = trigger_message["ts"]
  channel_id = trigger_message["channel"]["id"]
  formatted_message = f"""{message}
  
  :star: _Powered by <https://functions.zapier.com | Zapier Functions>_ :star:
  """
  
  # TODO: Type CNTRL + Space to select your account id 
  zapier.action.slack.channel_message(
    account_id=,        
    params={
        "channel":channel_id,  # Channel
        "text": formatted_message,  # Message Text
        "as_bot":"yes",  # Send as a bot? | Valid inputs: "no", "yes"
        "username":"JiraFairy",  # Bot Name
        "icon":":jira-fairy:",  # Bot Icon
        # "add_edit_link":"",  # Include a link to this Zap? | Valid inputs: "no", "yes"
        # "image_url":"",  # Attach Image by URL
        # "unfurl":"",  # Auto-Expand Links? | Valid inputs: "no", "yes"
        # "link_names":"",  # Link Usernames and Channel Names? | Valid inputs: "no", "yes"
        # "post_at":"",  # Schedule At
        # "file":"",  # File
        "thread_ts":thread_ts,  # Thread
        # "reply_broadcast":"",  # Send Channel Message? | Valid inputs: "no", "yes"
    },
    type_of="write",
  )


# Get the thread for the Slack trigger message and put it into a format consumable by OpenAI
def create_conversation_for_openai():
  message = get_message()
  # TODO: Type CNTRL + Space to select your account id 
  slack_thread_response = zapier.action.slack._zap_raw_request(
    account_id=,  
    params={
        # "raw_request_user_info":"",  # Raw Request User Info
        "method":"GET",  # HTTP Method | Valid inputs: "PATCH", "PUT", "POST", "GET", "DELETE"
        "url":"https://slack.com/api/conversations.replies",  # URL
        "querystring": {
          "channel": message["channel"]["id"], 
          "ts": message["ts"],
          },  # Query String Parameters
        # "header_info":"",  # Headers
        # "headers":"",  # Additional Request Headers
        # "body":"",  # Body
        # "api_docs_info":"",  # Api Docs Info
    },
    type_of="write",
  )

  body = slack_thread_response[0]["response"]["body"]
  json_body = json.loads(body)
  messages = json_body["messages"]
  
  # sent to OpenAI as the conversation
  conversation = []

  # Loop through the messages and get the user's real name for each message
  for m in messages:
    if m.get("user") is not None:
      
      # TODO: Type CNTRL + Space to select your account id
      user_response = zapier.action.slack.user_by_id(
        account_id=,            
        params={
            "id":m.get("user"),  # ID
            # "raw":"",  # Return Raw Results? | Valid inputs: "false", "true"
            # "_zap_search_success_on_miss":"",  # Should this step be considered a "success" when nothing is found? | Valid inputs: "true", "false"
        },
        type_of="search",
      )
      user_name = "unknown"
      if user_response[0] is not None:
        user_name = user_response[0].get("real_name", "unknown")
      
      # add to the conversation, which will be sent to OpenAI
      conversation.append({"user": user_name, "message": m.get("text")})
  
  return conversation
  
def create_openai_completion(conversation, ticket_types):
  formatted_ticket_types = " | ".join(string_ticket_types)
  english_ticket_types = " or ".join(string_ticket_types)

  prompt = """The following is a Slack conversation between employees of Zapier. 
        Create a Jira ticket to solve the problem identified by this conversation. 
        List out any acceptance criteria that may be mentioned in the conversation. 
        Do not use any pronouns in your response to refer to people other than "they", "them", or "their".
        The response should include an "issueType" that is {english_ticket_types}. 
        The response should be in a JSON object shaped like the following:
        { "summary": string, "description": string, "issueType": {formatted_ticket_types} }
        """
        
  openai_response = openai.chat.completions.create(
    model="gpt-3.5-turbo",  
    messages=[
        {   
            "role": "user", 
            "content": prompt}, 
        {
            "role": "user",
            "content": json.dumps(conversation)
        }]
    )

  openai_message_content = json.loads(openai_response.choices[0].message.content.strip())
  print(openai_message_content)

  issue_type = openai_message_content.get("issueType")
  summary = openai_message_content.get("summary")
  description = openai_message_content.get("description")
  
  if issue_type not in ticket_types:
    issue_type = ticket_types[0]

  type_map = {
    "Task": "10002",
    "Bug": "10004",
    "Story": "10001"
  }

  jira_description = description + f"""
  
  This ticket was generated by {user_real_name} via OpenAI based on this Slack thread: {slack_thread}. 
  """
    
  return {
    "summary": summary,
    "description": jira_description,
    "issue_type_code": type_map[issue_type],
    "issue_type": issue_type
  }

try:
  
  trigger_message = get_message()
  
#   # Variables that are used throughout the rest of the function
  trigger_user = zapier.trigger_output["user"]
  now = datetime.datetime.now()
  formatted_date = now.strftime("%m/%d/%y")

  channel_id = trigger_message["channel"]["id"]
  thread_ts = trigger_message["ts"]
  channel = trigger_message["channel"]["name"]
  slack_thread = trigger_message["permalink"]
  user_real_name = trigger_user["real_name"]
  user_email = trigger_user["profile"]["email"]
  string_ticket_types = ["Task", "Bug", "Story"]
    
  conversation = create_conversation_for_openai()
  print("Conversation sent to OpenAI: ", conversation)
  
  openai_completion = create_openai_completion(conversation, string_ticket_types)
  print("OpenAI completion:", openai_completion)
  
  issue_type = openai_completion["issue_type"]
  issue_type_code = openai_completion["issue_type_code"]
  description = openai_completion["description"]
  summary = openai_completion["summary"]
  
  # TODO: Type CNTRL + Space to select your account id and project
  jira_create_response = zapier.action.jira_software_cloud.create_issue(
    account_id=,                  
    params={
        "project":,   # Project
        "issuetype": issue_type_code,  # Issue Type
        # "info_actions":"",  # Info Actions
        # "info":"",  # Info
        # "user::assignee":,  # Assignee
        #"array::components":,  # Components
        # "any::customfield_10008":,  # Epic Link
        # "date::customfield_10051":"",  # Start date
        # "string::customfield_10275::doc":"",  # Zendesk Ticket IDs
        # "option::customfield_10346":"",  # Work Type | Valid inputs: "10585", "10586", "11646"
        "string::description":description,
        # "date::duedate":"",  # Due date
        # "array::fixVersions":[],  # Version
        "array::labels":["openai-generated"],  # Labels
        # "issuelink::parent":,  # Parent
        # "priority::priority":"",  # Priority | Valid inputs: "1", "2", "3", "4", "5", "10000"
        "string::summary":summary,  # Summary
    },
    type_of="write"
  )
  
  if jira_create_response is None:
    raise RuntimeError("Something went wrong when creating a Jira issue. There may be a mistake in the access control table.")
      
  print("JIRA_CREATE_RESPONSE: ", jira_create_response)
      
  ticket_number = jira_create_response[0]["key"]
  
  message_text = f""":jira: A {issue_type} ticket <https://zapierorg.atlassian.net/browse/{ticket_number} | 
  {ticket_number}> was just created based on the contents of this conversation."""
  send_slack_message(message_text)
  
except Exception as e:
  error_message = str(e)
  print(error_message)
  trigger_message = get_message()
  
  channel_id = trigger_message["channel"]["id"]
  thread_link = trigger_message["permalink"]  
  
  slack_message = f"Hmmm...something went wrong when I tried to create a Jira ticket just now. Here's the error: {error_message}"
  send_slack_message(slack_message)