import os import html import streamlit as st import pandas as pd from atlassian import Jira import requests from openai import OpenAI from datetime import date, timedelta # ------------------------- # Environment-based secrets # ------------------------- JIRA_URL = os.getenv("JIRA_URL") JIRA_USERNAME = os.getenv("JIRA_USERNAME") JIRA_API_TOKEN = os.getenv("JIRA_API_TOKEN") ZENDESK_EMAIL = os.getenv("ZENDESK_EMAIL") ZENDESK_SUBDOMAIN = os.getenv("ZENDESK_SUBDOMAIN") ZENDESK_API_KEY = os.getenv("ZENDESK_API_KEY") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") client = OpenAI(api_key=OPENAI_API_KEY) # ------------------------- # JIRA Client # ------------------------- jira = Jira(url=JIRA_URL, username=JIRA_USERNAME, password=JIRA_API_TOKEN) # ------------------------- # OpenAI Summarization # ------------------------- @st.cache_data(show_spinner=False) def summarize_ticket(text: str) -> str: if not text: return "No description" prompt = ( "Summarize this Zendesk ticket in 1–3 sentences:\n\n" + text + "\n\nSummary:" ) resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": prompt}], temperature=0.3, max_tokens=150, ) return resp.choices[0].message.content.strip() # ------------------------- # Zendesk Search Function # ------------------------- def search_zendesk_tickets(site_name: str, keyword: str) -> pd.DataFrame: terms = [] if site_name: terms.append(f'"{site_name}"') if keyword: terms.append(f'"{keyword}"') query_str = " ".join(terms) url = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/search.json" params = {"query": f"type:ticket {query_str}", "include": "users"} auth = (f"{ZENDESK_EMAIL}/token", ZENDESK_API_KEY) resp = requests.get(url, auth=auth, params=params) if not resp.ok: st.error(f"Zendesk error {resp.status_code}") return pd.DataFrame() tickets = resp.json().get("results", []) rows = [] for t in tickets: rows.append( { "ID": t["id"], "Subject": html.escape(t.get("subject", "")), "Status": html.escape(t.get("status", "")), "Created At": t.get("created_at", ""), "Updated At": t.get("updated_at", ""), "Description": t.get("description", ""), # keep for summary } ) df = pd.DataFrame(rows) # generate summaries and attach as new column df["OpenAI Ticket Summary"] = df["Description"].apply(summarize_ticket) return df # ------------------------- # Jira Search Function # ------------------------- @st.cache_data(show_spinner=False) def search_jira_issues( site_name: str, keyword: str, start_date: date, end_date: date ) -> pd.DataFrame: # Build JQL clauses clauses = [] if site_name: clauses.append(f'text ~ "{site_name}"') if keyword: clauses.append(f'text ~ "{keyword}"') clauses.append(f'created >= "{start_date.isoformat()}"') clauses.append(f'created <= "{end_date.isoformat()}"') jql = " AND ".join(clauses) # Execute the JQL query, limiting to 100 issues resp = jira.jql(jql, limit=100) issues = resp.get("issues", []) rows = [] for issue in issues: f = issue["fields"] rows.append( { "Key": issue["key"], "Summary": html.escape(f.get("summary", "")), "Status": html.escape(f.get("status", {}).get("name", "")), "Created At": f.get("created", ""), "Updated At": f.get("updated", ""), } ) return pd.DataFrame(rows) # ------------------------- # App Config # ------------------------- st.set_page_config(layout="wide") st.title("Unified Support Dashboard") if "zendesk_df" not in st.session_state: st.session_state.zendesk_df = pd.DataFrame() if "jira_df" not in st.session_state: st.session_state.jira_df = pd.DataFrame() # ------------------------- # Main: Tabs # ------------------------- tabs = st.tabs(["Zendesk Lookup", "Jira Lookup"]) # ---- Tab 1: Zendesk ---- with tabs[0]: st.header("Zendesk Lookup") site_input = st.text_input( "Site Name", placeholder="example.com", key="zendesk_site" ) keyword_input = st.text_input( "Keyword", placeholder="timeout", key="zendesk_keyword" ) start_input = st.date_input( "Created After", value=date.today() - timedelta(days=7), key="zendesk_start" ) end_input = st.date_input("Created Before", value=date.today(), key="zendesk_end") if st.button("Search Zendesk Tickets", key="zendesk_search"): st.session_state.zendesk_df = search_zendesk_tickets(site_input, keyword_input) df_z = st.session_state.zendesk_df.copy() if not df_z.empty: # parse & filter dates df_z["Created At"] = pd.to_datetime(df_z["Created At"]) df_z["Updated At"] = pd.to_datetime(df_z["Updated At"]) mask = (df_z["Created At"].dt.date >= start_input) & ( df_z["Created At"].dt.date <= end_input ) df_z = df_z.loc[mask] # sort by Created At descending df_z = df_z.sort_values("Created At", ascending=False) # format timestamps 12-hour df_z["Created At"] = ( df_z["Created At"].dt.strftime("%Y-%m-%d %I:%M %p").str.lower() ) df_z["Updated At"] = ( df_z["Updated At"].dt.strftime("%Y-%m-%d %I:%M %p").str.lower() ) # hyperlink ID base_url = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/agent/tickets" df_z["ID"] = df_z["ID"].apply( lambda x: f'{x}' ) # render fixed-height table including the new summary column html_tbl = df_z.to_html( index=False, escape=False, columns=[ "ID", "Subject", "Status", "Created At", "Updated At", "OpenAI Ticket Summary", ], ) scrollable = f"""