github-actions[bot] commited on
Commit
9ca48e9
·
1 Parent(s): 21107d3

sync: automatic content update from github

Browse files
Files changed (6) hide show
  1. README.md +10 -4
  2. app.py +253 -0
  3. .gitattributes → gitattributes +0 -0
  4. index.html +0 -19
  5. requirements.txt +5 -0
  6. style.css +0 -28
README.md CHANGED
@@ -1,10 +1,16 @@
1
  ---
2
  title: Partner Management
3
- emoji: 📊
4
- colorFrom: pink
5
- colorTo: blue
6
- sdk: static
 
 
7
  pinned: false
8
  ---
9
 
 
 
 
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
  title: Partner Management
3
+ emoji: 🦀
4
+ colorFrom: purple
5
+ colorTo: pink
6
+ sdk: streamlit
7
+ sdk_version: 1.45.0
8
+ app_file: app.py
9
  pinned: false
10
  ---
11
 
12
+ This dashboard surfaces support tickets for partner management teams. Use the
13
+ "Zendesk Lookup" and "Jira Lookup" tabs to search by site, keyword, and date
14
+ range with filters scoped to each tab.
15
+
16
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import html
3
+ import streamlit as st
4
+ import pandas as pd
5
+ from atlassian import Jira
6
+ import requests
7
+ from openai import OpenAI
8
+ from datetime import date, timedelta
9
+
10
+ # -------------------------
11
+ # Environment-based secrets
12
+ # -------------------------
13
+ JIRA_URL = os.getenv("JIRA_URL")
14
+ JIRA_USERNAME = os.getenv("JIRA_USERNAME")
15
+ JIRA_API_TOKEN = os.getenv("JIRA_API_TOKEN")
16
+
17
+ ZENDESK_EMAIL = os.getenv("ZENDESK_EMAIL")
18
+ ZENDESK_SUBDOMAIN = os.getenv("ZENDESK_SUBDOMAIN")
19
+ ZENDESK_API_KEY = os.getenv("ZENDESK_API_KEY")
20
+
21
+ OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
22
+ client = OpenAI(api_key=OPENAI_API_KEY)
23
+
24
+ # -------------------------
25
+ # JIRA Client
26
+ # -------------------------
27
+ jira = Jira(url=JIRA_URL, username=JIRA_USERNAME, password=JIRA_API_TOKEN)
28
+
29
+
30
+ # -------------------------
31
+ # OpenAI Summarization
32
+ # -------------------------
33
+ @st.cache_data(show_spinner=False)
34
+ def summarize_ticket(text: str) -> str:
35
+ if not text:
36
+ return "No description"
37
+ prompt = (
38
+ "Summarize this Zendesk ticket in 1–3 sentences:\n\n" + text + "\n\nSummary:"
39
+ )
40
+ resp = client.chat.completions.create(
41
+ model="gpt-4o-mini",
42
+ messages=[{"role": "user", "content": prompt}],
43
+ temperature=0.3,
44
+ max_tokens=150,
45
+ )
46
+ return resp.choices[0].message.content.strip()
47
+
48
+
49
+ # -------------------------
50
+ # Zendesk Search Function
51
+ # -------------------------
52
+ def search_zendesk_tickets(site_name: str, keyword: str) -> pd.DataFrame:
53
+ terms = []
54
+ if site_name:
55
+ terms.append(f'"{site_name}"')
56
+ if keyword:
57
+ terms.append(f'"{keyword}"')
58
+ query_str = " ".join(terms)
59
+
60
+ url = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/api/v2/search.json"
61
+ params = {"query": f"type:ticket {query_str}", "include": "users"}
62
+ auth = (f"{ZENDESK_EMAIL}/token", ZENDESK_API_KEY)
63
+ resp = requests.get(url, auth=auth, params=params)
64
+ if not resp.ok:
65
+ st.error(f"Zendesk error {resp.status_code}")
66
+ return pd.DataFrame()
67
+
68
+ tickets = resp.json().get("results", [])
69
+ rows = []
70
+ for t in tickets:
71
+ rows.append(
72
+ {
73
+ "ID": t["id"],
74
+ "Subject": html.escape(t.get("subject", "")),
75
+ "Status": html.escape(t.get("status", "")),
76
+ "Created At": t.get("created_at", ""),
77
+ "Updated At": t.get("updated_at", ""),
78
+ "Description": t.get("description", ""), # keep for summary
79
+ }
80
+ )
81
+ df = pd.DataFrame(rows)
82
+
83
+ # generate summaries and attach as new column
84
+ df["OpenAI Ticket Summary"] = df["Description"].apply(summarize_ticket)
85
+ return df
86
+
87
+
88
+ # -------------------------
89
+ # Jira Search Function
90
+ # -------------------------
91
+
92
+
93
+ @st.cache_data(show_spinner=False)
94
+ def search_jira_issues(
95
+ site_name: str, keyword: str, start_date: date, end_date: date
96
+ ) -> pd.DataFrame:
97
+ # Build JQL clauses
98
+ clauses = []
99
+ if site_name:
100
+ clauses.append(f'text ~ "{site_name}"')
101
+ if keyword:
102
+ clauses.append(f'text ~ "{keyword}"')
103
+ clauses.append(f'created >= "{start_date.isoformat()}"')
104
+ clauses.append(f'created <= "{end_date.isoformat()}"')
105
+ jql = " AND ".join(clauses)
106
+
107
+ # Execute the JQL query, limiting to 100 issues
108
+ resp = jira.jql(jql, limit=100)
109
+ issues = resp.get("issues", [])
110
+
111
+ rows = []
112
+ for issue in issues:
113
+ f = issue["fields"]
114
+ rows.append(
115
+ {
116
+ "Key": issue["key"],
117
+ "Summary": html.escape(f.get("summary", "")),
118
+ "Status": html.escape(f.get("status", {}).get("name", "")),
119
+ "Created At": f.get("created", ""),
120
+ "Updated At": f.get("updated", ""),
121
+ }
122
+ )
123
+
124
+ return pd.DataFrame(rows)
125
+
126
+
127
+ # -------------------------
128
+ # App Config
129
+ # -------------------------
130
+ st.set_page_config(layout="wide")
131
+ st.title("Unified Support Dashboard")
132
+
133
+ if "zendesk_df" not in st.session_state:
134
+ st.session_state.zendesk_df = pd.DataFrame()
135
+ if "jira_df" not in st.session_state:
136
+ st.session_state.jira_df = pd.DataFrame()
137
+
138
+ # -------------------------
139
+ # Main: Tabs
140
+ # -------------------------
141
+ tabs = st.tabs(["Zendesk Lookup", "Jira Lookup"])
142
+
143
+ # ---- Tab 1: Zendesk ----
144
+ with tabs[0]:
145
+ st.header("Zendesk Lookup")
146
+ site_input = st.text_input(
147
+ "Site Name", placeholder="example.com", key="zendesk_site"
148
+ )
149
+ keyword_input = st.text_input(
150
+ "Keyword", placeholder="timeout", key="zendesk_keyword"
151
+ )
152
+ start_input = st.date_input(
153
+ "Created After", value=date.today() - timedelta(days=7), key="zendesk_start"
154
+ )
155
+ end_input = st.date_input("Created Before", value=date.today(), key="zendesk_end")
156
+ if st.button("Search Zendesk Tickets", key="zendesk_search"):
157
+ st.session_state.zendesk_df = search_zendesk_tickets(site_input, keyword_input)
158
+
159
+ df_z = st.session_state.zendesk_df.copy()
160
+ if not df_z.empty:
161
+ # parse & filter dates
162
+ df_z["Created At"] = pd.to_datetime(df_z["Created At"])
163
+ df_z["Updated At"] = pd.to_datetime(df_z["Updated At"])
164
+ mask = (df_z["Created At"].dt.date >= start_input) & (
165
+ df_z["Created At"].dt.date <= end_input
166
+ )
167
+ df_z = df_z.loc[mask]
168
+
169
+ # sort by Created At descending
170
+ df_z = df_z.sort_values("Created At", ascending=False)
171
+
172
+ # format timestamps 12-hour
173
+ df_z["Created At"] = (
174
+ df_z["Created At"].dt.strftime("%Y-%m-%d %I:%M %p").str.lower()
175
+ )
176
+ df_z["Updated At"] = (
177
+ df_z["Updated At"].dt.strftime("%Y-%m-%d %I:%M %p").str.lower()
178
+ )
179
+
180
+ # hyperlink ID
181
+ base_url = f"https://{ZENDESK_SUBDOMAIN}.zendesk.com/agent/tickets"
182
+ df_z["ID"] = df_z["ID"].apply(
183
+ lambda x: f'<a href="{base_url}/{x}" target="_blank">{x}</a>'
184
+ )
185
+
186
+ # render fixed-height table including the new summary column
187
+ html_tbl = df_z.to_html(
188
+ index=False,
189
+ escape=False,
190
+ columns=[
191
+ "ID",
192
+ "Subject",
193
+ "Status",
194
+ "Created At",
195
+ "Updated At",
196
+ "OpenAI Ticket Summary",
197
+ ],
198
+ )
199
+ scrollable = f"""
200
+ <div style="height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 4px;">
201
+ {html_tbl}
202
+ </div>
203
+ """
204
+ st.markdown(scrollable, unsafe_allow_html=True)
205
+
206
+ # ---- Tab 2: Jira ----
207
+ with tabs[1]:
208
+ st.header("Jira Lookup")
209
+ site_input = st.text_input("Site Name", placeholder="example.com", key="jira_site")
210
+ keyword_input = st.text_input("Keyword", placeholder="timeout", key="jira_keyword")
211
+ start_input = st.date_input(
212
+ "Created After", value=date.today() - timedelta(days=7), key="jira_start"
213
+ )
214
+ end_input = st.date_input("Created Before", value=date.today(), key="jira_end")
215
+ if st.button("Search Jira Issues", key="jira_search"):
216
+ st.session_state.jira_df = search_jira_issues(
217
+ site_input, keyword_input, start_input, end_input
218
+ )
219
+
220
+ df_j = st.session_state.jira_df.copy()
221
+
222
+ if not df_j.empty:
223
+ # parse & sort by Created At descending
224
+ df_j["Created At"] = pd.to_datetime(df_j["Created At"])
225
+ df_j["Updated At"] = pd.to_datetime(df_j["Updated At"])
226
+ df_j = df_j.sort_values("Created At", ascending=False)
227
+
228
+ # 12-hour fmt with am/pm
229
+ df_j["Created At"] = (
230
+ df_j["Created At"].dt.strftime("%Y-%m-%d %I:%M %p").str.lower()
231
+ )
232
+ df_j["Updated At"] = (
233
+ df_j["Updated At"].dt.strftime("%Y-%m-%d %I:%M %p").str.lower()
234
+ )
235
+
236
+ # hyperlink the key to the JIRA issue
237
+ base_jira = JIRA_URL.rstrip("/")
238
+ df_j["Key"] = df_j["Key"].apply(
239
+ lambda k: f'<a href="{base_jira}/browse/{k}" target="_blank">{k}</a>'
240
+ )
241
+
242
+ # render as fixed-height, scrollable HTML table
243
+ html_tbl = df_j.to_html(
244
+ index=False,
245
+ escape=False,
246
+ columns=["Key", "Summary", "Status", "Created At", "Updated At"],
247
+ )
248
+ scrollable = f"""
249
+ <div style="height: 400px; overflow-y: auto; border: 1px solid #ddd; padding: 4px;">
250
+ {html_tbl}
251
+ </div>
252
+ """
253
+ st.markdown(scrollable, unsafe_allow_html=True)
.gitattributes → gitattributes RENAMED
File without changes
index.html DELETED
@@ -1,19 +0,0 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ atlassian-python-api
2
+ openai
3
+ pandas
4
+ requests
5
+ streamlit
style.css DELETED
@@ -1,28 +0,0 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
4
- }
5
-
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
9
- }
10
-
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
16
- }
17
-
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
24
- }
25
-
26
- .card p:last-child {
27
- margin-bottom: 0;
28
- }