-
Notifications
You must be signed in to change notification settings - Fork 18
feat: Notify customers if newer submitter is available. #274
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
Draft
karthikbekalp
wants to merge
5
commits into
aws-deadline:mainline
Choose a base branch
from
karthikbekalp:updateDialogp
base: mainline
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
88b0bfb
feat: Notify customers if newer submitter is available.
karthikbekalp a661875
chore: Add setting to deactivate notifications.
karthikbekalp aa14afb
chore: Responded to comments from Joel W
karthikbekalp ad4798f
chore: Modify second dialog to redirect to the setup instructions.
karthikbekalp b9f7530
Merge branch 'mainline' into updateDialogp
Cherie-Chen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| """ | ||
| Update checker for the Deadline Cloud for Unreal Engine plugin. | ||
|
|
||
| Checks the GitHub releases API for a newer version and shows an Unreal | ||
| Editor dialog when an update is available. Respects the Deadline Cloud | ||
| ``settings.submitter_update_notification`` config toggle. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import json | ||
| import logging | ||
| import os | ||
| import socket | ||
| import ssl | ||
| import urllib.request | ||
| import urllib.error | ||
| import webbrowser | ||
|
|
||
| import botocore | ||
| import unreal | ||
|
|
||
| from packaging.version import Version, InvalidVersion | ||
|
|
||
| from deadline.client.config import config_file | ||
| from deadline.unreal_submitter._version import version as _current_version | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| GITHUB_LATEST_RELEASE_URL = ( | ||
| "https://api.github.com/repos/aws-deadline/deadline-cloud-for-unreal-engine/releases/latest" | ||
| ) | ||
| RELEASES_PAGE_URL = "https://github.com/aws-deadline/deadline-cloud-for-unreal-engine/releases" | ||
| SETUP_GUIDE_URL = "https://aws-deadline.github.io/unreal-engine/setup-submitter/" | ||
| _REQUEST_TIMEOUT_SECONDS = 5 | ||
|
|
||
|
|
||
| def _is_update_notification_enabled() -> bool: | ||
| """Check whether the user has opted in to update notifications.""" | ||
| return config_file.str2bool(config_file.get_setting("settings.submitter_update_notification")) | ||
|
|
||
|
|
||
| def _get_current_version() -> str: | ||
| """Return the currently installed plugin version string.""" | ||
| return _current_version | ||
|
|
||
|
|
||
| def _fetch_latest_version() -> str | None: | ||
| """Fetch the latest release tag from GitHub. | ||
|
|
||
| Returns: | ||
| The version string (e.g. ``"0.6.5"``) or ``None`` on failure. | ||
| """ | ||
| # Pin to GitHub REST API v3 JSON format so the response shape stays stable. | ||
| req = urllib.request.Request( | ||
| GITHUB_LATEST_RELEASE_URL, | ||
| headers={"Accept": "application/vnd.github.v3+json"}, | ||
| ) | ||
|
|
||
| # Build a strict TLS context: enforce TLS 1.2+ and use the botocore CA | ||
| # bundle so certificate verification works even in embedded Python | ||
| # environments (e.g. Unreal) that may lack system root certificates. | ||
| ctx = ssl.create_default_context(ssl.Purpose.SERVER_AUTH) | ||
| ctx.minimum_version = ssl.TLSVersion.TLSv1_2 | ||
| ctx.load_verify_locations(_get_botocore_ca_bundle()) | ||
|
|
||
| try: | ||
| with urllib.request.urlopen(req, timeout=_REQUEST_TIMEOUT_SECONDS, context=ctx) as resp: | ||
| data = json.loads(resp.read().decode("utf-8")) | ||
| tag = data.get("tag_name", "") | ||
| return tag.lstrip("v") if tag else None | ||
| except urllib.error.URLError: | ||
| return None | ||
| except (socket.timeout, TimeoutError): | ||
| return None | ||
| except json.JSONDecodeError: | ||
| return None | ||
|
|
||
|
|
||
| def _get_botocore_ca_bundle() -> str: | ||
| """Return the path to botocore's bundled CA certificate bundle.""" | ||
| return os.path.join(os.path.dirname(botocore.__file__), "cacert.pem") | ||
|
|
||
|
|
||
| def _is_update_available(current: str, latest: str) -> bool: | ||
| """Return True if *latest* is strictly newer than *current*.""" | ||
| try: | ||
| return Version(latest) > Version(current) | ||
| except InvalidVersion: | ||
| return False | ||
|
|
||
|
|
||
| def safe_check_and_show_update_dialog() -> bool: | ||
| """Check GitHub for a newer release and show an Unreal dialog if found. | ||
|
|
||
| Returns: | ||
| ``True`` if the user chose to open the download page (caller may | ||
| want to skip opening the submitter), ``False`` otherwise. | ||
| """ | ||
| try: | ||
| return _check_and_show_update_dialog() | ||
| except Exception: | ||
| logger.debug("Update check failed -- skipping", exc_info=True) | ||
| return False | ||
|
|
||
|
|
||
| def _check_and_show_update_dialog() -> bool: | ||
| """Internal implementation of the update check and dialog flow.""" | ||
| if not _is_update_notification_enabled(): | ||
| return False | ||
|
|
||
| current_version = _get_current_version() | ||
|
|
||
| latest_version = _fetch_latest_version() | ||
|
|
||
| if not latest_version: | ||
| return False | ||
|
|
||
| if not _is_update_available(current_version, latest_version): | ||
| return False | ||
|
|
||
| message = ( | ||
| f"Version {latest_version} of Deadline Cloud for Unreal Engine " | ||
| f"submitter is now available.\n\n" | ||
| f"Current: {current_version} -> New: {latest_version}\n\n" | ||
| f"View release notes:\n{RELEASES_PAGE_URL}\n\n" | ||
| "To disable these notifications, go to Edit > Project Settings > " | ||
| 'search for "Deadline" and uncheck "Show Submitter Update ' | ||
| 'Notifications" under General Settings.\n\n' | ||
| "Click 'Yes' to open the release page, or 'No' to dismiss." | ||
| ) | ||
|
|
||
| response = unreal.EditorDialog.show_message( | ||
| "New version available", | ||
| message, | ||
| unreal.AppMsgType.YES_NO, | ||
| ) | ||
|
|
||
| if response == unreal.AppReturnType.YES: | ||
| try: | ||
| webbrowser.open(RELEASES_PAGE_URL) | ||
| except Exception: | ||
| return False | ||
|
|
||
| guide_message = ( | ||
| "Please follow the setup guide to install the new release and " | ||
| "then restart Unreal Engine to use the new version.\n\n" | ||
| f"Setup guide:\n{SETUP_GUIDE_URL}\n\n" | ||
| "Click 'Yes' to open the setup guide, or 'No' to dismiss." | ||
| ) | ||
|
|
||
| guide_response = unreal.EditorDialog.show_message( | ||
| "Installation Guide", | ||
| guide_message, | ||
| unreal.AppMsgType.YES_NO, | ||
| ) | ||
|
|
||
| if guide_response == unreal.AppReturnType.YES: | ||
| try: | ||
| webbrowser.open(SETUP_GUIDE_URL) | ||
| except Exception: | ||
| logger.debug("Failed to open setup guide URL", exc_info=True) | ||
| return True | ||
|
|
||
| return False | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
106 changes: 106 additions & 0 deletions
106
.../Source/UnrealDeadlineCloudService/Private/Tests/DeadlinePluginTest_UpdateDialog.spec.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
|
|
||
| #pragma once | ||
| #include "Misc/AutomationTest.h" | ||
| #include "CoreMinimal.h" | ||
| #include "Engine/Engine.h" | ||
| #include "UObject/UObjectGlobals.h" | ||
| #include "DeadlineCloudJobSettings/DeadlineCloudDeveloperSettings.h" | ||
| #include "PythonAPILibraries/DeadlineCloudSettingsLibrary.h" | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // Spec tests for the update dialog flow. | ||
| // | ||
| // The update-notification dialog itself is driven by Python | ||
| // (update_check.py -> unreal.EditorDialog.show_message) and is covered by | ||
| // the Python unit tests in test_update_check.py. | ||
| // | ||
| // These C++ automation tests verify the settings-layer contract that the | ||
| // Python code depends on: | ||
| // - The ShowUpdateNotifications property defaults to true. | ||
| // - The config key "settings.submitter_update_notification" is readable | ||
| // and returns a valid value via the settings library. | ||
| // - Toggling the setting via the UI persists through SaveToFile and | ||
| // is correctly restored by RefreshFromDefaultProfileInternal. | ||
| // --------------------------------------------------------------------------- | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // 1. Default value: a fresh struct should have notifications enabled | ||
| // --------------------------------------------------------------------------- | ||
| IMPLEMENT_SIMPLE_AUTOMATION_TEST( | ||
| FUpdateDialog_SettingDefaultIsTrue, | ||
| "DeadlineCloud.UpdateDialog.Setting.DefaultIsTrue", | ||
| EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) | ||
|
|
||
| bool FUpdateDialog_SettingDefaultIsTrue::RunTest(const FString& Parameters) | ||
| { | ||
| FDeadlineCloudGeneralPluginSettings DefaultGeneral; | ||
| TestTrue( | ||
| TEXT("ShowUpdateNotifications should default to true on a fresh struct"), | ||
| DefaultGeneral.ShowUpdateNotifications); | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| // --------------------------------------------------------------------------- | ||
| // 2. Save-and-reload round-trip through the config file | ||
| // Toggles the setting off, saves to the Deadline Cloud config file via | ||
| // SaveToFile(), reloads via RefreshFromDefaultProfileInternal(), and | ||
| // verifies the bool was correctly persisted as the string "false" and | ||
| // mapped back to false. Then does the same for true. Restores original. | ||
| // --------------------------------------------------------------------------- | ||
| IMPLEMENT_SIMPLE_AUTOMATION_TEST( | ||
| FUpdateDialog_SettingSaveAndReloadRoundTrip, | ||
| "DeadlineCloud.UpdateDialog.Setting.SaveAndReloadRoundTrip", | ||
| EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter) | ||
|
|
||
| bool FUpdateDialog_SettingSaveAndReloadRoundTrip::RunTest(const FString& Parameters) | ||
| { | ||
| UDeadlineCloudDeveloperSettings* Settings = UDeadlineCloudDeveloperSettings::GetMutable(); | ||
| TestNotNull(TEXT("DeveloperSettings singleton must exist"), Settings); | ||
| if (!Settings) return false; | ||
|
|
||
| UDeadlineCloudSettingsLibrary* Library = UDeadlineCloudSettingsLibrary::Get(); | ||
| if (!Library) | ||
| { | ||
| AddWarning(TEXT("DeadlineCloudSettingsLibrary not available (Python not initialized). Skipping round-trip test.")); | ||
| return true; | ||
| } | ||
|
|
||
| const bool bOriginal = Settings->WorkStationConfiguration.General.ShowUpdateNotifications; | ||
| const FString ConfigKey = TEXT("settings.submitter_update_notification"); | ||
|
|
||
| // --- Toggle OFF, save, reload, verify --- | ||
| Settings->WorkStationConfiguration.General.ShowUpdateNotifications = false; | ||
| Settings->SaveToFile(); | ||
|
|
||
| FString SavedValue = Library->GetAWSStringConfigSetting(ConfigKey); | ||
| TestTrue( | ||
| TEXT("Config should contain 'false' after saving with notifications off"), | ||
| SavedValue.Equals(TEXT("false"), ESearchCase::IgnoreCase)); | ||
|
|
||
| Settings->RefreshFromDefaultProfileInternal(); | ||
| TestFalse( | ||
| TEXT("ShowUpdateNotifications should be false after reload"), | ||
| Settings->WorkStationConfiguration.General.ShowUpdateNotifications); | ||
|
|
||
| // --- Toggle ON, save, reload, verify --- | ||
| Settings->WorkStationConfiguration.General.ShowUpdateNotifications = true; | ||
| Settings->SaveToFile(); | ||
|
|
||
| SavedValue = Library->GetAWSStringConfigSetting(ConfigKey); | ||
| TestTrue( | ||
| TEXT("Config should contain 'true' after saving with notifications on"), | ||
| SavedValue.Equals(TEXT("true"), ESearchCase::IgnoreCase)); | ||
|
|
||
| Settings->RefreshFromDefaultProfileInternal(); | ||
| TestTrue( | ||
| TEXT("ShowUpdateNotifications should be true after reload"), | ||
| Settings->WorkStationConfiguration.General.ShowUpdateNotifications); | ||
|
|
||
| // --- Restore original --- | ||
| Settings->WorkStationConfiguration.General.ShowUpdateNotifications = bOriginal; | ||
| Settings->SaveToFile(); | ||
|
|
||
| return true; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.