-
Notifications
You must be signed in to change notification settings - Fork 2
Initial app-canary #156
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Initial app-canary #156
Conversation
@@ -0,0 +1,112 @@ | |||
# requirements.txt generated by rsconnect-python on 2025-05-29 18:04:33.689762+00:00 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind trying to remove this and use Publisher's scan instead? I suspect that will make a much more limited requirements.txt
Also, we should consider not pinning every single dependency here. Floating versions so long as they are compatible will mean less maintenance burden over time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A bit more context on why requirements.txt generated by rsconnect-python aren't necessarily what we want: posit-dev/rsconnect-python#538
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind trying to remove this and use Publisher's scan instead? I suspect that will make a much more limited requirements.txt
I'm definitely having issues with rsconnect-python
with this extension. It does not seem to be scanning the qmd for requirements and building a requirements.txt
with just the used packages like I believe it should. Most likely user error as it does not appear I had this same issue with other Python extensions.
Also, we should consider not pinning every single dependency here. Floating versions so long as they are compatible will mean less maintenance burden over time.
This probably requires more discussion and education on my end. Both rsconnect
and Publisher scan currently create tightly pinned requirements.txt
with no obvious way to control that. And all of the current extensions in the repo appear to have this same concern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably requires more discussion and education on my end. Both rsconnect and Publisher scan currently create tightly pinned requirements.txt with no obvious way to control that. And all of the current extensions in the repo appear to have this same concern.
Yeah, this has been a common pattern, but one we need to step away from. Pinning everything to exactly one version means that if any of these have upgrades with security fixes we will need to issue a new release bumping the pin. If we have something like >x.y.z
or no version specification at all, folks will get fixed versions automatically without us doing anything.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you mind doing this, please?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a full review, since this is a draft.
I don't actually know how Quarto dashboards work! Do they run in-browser via web assembly or use some other framework to run? My initial thought was, "maybe there's a better framework", but actually I think it might be a great choice — good to cover different types of content that Connect supports!
Setting the GUIDs to track via an environment variable kind of reveals the lack of mutable local storage for content, but it seems like a good solution! Plus, in a future iteration, you could build UI to manage GUIDs by settings its environment variable via the SDK.
Oops, I didn't mean to hit "approve", meant to hit "comment". |
7ca3a77
to
a90ab3c
Compare
Quarto was chosen so we can leverage the Connect scheduler feature, which as one of the requirements.
Agreed, my assumption is a UI-based way to manage the GUID is likely going to be the first thing a user would request. It's not a good experiencing doing it manually via env vars. |
Spot on, @cgraham-rs for a bit more context: this looks like a standard (rendered) quarto document / "report" what you have here (which is great, possibly even just fine / exactly what we need!) there is a thing known as a "Quarto Dashboard" which is very similar to a rendered quarto document, but presents information with cards in a view that is much more reminiscent of a dashboard. But under the hood these are all rendered types on Connect, where connect does ~ |
Shall we review and merge this so we can enjoy tiny follow-on PR reviews? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a Quarto expert and I might be missing conventions, but here I have some suggestions:
There are many if
blocks to add env vars instructions when needed, I think you could have a method looping through the environment vars required, re-structuring these a bit e.g:
required_env_vars = ["CONNECT_SERVER", "CONNECT_API_KEY", "CANARY_GUIDS"]
def check_required_env():
env_values = []
for reqenv in required_env_vars:
env_value = os.environ.get(reqenv, "")
if not env_value:
show_instructions = True
instructions.append(f"Please set the {reqenv} environment variable.")
else:
env_vars.append(env_value)
return env_values
def unpack_guids(guids_str):
guids = [guid.strip() for guid in app_guid_str.split(',') if guid.strip()]
if not guids:
show_instructions = True
instructions.append("CANARY_GUIDS should be a comma separated list with content GUIDs you wish to monitor.")
return guids
connect_server, api_key, app_guids_str = check_required_env()
app_guids = unpack_guids(app_guids_str)
if show_instructions:
# We'll use this flag later to display instructions instead of results
results = []
df = pd.DataFrame() # Empty DataFrame
check_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
else:
prep_apps_status()
Notice above in my suggestion the prep_apps_status()
func, I'm not a fan of having big chunks of code within if|else
blocks, so many things that you have within the else
block when all env vars are ok, can be taken out. e.g:
def server_check():
# Headers for Connect API
headers = {"Authorization": f"Key {api_key}"}
# Check if server is reachable
try:
server_check = requests.get(
f"{connect_server}/__ping__",
headers=headers,
timeout=5
)
server_check.raise_for_status()
except requests.exceptions.RequestException as e:
raise RuntimeError(f"Connect server at {connect_server} is unavailable: {str(e)}")
def get_app_details():
...
def validate_app():
...
def prep_apps_status():
server_check()
# Check all apps and collect results
results = []
for guid in app_guids:
results.append(validate_app(guid))
...
Similarly, for the second python block, you could have methods like render_instructions()
and render_apps_table()
.
In general, my suggestion would be to leverage a bit more on functions to have smaller units of code responsible of one thing at a time, splitting in functions will also help in the future to maintain or update as needed by quickly noticing which function is responsible of xyz.
Screenshot from deployment to Connect
When the required Var is not set
Email report when any content has failed.