Skip to content

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

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
Open

Initial app-canary #156

wants to merge 10 commits into from

Conversation

cgraham-rs
Copy link
Contributor

@cgraham-rs cgraham-rs commented May 29, 2025

Screenshot from deployment to Connect

image

When the required Var is not set

image

Email report when any content has failed.

image

@@ -0,0 +1,112 @@
# requirements.txt generated by rsconnect-python on 2025-05-29 18:04:33.689762+00:00
Copy link
Collaborator

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.

Copy link
Collaborator

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

Copy link
Contributor Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator

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?

Copy link
Collaborator

@toph-allen toph-allen left a 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.

@toph-allen
Copy link
Collaborator

Oops, I didn't mean to hit "approve", meant to hit "comment".

@toph-allen toph-allen self-requested a review May 30, 2025 20:46
@cgraham-rs cgraham-rs force-pushed the cgraham/app-canary branch from 7ca3a77 to a90ab3c Compare June 3, 2025 14:06
@cgraham-rs cgraham-rs marked this pull request as ready for review June 4, 2025 19:24
@cgraham-rs
Copy link
Contributor Author

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!

Quarto was chosen so we can leverage the Connect scheduler feature, which as one of the requirements.

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.

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.

@jonkeane
Copy link
Collaborator

jonkeane commented Jun 6, 2025

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!

Quarto was chosen so we can leverage the Connect scheduler feature, which as one of the requirements.

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 ~ quarto render and then it displays the output of that. There isn't any magical WASM or anything like that here (though, technically, one could add a WASM chunk to a quarto doc and then it would be that!)

@cgraham-rs
Copy link
Contributor Author

Shall we review and merge this so we can enjoy tiny follow-on PR reviews?

Copy link
Contributor

@marcosnav marcosnav left a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants