@@ -6,53 +6,54 @@ format: email
66``` {python}
77#| echo: false
88
9+ import json
910import os
1011import requests
1112import datetime
1213import pandas as pd
13- from great_tables import GT, style, loc, exibble, html
14+ from posit import connect
15+ from great_tables import GT, style, loc, html
16+ from IPython.display import HTML, display
1417
1518# Used to display on-screen setup instructions if environment variables are missing
1619show_instructions = False
1720instructions = []
1821gt_tbl = None
1922
20- # Read CONNECT_SERVER from environment, should be configured automatically when run on Connect
23+ # Read CONNECT_SERVER from environment, this is automatically configured on Connect, set manually for local dev
2124connect_server = os.environ.get("CONNECT_SERVER", "")
2225if not connect_server:
2326 show_instructions = True
2427 instructions.append("Please set the CONNECT_SERVER environment variable.")
2528
26- # Read CONNECT_API_KEY from environment, should be configured automatically when run on Connect
29+ # Read CONNECT_API_KEY from environment, this is automatically configured on Connect, set manually for local dev
2730api_key = os.environ.get("CONNECT_API_KEY", "")
2831if not api_key:
2932 show_instructions = True
3033 instructions.append("Please set the CONNECT_API_KEY environment variable.")
3134
32- # Read CANARY_GUIDS from environment, needs to be manually configured on Connect
33- app_guid_str = os.environ.get("CANARY_GUIDS", "")
34- if not app_guid_str :
35+ # Read CANARY_GUIDS from environment, needs to be manually configured on Connect and for local dev
36+ canary_guids_str = os.environ.get("CANARY_GUIDS", "")
37+ if not canary_guids_str :
3538 show_instructions = True
3639 instructions.append("Please set the CANARY_GUIDS environment variable. It should be a comma separated list of GUID you wish to monitor.")
37- app_guids = []
40+ canary_guids = []
3841else:
3942 # Clean up the GUIDs
40- app_guids = [guid.strip() for guid in app_guid_str .split(',') if guid.strip()]
41- if not app_guids :
43+ canary_guids = [guid.strip() for guid in canary_guids_str .split(',') if guid.strip()]
44+ if not canary_guids :
4245 show_instructions = True
43- instructions.append("CANARY_GUIDS environment variable is empty or contains only whitespace. It should be a comma separated list of GUID you wish to monitor. Raw CANARY_GUIDS value: '{app_guid_str }'")
46+ instructions.append(f "CANARY_GUIDS environment variable is set but is empty or contains only whitespace. It should be a comma separated list of GUID you wish to monitor. Raw CANARY_GUIDS value: '{canary_guids_str }'")
4447
45- if show_instructions:
46- # We'll use this flag later to display instructions instead of results
47- results = []
48- df = pd.DataFrame() # Empty DataFrame
49- check_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
50- else:
51- # Continue with normal execution
48+ # Instantiate a Connect client using posit-sdk where api_key and url are automatically read from our environment vars
49+ client = connect.Client()
50+
51+ if not show_instructions:
52+ # Proceed to validate our monitored GUIDS
5253 # Headers for Connect API
5354 headers = {"Authorization": f"Key {api_key}"}
5455
55- # Check if server is reachable
56+ # Check if server is reachable, would only be a potential problem during local dev
5657 try:
5758 server_check = requests.get(
5859 f"{connect_server}/__ping__",
@@ -64,28 +65,45 @@ else:
6465 raise RuntimeError(f"Connect server at {connect_server} is unavailable: {str(e)}")
6566
6667 # Function to get app details from Connect API
67- def get_app_details (guid):
68+ def get_content (guid):
6869 try:
6970 # Get app details from Connect API
70- app_details_url = f"{connect_server}/__api__/v1/content/{guid}"
71- app_details_response = requests.get(
72- app_details_url,
73- headers=headers,
74- timeout=5
75- )
76- app_details_response.raise_for_status()
77- return app_details_response.json()
78- except Exception:
79- return {"title": "Unknown", "guid": guid}
71+ content = client.content.get(guid)
72+ return content
73+ except Exception as e:
74+ # Initialize default error message
75+ error_message = str(e)
76+
77+ # posit-sdk will return a ClientError if there is a problem getting the guid, parse the error message
78+ if isinstance(e, connect.errors.ClientError):
79+ try:
80+ # ClientError from posit-connect SDK stores error as string that contains JSON
81+ # Convert the string representation to a dict
82+ error_data = json.loads(str(e))
83+ if isinstance(error_data, dict):
84+ # Extract the specific error message
85+ if "error_message" in error_data:
86+ error_message = error_data["error_message"]
87+ elif "error" in error_data:
88+ error_message = error_data["error"]
89+ except json.JSONDecodeError:
90+ # If parsing fails, keep the original error message
91+ pass
92+
93+ # Return content with error in title
94+ return {
95+ "title": f"ERROR: {error_message}",
96+ "guid": guid
97+ }
8098
8199 # Function to validate app health (simple HTTP 200 check)
82100 def validate_app(guid):
83101 # Get app details
84- app_details = get_app_details (guid)
85- app_name = app_details .get("title", "Unknown")
102+ content = get_content (guid)
103+ app_name = content .get("title", "Unknown")
86104
87105 # Extract content_url if available
88- dashboard_url = app_details .get("dashboard_url", "")
106+ dashboard_url = content .get("dashboard_url", "")
89107
90108 try:
91109 app_url = f"{connect_server}/content/{guid}"
@@ -115,7 +133,7 @@ else:
115133
116134 # Check all apps and collect results
117135 results = []
118- for guid in app_guids :
136+ for guid in canary_guids :
119137 results.append(validate_app(guid))
120138
121139 # Convert results to DataFrame for easy display
@@ -147,6 +165,17 @@ else:
147165# Create a table with basic styling
148166if not show_instructions and not df.empty:
149167
168+ # Format the canary_guids as a string for display
169+ canary_guids_str = ", ".join(canary_guids)
170+
171+ # Use HTML to create a callout box
172+ display(HTML(f"""
173+ <div style="border: 1px solid #ccc; border-radius: 8px; padding: 10px; margin-bottom: 15px; background-color: #f8f9fa;">
174+ <div style="margin-top: 0; padding-bottom: 8px; border-bottom: 1px solid #eaecef; font-weight: bold; font-size: 1.2em;">Monitored GUIDs</div>
175+ <div style="padding: 5px 0;">{canary_guids_str}</div>
176+ </div>
177+ """))
178+
150179 # First create links for name and guid columns
151180 df_display = df.copy()
152181
@@ -174,20 +203,43 @@ if not show_instructions and not df.empty:
174203 style.fill("red"),
175204 locations=loc.body(columns="status", rows=lambda df: df["status"] == "FAIL")
176205 ))
177-
178- # Display instructions if setup failed
179- if show_instructions:
180- # Create a DataFrame with instructions
181- instructions_df = pd.DataFrame({
182- "Setup has failed": instructions
183- })
206+ elif show_instructions:
207+ # Create a callout box for instructions
208+ instructions_html = ""
209+ for instruction in instructions:
210+ instructions_html += f"<div style='margin-bottom: 10px;'>{instruction}</div>"
184211
185- # Create a GT table for instructions
186- gt_tbl = GT(instructions_df)
187- gt_tbl = (gt_tbl
188- .tab_source_note(
189- source_note=html("See Posit Connect documentation for <a href='https://docs.posit.co/connect/user/content-settings/#content-vars' target='_blank'>Vars (environment variables)</a>")
190- ))
212+ display(HTML(f"""
213+ <div style="border: 1px solid #cc0000; border-radius: 8px; padding: 10px; margin-bottom: 15px; background-color: #fff8f8;">
214+ <div style="margin-top: 0; padding-bottom: 8px; border-bottom: 1px solid #eaecef; color: #cc0000; font-weight: bold; font-size: 1.2em;">⚠️ Setup Instructions</div>
215+ {instructions_html}
216+ <div style="padding-top: 8px; font-size: 0.9em; border-top: 1px solid #eaecef;">
217+ See Posit Connect documentation for <a href='https://docs.posit.co/connect/user/content-settings/#content-vars' target='_blank'>Vars (environment variables)</a>
218+ </div>
219+ </div>
220+ """))
221+
222+ # Set gt_tbl to None since we're using HTML display instead
223+ gt_tbl = None
224+ else:
225+ # We should only hit this catchall if the dataframe is empty (a likely error) and there are no instructions
226+ display(HTML(f"""
227+ <div style="border: 1px solid #f0ad4e; border-radius: 8px; padding: 10px; margin-bottom: 15px; background-color: #fcf8e3;">
228+ <div style="margin-top: 0; padding-bottom: 8px; border-bottom: 1px solid #eaecef; color: #cc0000; font-weight: bold; font-size: 1.2em;">⚠️ No results available</div>
229+ <div style="padding: 5px 0;">
230+ No monitoring results were found. This could be because:
231+ <ul>
232+ <li>No valid GUIDs were provided</li>
233+ <li>There was an issue connecting to the specified content</li>
234+ <li>The environment is properly configured but there was an error that caused no data to be returned</li>
235+ </ul>
236+ <p>Please check your CANARY_GUIDS environment variable and ensure it contains valid content identifiers.</p>
237+ </div>
238+ </div>
239+ """))
240+
241+ # Set gt_tbl to None since we're using HTML display instead
242+ gt_tbl = None
191243
192244# Compute if we should send an email, only send if at least one app has a failure
193245if 'df' in locals() and 'status' in df.columns:
0 commit comments