From bf29b27937f4f1ff0828a6b49859e7e0570b685e Mon Sep 17 00:00:00 2001 From: amzn-hdoke <61443753+hdoke@users.noreply.github.com> Date: Fri, 4 Jun 2021 20:48:35 -0700 Subject: [PATCH] Add AWSAttribution feature (#1164) * LYN-3601: Provide skeleton classes for AWS Attribution (#31) Provide skeleton classes for AWS Attribution, along with some basic unit tests * Add AWS Attribution UI and settings (#56) * Adding AWS Attributions UX and corresponding editor preference s setting * Fix serialized field description * Fixed update frequency to be a day * Handling editor startup with default values for AWSAttribution * Add missing header and remove AWSCoreSystemComponentMock fron test * Generate and post AWSAttribution metric (#69) * Adding AWS Attribution Api service job * Adding support for config endpoint override * Update Api endpoint formatting, fix default region * Remove extra header * Fixes for link issues * Fix Unittest namespace * Instantiating AWSAttributionSystemComponent in AWS.Editor module * Update AttributionMetric with engine version and AWS enabled gems (#77) * Update AttributionMetric with engine version and AWS enabled gems * Fix warnings * Undoing accidental change * Saving level PrefabLevel_OpensLevelWithEntities * Remove overriding editorprefrences.setreg * Revert "Saving level PrefabLevel_OpensLevelWithEntities" This reverts commit 529af70c55ece70fc6bc29ceb83bef60413713a3. * Move AWS preferences to its own temp settings file * Undo accidental file add * Add missing string params in warning messages Co-authored-by: Pip Potter <61438964+lmbr-pip@users.noreply.github.com> --- .../Editor/EditorPreferencesDialog.cpp | 2 + .../Editor/EditorPreferencesPageAWS.cpp | 151 +++++++ .../Sandbox/Editor/EditorPreferencesPageAWS.h | 60 +++ Code/Sandbox/Editor/MainWindow.qrc | 1 + Code/Sandbox/Editor/PreferencesStdPages.cpp | 8 + Code/Sandbox/Editor/editor_lib_files.cmake | 2 + .../Editor/res/AWS_preferences_icon.svg | 3 + Gems/AWSCore/Code/CMakeLists.txt | 4 +- .../Include/Private/AWSCoreEditorModule.h | 2 +- .../Attribution/AWSAttributionServiceApi.h | 71 +++ .../Attribution/AWSCoreAttributionConstant.h | 25 ++ .../Attribution/AWSCoreAttributionManager.h | 53 +++ .../Attribution/AWSCoreAttributionMetric.h | 62 +++ .../AWSCoreAttributionSystemComponent.h | 50 +++ .../Public/Framework/ServiceClientJobConfig.h | 6 + .../Code/Source/AWSCoreEditorModule.cpp | 5 +- .../Attribution/AWSAttributionServiceApi.cpp | 45 ++ .../Attribution/AWSCoreAttributionManager.cpp | 275 ++++++++++++ .../Attribution/AWSCoreAttributionMetric.cpp | 106 +++++ .../AWSCoreAttributionSystemComponent.cpp | 81 ++++ .../AWSAttributionServiceApiTest.cpp | 85 ++++ .../AWSCoreAttributionManagerTest.cpp | 414 ++++++++++++++++++ .../AWSCoreAttributionMetricTest.cpp | 50 +++ .../AWSCoreAttributionSystemComponentTest.cpp | 132 ++++++ .../Code/Tests/TestFramework/AWSCoreFixture.h | 31 +- Gems/AWSCore/Code/awscore_editor_files.cmake | 9 + .../Code/awscore_editor_tests_files.cmake | 4 + 27 files changed, 1732 insertions(+), 5 deletions(-) create mode 100644 Code/Sandbox/Editor/EditorPreferencesPageAWS.cpp create mode 100644 Code/Sandbox/Editor/EditorPreferencesPageAWS.h create mode 100644 Code/Sandbox/Editor/res/AWS_preferences_icon.svg create mode 100644 Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSAttributionServiceApi.h create mode 100644 Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h create mode 100644 Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionManager.h create mode 100644 Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionMetric.h create mode 100644 Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionSystemComponent.h create mode 100644 Gems/AWSCore/Code/Source/Editor/Attribution/AWSAttributionServiceApi.cpp create mode 100644 Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp create mode 100644 Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp create mode 100644 Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionSystemComponent.cpp create mode 100644 Gems/AWSCore/Code/Tests/Editor/Attribution/AWSAttributionServiceApiTest.cpp create mode 100644 Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionManagerTest.cpp create mode 100644 Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionMetricTest.cpp create mode 100644 Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionSystemComponentTest.cpp diff --git a/Code/Sandbox/Editor/EditorPreferencesDialog.cpp b/Code/Sandbox/Editor/EditorPreferencesDialog.cpp index e5bcfd6bad..679c73e7df 100644 --- a/Code/Sandbox/Editor/EditorPreferencesDialog.cpp +++ b/Code/Sandbox/Editor/EditorPreferencesDialog.cpp @@ -35,6 +35,7 @@ #include "EditorPreferencesPageViewportMovement.h" #include "EditorPreferencesPageViewportDebug.h" #include "EditorPreferencesPageExperimentalLighting.h" +#include "EditorPreferencesPageAWS.h" #include "LyViewPaneNames.h" AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING @@ -72,6 +73,7 @@ EditorPreferencesDialog::EditorPreferencesDialog(QWidget* pParent) CEditorPreferencesPage_ViewportMovement::Reflect(*serializeContext); CEditorPreferencesPage_ViewportDebug::Reflect(*serializeContext); CEditorPreferencesPage_ExperimentalLighting::Reflect(*serializeContext); + CEditorPreferencesPage_AWS::Reflect(*serializeContext); } } diff --git a/Code/Sandbox/Editor/EditorPreferencesPageAWS.cpp b/Code/Sandbox/Editor/EditorPreferencesPageAWS.cpp new file mode 100644 index 0000000000..edaf813cd8 --- /dev/null +++ b/Code/Sandbox/Editor/EditorPreferencesPageAWS.cpp @@ -0,0 +1,151 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#include "EditorDefs.h" + +#include "EditorPreferencesPageAWS.h" + +// AzCore +#include +#include +#include + +void CEditorPreferencesPage_AWS::Reflect(AZ::SerializeContext& serialize) +{ + serialize.Class() + ->Version(1) + ->Field("AWSAttributionEnabled", &UsageOptions::m_awsAttributionEnabled); + + serialize.Class() + ->Version(1) + ->Field("UsageOptions", &CEditorPreferencesPage_AWS::m_usageOptions); + + AZ::EditContext* editContext = serialize.GetEditContext(); + if (editContext) + { + editContext->Class("Options", "") + ->DataElement(AZ::Edit::UIHandlers::CheckBox, &UsageOptions::m_awsAttributionEnabled, "Send Metrics usage to AWS", + "Reports Gem usage to AWS on Editor launch"); + + editContext->Class("AWS Preferences", "AWS Preferences") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::Visibility, AZ_CRC("PropertyVisibility_ShowChildrenOnly", 0xef428f20)) + ->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_AWS::m_usageOptions, "AWS Usage Data", "AWS Usage Options"); + } +} + + +CEditorPreferencesPage_AWS::CEditorPreferencesPage_AWS() +{ + m_settingsRegistry = AZStd::make_unique(); + InitializeSettings(); + + // TODO Update with AWS svg. + m_icon = QIcon(":/res/AWS_preferences_icon.svg"); +} + +CEditorPreferencesPage_AWS::~CEditorPreferencesPage_AWS() +{ + m_settingsRegistry.reset(); +} + +const char* CEditorPreferencesPage_AWS::GetTitle() +{ + return "AWS"; +} + +QIcon& CEditorPreferencesPage_AWS::GetIcon() +{ + return m_icon; +} + +void CEditorPreferencesPage_AWS::OnApply() +{ + m_settingsRegistry->Set(AWSAttributionEnabledKey, m_usageOptions.m_awsAttributionEnabled); + SaveSettingsRegistryFile(); +} + +const CEditorPreferencesPage_AWS::UsageOptions& CEditorPreferencesPage_AWS::GetUsageOptions() +{ + return m_usageOptions; +} + +void CEditorPreferencesPage_AWS::SaveSettingsRegistryFile() +{ + AZ::Job* job = AZ::CreateJobFunction( + [this]() + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "File IO is not initialized."); + + // Resolve path to editor_aws_preferences.setreg + AZStd::string editorPreferencesFilePath = + AZStd::string::format("@user@/%s/%s", AZ::SettingsRegistryInterface::RegistryFolder, EditorAWSPreferencesFileName); + AZStd::array resolvedPath{}; + fileIO->ResolvePath(editorPreferencesFilePath.c_str(), resolvedPath.data(), resolvedPath.size()); + + AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings; + dumperSettings.m_prettifyOutput = true; + dumperSettings.m_jsonPointerPrefix = AWSAttributionSettingsPrefixKey; + + AZStd::string stringBuffer; + AZ::IO::ByteContainerStream stringStream(&stringBuffer); + if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream( + *m_settingsRegistry, AWSAttributionSettingsPrefixKey, stringStream, dumperSettings)) + { + AZ_Warning( + "AWSAttributionManager", false, R"(Unable to save changes to the Editor AWS Preferences registry file at "%s"\n)", + resolvedPath.data()); + return; + } + + bool saved{}; + constexpr auto configurationMode = + AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; + if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPath.data(), configurationMode)) + { + saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size(); + } + + AZ_Warning( + "AWSAttributionManager", saved, R"(Unable to save Editor AWS Preferences registry file to path "%s"\n)", + editorPreferencesFilePath.c_str()); + }, + true); + job->Start(); +} + +void CEditorPreferencesPage_AWS::InitializeSettings() +{ + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "File IO is not initialized."); + + // Resolve path to editor_aws_preferences.setreg + AZStd::string editorAWSPreferencesFilePath = + AZStd::string::format("@user@/%s/%s", AZ::SettingsRegistryInterface::RegistryFolder, EditorAWSPreferencesFileName); + AZStd::array resolvedPathAWSPreference{}; + if (!fileIO->ResolvePath(editorAWSPreferencesFilePath.c_str(), resolvedPathAWSPreference.data(), resolvedPathAWSPreference.size())) + { + AZ_Warning("AWSAttributionManager", false, "Error resolving path %s", resolvedPathAWSPreference.data()); + return; + } + + if (fileIO->Exists(resolvedPathAWSPreference.data())) + { + m_settingsRegistry->MergeSettingsFile(resolvedPathAWSPreference.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + } + + if (!m_settingsRegistry->Get(m_usageOptions.m_awsAttributionEnabled, AWSAttributionEnabledKey)) + { + // If key is missing default to on. + m_usageOptions.m_awsAttributionEnabled = true; + } +} diff --git a/Code/Sandbox/Editor/EditorPreferencesPageAWS.h b/Code/Sandbox/Editor/EditorPreferencesPageAWS.h new file mode 100644 index 0000000000..b0dc7ee740 --- /dev/null +++ b/Code/Sandbox/Editor/EditorPreferencesPageAWS.h @@ -0,0 +1,60 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ +#pragma once + +#include "Include/IPreferencesPage.h" +#include +#include +#include +#include + +class CEditorPreferencesPage_AWS + : public IPreferencesPage +{ +public: + AZ_RTTI(CEditorPreferencesPage_AWS, "{51FB9557-ABA3-4FD7-803A-1784F5B06F5F}", IPreferencesPage) + + static void Reflect(AZ::SerializeContext& serialize); + + CEditorPreferencesPage_AWS(); + virtual ~CEditorPreferencesPage_AWS(); + + // IPreferencesPage interface methods. + virtual const char* GetCategory() override { return "AWS"; } + virtual const char* GetTitle() override; + virtual QIcon& GetIcon() override; + virtual void OnApply() override; + virtual void OnCancel() override {} + virtual bool OnQueryCancel() override { return true; } + +protected: + struct UsageOptions + { + AZ_TYPE_INFO(UsageOptions, "{2B7D9B19-D13B-4E54-B724-B2FD8D0828B3}") + + bool m_awsAttributionEnabled; + }; + + const UsageOptions& GetUsageOptions(); + +private: + void InitializeSettings(); + void SaveSettingsRegistryFile(); + UsageOptions m_usageOptions; + QIcon m_icon; + AZStd::unique_ptr m_settingsRegistry; + + static constexpr char AWSAttributionEnabledKey[] = "/Amazon/AWS/Preferences/AWSAttributionEnabled"; + static constexpr char EditorPreferencesFileName[] = "editorpreferences.setreg"; + static constexpr char EditorAWSPreferencesFileName[] = "editor_aws_preferences.setreg"; + static constexpr char AWSAttributionSettingsPrefixKey[] = "/Amazon/AWS/Preferences"; +}; diff --git a/Code/Sandbox/Editor/MainWindow.qrc b/Code/Sandbox/Editor/MainWindow.qrc index 476159fbfd..fd207ebe13 100644 --- a/Code/Sandbox/Editor/MainWindow.qrc +++ b/Code/Sandbox/Editor/MainWindow.qrc @@ -143,6 +143,7 @@ res/Camera.svg res/Debug.svg res/Experimental.svg + res/AWS_preferences_icon.svg res/Files.svg res/Gizmos.svg res/Global.svg diff --git a/Code/Sandbox/Editor/PreferencesStdPages.cpp b/Code/Sandbox/Editor/PreferencesStdPages.cpp index 032b01e353..3a66d6e3f4 100644 --- a/Code/Sandbox/Editor/PreferencesStdPages.cpp +++ b/Code/Sandbox/Editor/PreferencesStdPages.cpp @@ -15,6 +15,8 @@ #include "PreferencesStdPages.h" +#include + // Editor #include "EditorPreferencesPageGeneral.h" #include "EditorPreferencesPageFiles.h" @@ -23,6 +25,7 @@ #include "EditorPreferencesPageViewportMovement.h" #include "EditorPreferencesPageViewportDebug.h" #include "EditorPreferencesPageExperimentalLighting.h" +#include "EditorPreferencesPageAWS.h" ////////////////////////////////////////////////////////////////////////// @@ -42,6 +45,11 @@ CStdPreferencesClassDesc::CStdPreferencesClassDesc() }; m_pageCreators.push_back([]() { return new CEditorPreferencesPage_ExperimentalLighting(); }); + + if (AzToolsFramework::IsComponentWithServiceRegistered(AZ_CRC_CE("AWSCoreEditorService"))) + { + m_pageCreators.push_back([]() { return new CEditorPreferencesPage_AWS(); }); + } } HRESULT CStdPreferencesClassDesc::QueryInterface(const IID& riid, void** ppvObj) diff --git a/Code/Sandbox/Editor/editor_lib_files.cmake b/Code/Sandbox/Editor/editor_lib_files.cmake index e1cf18df55..ebd7f89cfb 100644 --- a/Code/Sandbox/Editor/editor_lib_files.cmake +++ b/Code/Sandbox/Editor/editor_lib_files.cmake @@ -586,6 +586,8 @@ set(FILES EditorPreferencesPageViewportDebug.cpp EditorPreferencesPageExperimentalLighting.h EditorPreferencesPageExperimentalLighting.cpp + EditorPreferencesPageAWS.h + EditorPreferencesPageAWS.cpp EditorPreferencesDialog.h EditorPreferencesDialog.cpp EditorPreferencesDialog.ui diff --git a/Code/Sandbox/Editor/res/AWS_preferences_icon.svg b/Code/Sandbox/Editor/res/AWS_preferences_icon.svg new file mode 100644 index 0000000000..e2a86cf162 --- /dev/null +++ b/Code/Sandbox/Editor/res/AWS_preferences_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/Gems/AWSCore/Code/CMakeLists.txt b/Gems/AWSCore/Code/CMakeLists.txt index 7edb22124f..d6cd57355f 100644 --- a/Gems/AWSCore/Code/CMakeLists.txt +++ b/Gems/AWSCore/Code/CMakeLists.txt @@ -64,11 +64,13 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS) Include/Public BUILD_DEPENDENCIES PRIVATE + AZ::AzQtComponents 3rdParty::Qt::Core 3rdParty::Qt::Widgets - AZ::AzQtComponents + Gem::AWSCore.Static PUBLIC AZ::AzToolsFramework + 3rdParty::AWSNativeSDK::AWSCore ) ly_add_target( diff --git a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h index 45a2c1f9f7..144976f355 100644 --- a/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h +++ b/Gems/AWSCore/Code/Include/Private/AWSCoreEditorModule.h @@ -16,7 +16,7 @@ namespace AWSCore { class AWSCoreEditorModule - :public AZ::Module + : public AZ::Module { public: AZ_RTTI(AWSCoreEditorModule, "{C1C9B898-848B-4C2F-A7AA-69642D12BCB5}", AZ::Module); diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSAttributionServiceApi.h b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSAttributionServiceApi.h new file mode 100644 index 0000000000..d14e51589c --- /dev/null +++ b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSAttributionServiceApi.h @@ -0,0 +1,71 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#pragma once + +#include +#include + +namespace AWSCore +{ + namespace ServiceAPI + { + //! Struct for storing the success response. + struct AWSAtrributionSuccessResponse + { + //! Identify the expected property type and provide a location where the property value can be stored. + //! @param key Name of the property. + //! @param reader JSON reader to read the property. + bool OnJsonKey(const char* key, AWSCore::JsonReader& reader); + + AZStd::string result; //!< Processing result for the input record. + }; + + // Service RequestJobs + AWS_FEATURE_GEM_SERVICE(AWSAttribution); + + //! POST request to send attribution metric to the backend. + //! The path for this service API is "/prod/metrics". + class AWSAttributionRequest + : public AWSCore::ServiceRequest + { + public: + SERVICE_REQUEST(AWSAttribution, HttpMethod::HTTP_POST, "/metrics"); + + bool UseAWSCredentials() + { + return false; + } + + //! Request body for the service API request. + struct Parameters + { + //! Build the service API request. + //! @request Builder for generating the request. + //! @return Whether the request is built successfully. + bool BuildRequest(AWSCore::RequestBuilder& request); + + //! Write to the service API request body. + //! @param writer JSON writer for the serialization. + //! @return Whether the serialization is successful. + bool WriteJson(AWSCore::JsonWriter& writer) const; + + AttributionMetric metric; + }; + + AWSAtrributionSuccessResponse result; + Parameters parameters; //! Request parameter. + }; + + using AWSAttributionRequestJob = AWSCore::ServiceRequestJob; + } // ServiceAPI +} // AWSMetrics diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h new file mode 100644 index 0000000000..503abfb9cf --- /dev/null +++ b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h @@ -0,0 +1,25 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +namespace AWSCore +{ + //! Default metrics attribute keys + static constexpr char AwsAttributionAttributeKeyVersion[] = "version"; + static constexpr char AwsAttributionAttributeKeyO3DEVersion[] = "o3de_version"; + static constexpr char AwsAttributionAttributeKeyPlatform[] = "platform"; + static constexpr char AwsAttributionAttributeKeyPlatformVersion[] = "platform_version"; + static constexpr char AwsAttributionAttributeKeyActiveAWSGems[] = "aws_gems"; + static constexpr char AwsAttributionAttributeKeyTimestamp[] = "timestamp"; + +} // namespace AWSCOre diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionManager.h b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionManager.h new file mode 100644 index 0000000000..ad3b8c72c9 --- /dev/null +++ b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionManager.h @@ -0,0 +1,53 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once +#include +#include + +#include +#include + +namespace AWSCore +{ + //! Manages operational metrics for AWS gems + class AWSAttributionManager + { + public: + AWSAttributionManager(); + virtual ~AWSAttributionManager(); + + //! Perform initialization + void Init(); + + //! Run metric check + void MetricCheck(); + + protected: + virtual void SubmitMetric(AttributionMetric& metric); + virtual void UpdateMetric(AttributionMetric& metric); + void UpdateLastSend(); + void SetApiEndpointAndRegion(ServiceAPI::AWSAttributionRequestJob::Config* config); + + private: + bool ShouldGenerateMetric() const; + + AZStd::string GetEngineVersion() const; + AZStd::string GetPlatform() const; + void GetActiveAWSGems(AZStd::vector& gemNames); + + void SaveSettingsRegistryFile(); + + AZStd::unique_ptr m_settingsRegistry; + }; + +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionMetric.h b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionMetric.h new file mode 100644 index 0000000000..42518b8df5 --- /dev/null +++ b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionMetric.h @@ -0,0 +1,62 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or + * a third party where indicated. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace AWSCore +{ + //! Defines the operational metric sent periodically + class AttributionMetric + { + public: + AZ_TYPE_INFO(MetricsAttribute, "{6483F481-0C18-4171-8B59-A44F2F28EAE5}") + + AttributionMetric(); + AttributionMetric(const AZStd::string& timestamp); + ~AttributionMetric() = default; + + void SetO3DEVersion(const AZStd::string& version); + void SetPlatform(const AZStd::string& platform, const AZStd::string& platformVersion); + void AddActiveGem(const AZStd::string& gemName); + + //! Serialize the metrics object queue to a string. + //! @return Serialized string. + AZStd::string SerializeToJson(); + + //! Serialize the metrics object to JSON for the sending requests. + //! @param writer JSON writer for the serialization. + //! @return Whether the metrics event is serialized successfully. + bool SerializeToJson(AWSCore::JsonWriter& writer) const; + + //! Read from a JSON value to the metrics event. + //! @param metricsObjVal JSON value to read from. + //! @return Whether the metrics event is created successfully. + bool ReadFromJson(rapidjson::Value& metricsObjVal); + + //! Generates a UTC 8601 formatted timestamp + static AZStd::string GenerateTimeStamp(); + private: + AZStd::string m_version; //!< Schema version in use + AZStd::string m_o3deVersion; //!< O3DE editor version in use + AZStd::string m_platform; //!< OS type + AZStd::string m_platformVersion; //!< OS subtype + AZStd::string m_timestamp; //!< Metric generation time + AZStd::vector m_activeAWSGems; //!< Active AWS Gems in project + }; +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionSystemComponent.h b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionSystemComponent.h new file mode 100644 index 0000000000..476b7ba842 --- /dev/null +++ b/Gems/AWSCore/Code/Include/Private/Editor/Attribution/AWSCoreAttributionSystemComponent.h @@ -0,0 +1,50 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#pragma once + +#include +#include + +namespace AWSCore +{ + class AWSAttributionManager; + + //! Attribution System Component. Responsible for instantiating and managing AWS Attribution Manager + class AWSAttributionSystemComponent: + public AZ::Component + { + public: + AZ_COMPONENT(AWSAttributionSystemComponent, "{366861EC-8337-4180-A202-4E4DF082A3A8}"); + + AWSAttributionSystemComponent(); + ~AWSAttributionSystemComponent() = default; + + static void Reflect(AZ::ReflectContext* context);\ + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + protected: + //////////////////////////////////////////////////////////////////////// + // AZ::Component interface implementation + void Init() override; + void Activate() override; + void Deactivate() override; + //////////////////////////////////////////////////////////////////////// + + private: + AZStd::unique_ptr m_manager; //!< pointer to the attribution manager which handles operational metrics + }; +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h b/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h index 722fc098a0..709e786adc 100644 --- a/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h +++ b/Gems/AWSCore/Code/Include/Public/Framework/ServiceClientJobConfig.h @@ -113,7 +113,13 @@ namespace AWSCore /// needed. See it's use in ServiceRequestJobConfig. const AZStd::string GetServiceUrl() override { + if (endpointOverride.has_value()) + { + return endpointOverride.value().c_str(); + } + AZStd::string serviceUrl; + if (!ServiceTraitsType::RESTApiIdKeyName && !ServiceTraitsType::RESTApiStageKeyName) { AWSResourceMappingRequestBus::BroadcastResult( diff --git a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp index 69e45bfd68..2f4519c960 100644 --- a/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp +++ b/Gems/AWSCore/Code/Source/AWSCoreEditorModule.cpp @@ -11,6 +11,7 @@ #include #include +#include namespace AWSCore { @@ -19,6 +20,7 @@ namespace AWSCore // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here. m_descriptors.insert(m_descriptors.end(), { AWSCoreEditorSystemComponent::CreateDescriptor(), + AWSAttributionSystemComponent::CreateDescriptor() }); } @@ -28,7 +30,8 @@ namespace AWSCore AZ::ComponentTypeList AWSCoreEditorModule::GetRequiredSystemComponents() const { return AZ::ComponentTypeList{ - azrtti_typeid() + azrtti_typeid(), + azrtti_typeid() }; } diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSAttributionServiceApi.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSAttributionServiceApi.cpp new file mode 100644 index 0000000000..3c82ec98f9 --- /dev/null +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSAttributionServiceApi.cpp @@ -0,0 +1,45 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include + +namespace AWSCore +{ + namespace ServiceAPI + { + constexpr char AwsAttributionServiceResultResponseKey[] = "statusCode"; + + bool AWSAtrributionSuccessResponse::OnJsonKey(const char* key, AWSCore::JsonReader& reader) + { + if (strcmp(key, AwsAttributionServiceResultResponseKey) == 0) + { + return reader.Accept(result); + } + return reader.Ignore(); + } + + bool AWSAttributionRequest::Parameters::BuildRequest(AWSCore::RequestBuilder& request) + { + bool ok = true; + ok = ok && request.WriteJsonBodyParameter(*this); + return ok; + } + + bool AWSAttributionRequest::Parameters::WriteJson(AWSCore::JsonWriter& writer) const + { + bool ok = true; + ok = ok && metric.SerializeToJson(writer); + return ok; + } + } +} diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp new file mode 100644 index 0000000000..e7a6828903 --- /dev/null +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionManager.cpp @@ -0,0 +1,275 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +namespace AWSCore +{ + static constexpr const char* EngineVersionJsonKey = "O3DEVersion"; + + constexpr char EditorAWSPreferencesFileName[] = "editor_aws_preferences.setreg"; + constexpr char AWSAttributionSettingsPrefixKey[] = "/Amazon/AWS/Preferences"; + constexpr char AWSAttributionEnabledKey[] = "/Amazon/AWS/Preferences/AWSAttributionEnabled"; + constexpr char AWSAttributionDelaySecondsKey[] = "/Amazon/AWS/Preferences/AWSAttributionDelaySeconds"; + constexpr char AWSAttributionLastTimeStampKey[] = "/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp"; + constexpr char AWSAttributionApiId[] = "xbzx78kvbk"; + constexpr char AWSAttributionChinaApiId[] = ""; + constexpr char AWSAttributionApiStage[] = "prod"; + + AWSAttributionManager::AWSAttributionManager() + { + m_settingsRegistry = AZStd::make_unique(); + } + + AWSAttributionManager::~AWSAttributionManager() + { + m_settingsRegistry.reset(); + } + + void AWSAttributionManager::Init() + { + } + + void AWSAttributionManager::MetricCheck() + { + if (ShouldGenerateMetric()) + { + // 1. Gather metadata and assemble metric + AttributionMetric metric; + UpdateMetric(metric); + // 2. Identify region and chose attribution endpoint + + // 3. Post metric + SubmitMetric(metric); + } + } + + bool AWSAttributionManager::ShouldGenerateMetric() const + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "File IO is not initialized."); + + // Resolve path to editor_aws_preferences.setreg + AZStd::string editorAWSPreferencesFilePath = + AZStd::string::format("@user@/%s/%s", AZ::SettingsRegistryInterface::RegistryFolder, EditorAWSPreferencesFileName); + AZStd::array resolvedPathAWSPreference{}; + if (!fileIO->ResolvePath(editorAWSPreferencesFilePath.c_str(), resolvedPathAWSPreference.data(), resolvedPathAWSPreference.size())) + { + AZ_Warning("AWSAttributionManager", false, "Error resolving path %s", resolvedPathAWSPreference.data()); + return false; + } + + if (fileIO->Exists(resolvedPathAWSPreference.data())) + { + m_settingsRegistry->MergeSettingsFile(resolvedPathAWSPreference.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + } + + bool awsAttributionEnabled = false; + if (!m_settingsRegistry->Get(awsAttributionEnabled, AWSAttributionEnabledKey)) + { + // If not found default to sending the metric. + awsAttributionEnabled = true; + } + + if (!awsAttributionEnabled) + { + return false; + } + + // If delayInSeconds is not found, set default to a day + AZ::u64 delayInSeconds = 0; + if (!m_settingsRegistry->Get(delayInSeconds, AWSAttributionDelaySecondsKey)) + { + AZ_Warning("AWSAttributionManager", false, "AWSAttribution delay key not found. Defaulting to delay to day"); + delayInSeconds = 86400; + m_settingsRegistry->Set(AWSAttributionDelaySecondsKey, delayInSeconds); + } + + AZ::u64 lastSendTimeStampSeconds = 0; + if (!m_settingsRegistry->Get(lastSendTimeStampSeconds, AWSAttributionLastTimeStampKey)) + { + // If last time stamp not found, assume this is the first attempt at sending. + return true; + } + + AZStd::chrono::seconds lastSendTimeStamp = AZStd::chrono::seconds(lastSendTimeStampSeconds); + AZStd::chrono::seconds secondsSinceLastSend = + AZStd::chrono::duration_cast(AZStd::chrono::system_clock::now().time_since_epoch()) - lastSendTimeStamp; + if (secondsSinceLastSend.count() >= delayInSeconds) + { + return true; + } + + return false; + } + + void AWSAttributionManager::SaveSettingsRegistryFile() + { + AZ::Job* job = AZ::CreateJobFunction( + [this]() + { + AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance(); + AZ_Assert(fileIO, "File IO is not initialized."); + + // Resolve path to editor_aws_preferences.setreg + AZStd::string editorPreferencesFilePath = AZStd::string::format("@user@/%s/%s", AZ::SettingsRegistryInterface::RegistryFolder, EditorAWSPreferencesFileName); + AZStd::array resolvedPath {}; + fileIO->ResolvePath(editorPreferencesFilePath.c_str(), resolvedPath.data(), resolvedPath.size()); + + AZ::SettingsRegistryMergeUtils::DumperSettings dumperSettings; + dumperSettings.m_prettifyOutput = true; + dumperSettings.m_jsonPointerPrefix = AWSAttributionSettingsPrefixKey; + + AZStd::string stringBuffer; + AZ::IO::ByteContainerStream stringStream(&stringBuffer); + if (!AZ::SettingsRegistryMergeUtils::DumpSettingsRegistryToStream( + *m_settingsRegistry, AWSAttributionSettingsPrefixKey, stringStream, dumperSettings)) + { + AZ_Warning( + "AWSAttributionManager", false, R"(Unable to save changes to the Editor AWS Preferences registry file at "%s"\n)", + resolvedPath.data()); + return; + } + + bool saved {}; + constexpr auto configurationMode = + AZ::IO::SystemFile::SF_OPEN_CREATE | AZ::IO::SystemFile::SF_OPEN_CREATE_PATH | AZ::IO::SystemFile::SF_OPEN_WRITE_ONLY; + if (AZ::IO::SystemFile outputFile; outputFile.Open(resolvedPath.data(), configurationMode)) + { + saved = outputFile.Write(stringBuffer.data(), stringBuffer.size()) == stringBuffer.size(); + } + + AZ_Warning( + "AWSAttributionManager", saved, R"(Unable to save Editor AWS Preferences registry file to path "%s"\n)", + editorPreferencesFilePath.c_str()); + }, + true); + job->Start(); + + } + + void AWSAttributionManager::UpdateLastSend() + { + if (!m_settingsRegistry->Set(AWSAttributionLastTimeStampKey, + AZStd::chrono::duration_cast(AZStd::chrono::system_clock::now().time_since_epoch()).count())) + { + AZ_Warning("AWSAttributionManager", true, "Failed to set AWSAttributionLastTimeStamp"); + return; + } + SaveSettingsRegistryFile(); + } + + void AWSAttributionManager::SetApiEndpointAndRegion(AWSCore::ServiceAPI::AWSAttributionRequestJob::Config* config) + { + // Get default config for the process to check the region. + // Assumption to determine China region is the default profile is set to China region. + auto profile_name = Aws::Auth::GetConfigProfileName(); + Aws::Client::ClientConfiguration clientConfig(profile_name.c_str()); + AZStd::string apiId = AWSAttributionApiId; + + if (clientConfig.region == Aws::Region::CN_NORTH_1 || clientConfig.region == Aws::Region::CN_NORTHWEST_1) + { + config->region = Aws::Region::CN_NORTH_1; + apiId = AWSAttributionChinaApiId; + } + + config->region = Aws::Region::US_WEST_2; + config->endpointOverride = + AWSResourceMappingUtils::FormatRESTApiUrl(apiId, config->region.value().c_str(), AWSAttributionApiStage).c_str(); + } + + AZStd::string AWSAttributionManager::GetEngineVersion() const + { + AZStd::string engineVersion; + auto engineSettingsPath = AZ::IO::FixedMaxPath{ AZ::Utils::GetEnginePath() } / "engine.json"; + if (AZ::IO::SystemFile::Exists(engineSettingsPath.c_str())) + { + AZ::SettingsRegistryImpl settingsRegistry; + if (settingsRegistry.MergeSettingsFile( + engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::EngineSettingsRootKey)) + { + settingsRegistry.Get(engineVersion, AZ::SettingsRegistryInterface::FixedValueString(AZ::SettingsRegistryMergeUtils::EngineSettingsRootKey) + "/" + EngineVersionJsonKey); + } + } + return engineVersion; + } + + AZStd::string AWSAttributionManager::GetPlatform() const + { + return AZ::GetPlatformName(AZ::g_currentPlatform); + } + + void AWSAttributionManager::GetActiveAWSGems(AZStd::vector& gems) + { + AZ::ModuleManagerRequestBus::Broadcast( + &AZ::ModuleManagerRequestBus::Events::EnumerateModules, + [this, &gems](const AZ::ModuleData& moduleData) + { + AZ::Entity* moduleEntity = moduleData.GetEntity(); + auto moduleEntityName = moduleEntity->GetName(); + if (moduleEntityName.contains("AWS")) + gems.push_back(moduleEntityName.substr(0, moduleEntityName.find_last_of("."))); + return true; + }); + } + + void AWSAttributionManager::UpdateMetric(AttributionMetric& metric) + { + AZStd::string engineVersion = this->GetEngineVersion(); + metric.SetO3DEVersion(engineVersion); + + AZStd::string platform = this->GetPlatform(); + metric.SetPlatform(platform, ""); + + AZStd::vector gemNames; + GetActiveAWSGems(gemNames); + for (AZStd::string& gemName : gemNames) + { + metric.AddActiveGem(gemName); + } + } + + void AWSAttributionManager::SubmitMetric(AttributionMetric& metric) + { + AWSCore::ServiceAPI::AWSAttributionRequestJob::Config* config = ServiceAPI::AWSAttributionRequestJob::GetDefaultConfig(); + SetApiEndpointAndRegion(config); + + ServiceAPI::AWSAttributionRequestJob* requestJob = ServiceAPI::AWSAttributionRequestJob::Create( + [this](ServiceAPI::AWSAttributionRequestJob* successJob) + { + AZ_UNUSED(successJob); + + UpdateLastSend(); + AZ_Printf("AWSAttributionManager", "AWSAttribution metric submit success"); + + }, {}, config); + + requestJob->parameters.metric = metric; + requestJob->Start(); + } + +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp new file mode 100644 index 0000000000..6ad322995e --- /dev/null +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionMetric.cpp @@ -0,0 +1,106 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include + +#pragma warning(disable : 4996) + +namespace AWSCore +{ + AttributionMetric::AttributionMetric(const AZStd::string& timestamp) + : m_version("1.1") + , m_timestamp(timestamp) + { + } + + AttributionMetric::AttributionMetric() + : m_version("1.1") + { + m_timestamp = AttributionMetric::GenerateTimeStamp(); + } + + void AttributionMetric::SetO3DEVersion(const AZStd::string& version) + { + m_o3deVersion = version; + } + + void AttributionMetric::SetPlatform(const AZStd::string& platform, const AZStd::string& platformVersion) + { + m_platform = platform; + m_platformVersion = platformVersion; + } + + void AttributionMetric::AddActiveGem(const AZStd::string& gemName) + { + m_activeAWSGems.push_back(gemName); + } + + AZStd::string AttributionMetric::SerializeToJson() + { + std::stringstream stringStream; + AWSCore::JsonOutputStream jsonStream{stringStream}; + AWSCore::JsonWriter writer{jsonStream}; + + SerializeToJson(writer); + + return stringStream.str().c_str(); + } + + bool AttributionMetric::SerializeToJson(AWSCore::JsonWriter& writer) const + { + bool ok = true; + ok = ok && writer.StartObject(); + + writer.Write(AwsAttributionAttributeKeyVersion, m_version.c_str()); + writer.Write(AwsAttributionAttributeKeyO3DEVersion, m_o3deVersion.c_str()); + writer.Write(AwsAttributionAttributeKeyPlatform, m_platform.c_str()); + writer.Write(AwsAttributionAttributeKeyPlatformVersion, m_platformVersion.c_str()); + + if (m_activeAWSGems.size() > 0) + { + writer.Key(AwsAttributionAttributeKeyActiveAWSGems); + writer.StartArray(); // to store Array of objects + for (auto& iter : m_activeAWSGems) + { + writer.String(iter.c_str()); + } + writer.EndArray(); + } + + writer.Write(AwsAttributionAttributeKeyTimestamp, m_timestamp.c_str()); + + ok = ok && writer.EndObject(); + return ok; + } + + bool AttributionMetric::ReadFromJson(rapidjson::Value& metricsObjVal) + { + AZ_UNUSED(metricsObjVal); + return false; + } + + AZStd::string AttributionMetric::GenerateTimeStamp() + { + // Timestamp format is using the UTC ISO8601 format + // TODO: Move to a general util as Metrics has similar requirement + time_t now; + time(&now); + char buffer[50]; + strftime(buffer, sizeof(buffer), "%FT%TZ", gmtime(&now)); + + return buffer; + } + +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionSystemComponent.cpp b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionSystemComponent.cpp new file mode 100644 index 0000000000..ea388972d3 --- /dev/null +++ b/Gems/AWSCore/Code/Source/Editor/Attribution/AWSCoreAttributionSystemComponent.cpp @@ -0,0 +1,81 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace AWSCore +{ + + AWSAttributionSystemComponent::AWSAttributionSystemComponent() + : m_manager(AZStd::make_unique()) + { + } + + void AWSAttributionSystemComponent::Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class()->Version(0); + if (AZ::EditContext* ec = serialize->GetEditContext()) + { + ec->Class("AWSCoreAttributions", "Generates operation metrics for AWSCore gem") + ->ClassElement(AZ::Edit::ClassElements::EditorData, ""); + } + } + } + + void AWSAttributionSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("AWSCoreAttributionService")); + } + + void AWSAttributionSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("AWSCoreAttributionService")); + } + + void AWSAttributionSystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("AWSCoreService")); + } + + void AWSAttributionSystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + AZ_UNUSED(dependent); + } + + void AWSAttributionSystemComponent::Init() + { + // load config if required - ie check if attributions should be generated and pass to manager + m_manager->Init(); + } + + void AWSAttributionSystemComponent::Activate() + { + m_manager->MetricCheck(); + } + + void AWSAttributionSystemComponent::Deactivate() + { + m_manager.reset(); + } + +} // namespace AWSCore + diff --git a/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSAttributionServiceApiTest.cpp b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSAttributionServiceApiTest.cpp new file mode 100644 index 0000000000..affa1ad69c --- /dev/null +++ b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSAttributionServiceApiTest.cpp @@ -0,0 +1,85 @@ +/* +* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or +* its licensors. +* +* For complete copyright and license terms please see the LICENSE at the root of this +* distribution (the "License"). All use of this software is governed by the License, +* or, if provided, by the license below or the license accompanying this file. Do not +* remove or modify any license notices. This file is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* +*/ + +#include +#include +#include +#include + +#include + +using namespace AWSCore; + +namespace AWSCoreUnitTest +{ + class JsonReaderMock + : public AWSCore::JsonReader + { + public: + MOCK_METHOD0(Ignore, bool()); + MOCK_METHOD1(Accept, bool(bool& target)); + MOCK_METHOD1(Accept, bool(AZStd::string& target)); + MOCK_METHOD1(Accept, bool(int& target)); + MOCK_METHOD1(Accept, bool(unsigned& target)); + MOCK_METHOD1(Accept, bool(int64_t& target)); + MOCK_METHOD1(Accept, bool(uint64_t& target)); + MOCK_METHOD1(Accept, bool(double& target)); + MOCK_METHOD1(Accept, bool(AWSCore::JsonKeyHandler keyHandler)); + MOCK_METHOD1(Accept, bool(AWSCore::JsonArrayHandler arrayHandler)); + }; + + class AWSAttributionServiceApiTest + : public UnitTest::ScopedAllocatorSetupFixture + { + public: + testing::NiceMock JsonReader; + }; + + TEST_F(AWSAttributionServiceApiTest, AWSAtrributionSuccessResponse_Serialization) + { + ServiceAPI::AWSAtrributionSuccessResponse response; + response.result = "ok"; + + EXPECT_CALL(JsonReader, Accept(response.result)).Times(1); + EXPECT_CALL(JsonReader, Ignore()).Times(0); + + response.OnJsonKey("statusCode", JsonReader); + } + + TEST_F(AWSAttributionServiceApiTest, AWSAtrributionSuccessResponse_Serialization_Ignore) + { + ServiceAPI::AWSAtrributionSuccessResponse response; + response.result = "ok"; + + EXPECT_CALL(JsonReader, Accept(response.result)).Times(0); + EXPECT_CALL(JsonReader, Ignore()).Times(1); + + response.OnJsonKey("", JsonReader); + } + + TEST_F(AWSAttributionServiceApiTest, BuildRequestBody_PostProducerEventsRequest_SerializedMetricsQueue) + { + ServiceAPI::AWSAttributionRequest request; + request.parameters.metric = AttributionMetric(); + + AWSCore::RequestBuilder requestBuilder{}; + EXPECT_TRUE(request.parameters.BuildRequest(requestBuilder)); + std::shared_ptr bodyContent = requestBuilder.GetBodyContent(); + EXPECT_TRUE(bodyContent != nullptr); + + AZStd::string bodyString; + std::istreambuf_iterator eos; + bodyString = AZStd::string{ std::istreambuf_iterator(*bodyContent), eos }; + AZ_Printf("AWSAttributionServiceApiTest", bodyString.c_str()); + EXPECT_TRUE(bodyString.find(AZStd::string::format("{\"%s\":\"1.1\"", AwsAttributionAttributeKeyVersion)) != AZStd::string::npos); + } +} diff --git a/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionManagerTest.cpp b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionManagerTest.cpp new file mode 100644 index 0000000000..298c250086 --- /dev/null +++ b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionManagerTest.cpp @@ -0,0 +1,414 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +using namespace AWSCore; + +namespace AWSAttributionUnitTest +{ + class ModuleDataMock: + public AZ::ModuleData + { + public: + AZStd::shared_ptr m_entity; + ModuleDataMock(AZStd::string name) + { + m_entity = AZStd::make_shared(); + m_entity->SetName(name); + } + virtual ~ModuleDataMock() + { + m_entity.reset(); + } + + AZ::DynamicModuleHandle* GetDynamicModuleHandle() const override + { + return nullptr; + } + /// Get the handle to the module class + AZ::Module* GetModule() const override + { + return nullptr; + } + /// Get the entity this module uses as a System Entity + AZ::Entity* GetEntity() const override + { + return m_entity.get(); + } + /// Get the debug name of the module + const char* GetDebugName() const override + { + return m_entity->GetName().c_str(); + } + }; + + class ModuleManagerRequestBusMock + : public AZ::ModuleManagerRequestBus::Handler + { + public: + + void EnumerateModulesMock(AZ::ModuleManagerRequests::EnumerateModulesCallback perModuleCallback) + { + auto data = ModuleDataMock("AWSCore.Editor.dll"); + perModuleCallback(data); + data = ModuleDataMock("AWSClientAuth.so"); + perModuleCallback(data); + } + + ModuleManagerRequestBusMock() + { + AZ::ModuleManagerRequestBus::Handler::BusConnect(); + ON_CALL(*this, EnumerateModules(testing::_)).WillByDefault(testing::Invoke(this, &ModuleManagerRequestBusMock::EnumerateModulesMock)); + } + + ~ModuleManagerRequestBusMock() + { + AZ::ModuleManagerRequestBus::Handler::BusDisconnect(); + } + + MOCK_METHOD1(EnumerateModules, void(AZ::ModuleManagerRequests::EnumerateModulesCallback perModuleCallback)); + MOCK_METHOD3(LoadDynamicModule, AZ::ModuleManagerRequests::LoadModuleOutcome(const char* modulePath, AZ::ModuleInitializationSteps lastStepToPerform, bool maintainReference)); + MOCK_METHOD3(LoadDynamicModules, AZ::ModuleManagerRequests::LoadModulesResult(const AZ::ModuleDescriptorList& modules, AZ::ModuleInitializationSteps lastStepToPerform, bool maintainReferences)); + MOCK_METHOD2(LoadStaticModules, AZ::ModuleManagerRequests::LoadModulesResult(AZ::CreateStaticModulesCallback staticModulesCb, AZ::ModuleInitializationSteps lastStepToPerform)); + MOCK_METHOD1(IsModuleLoaded, bool(const char* modulePath)); + }; + + class AWSAttributionManagerMock + : public AWSAttributionManager + { + public: + using AWSAttributionManager::SubmitMetric; + using AWSAttributionManager::UpdateMetric; + using AWSAttributionManager::SetApiEndpointAndRegion; + + + AWSAttributionManagerMock() + { + ON_CALL(*this, SubmitMetric(testing::_)).WillByDefault(testing::Invoke(this, &AWSAttributionManagerMock::SubmitMetricMock)); + } + + MOCK_METHOD1(SubmitMetric, void(AttributionMetric& metric)); + + void SubmitMetricMock(AttributionMetric& metric) + { + AZ_UNUSED(metric); + UpdateLastSend(); + } + }; + + class AttributionManagerTest + : public AWSCoreFixture + { + public: + + virtual ~AttributionManagerTest() = default; + + protected: + AZStd::shared_ptr m_serializeContext; + AZStd::unique_ptr m_registrationContext; + AZStd::shared_ptr m_settingsRegistry; + AZStd::unique_ptr m_jobContext; + AZStd::unique_ptr m_jobCancelGroup; + AZStd::unique_ptr m_jobManager; + AZStd::array m_resolvedSettingsPath; + ModuleManagerRequestBusMock m_moduleManagerRequestBusMock; + + void SetUp() override + { + AWSCoreFixture::SetUp(); + + char rootPath[AZ_MAX_PATH_LEN]; + AZ::Utils::GetExecutableDirectory(rootPath, AZ_MAX_PATH_LEN); + m_localFileIO->SetAlias("@user@", AZ_TRAIT_TEST_ROOT_FOLDER); + + m_localFileIO->ResolvePath("@user@/Registry/", m_resolvedSettingsPath.data(), m_resolvedSettingsPath.size()); + AZ::IO::SystemFile::CreateDir(m_resolvedSettingsPath.data()); + + m_localFileIO->ResolvePath("@user@/Registry/editor_aws_preferences.setreg", m_resolvedSettingsPath.data(), m_resolvedSettingsPath.size()); + + m_serializeContext = AZStd::make_unique(); + + AZ::JsonSystemComponent::Reflect(m_registrationContext.get()); + + m_settingsRegistry = AZStd::make_unique(); + + m_settingsRegistry->SetContext(m_serializeContext.get()); + m_settingsRegistry->SetContext(m_registrationContext.get()); + + AZ::SettingsRegistry::Register(m_settingsRegistry.get()); + + AZ::JobManagerDesc jobManagerDesc; + AZ::JobManagerThreadDesc threadDesc; + + m_jobManager.reset(aznew AZ::JobManager(jobManagerDesc)); + m_jobCancelGroup.reset(aznew AZ::JobCancelGroup()); + jobManagerDesc.m_workerThreads.push_back(threadDesc); + m_jobContext.reset(aznew AZ::JobContext(*m_jobManager, *m_jobCancelGroup)); + AZ::JobContext::SetGlobalContext(m_jobContext.get()); + } + + void TearDown() override + { + AZ::JobContext::SetGlobalContext(nullptr); + m_jobContext.reset(); + m_jobCancelGroup.reset(); + m_jobManager.reset(); + + AZ::SettingsRegistry::Unregister(m_settingsRegistry.get()); + + m_settingsRegistry.reset(); + m_serializeContext.reset(); + m_registrationContext.reset(); + + m_localFileIO->ResolvePath("@user@/Registry/", m_resolvedSettingsPath.data(), m_resolvedSettingsPath.size()); + AZ::IO::SystemFile::DeleteDir(m_resolvedSettingsPath.data()); + + delete AZ::IO::FileIOBase::GetInstance(); + AZ::IO::FileIOBase::SetInstance(nullptr); + + AWSCoreFixture::TearDown(); + } + }; + + TEST_F(AttributionManagerTest, MetricsSettings_AttributionDisabled_SkipsSend) + { + // GIVEN + AWSAttributionManagerMock manager; + manager.Init(); + + CreateFile(m_resolvedSettingsPath.data(), R"({ + "Amazon": { + "AWS": { + "Preferences": { + "AWSAttributionEnabled": false, + "AWSAttributionDelaySeconds": 30 + } + } + } + })"); + + EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(0); + EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(0); + + // WHEN + manager.MetricCheck(); + + // THEN + m_settingsRegistry->MergeSettingsFile(m_resolvedSettingsPath.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + AZ::u64 timeStamp = 0; + m_settingsRegistry->Get(timeStamp, "/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp"); + ASSERT_TRUE(timeStamp == 0); + + RemoveFile(m_resolvedSettingsPath.data()); + } + + TEST_F(AttributionManagerTest, AttributionEnabled_NoPreviousTimeStamp_SendSuccess) + { + // GIVEN + AWSAttributionManagerMock manager; + manager.Init(); + + CreateFile(m_resolvedSettingsPath.data(), R"({ + "Amazon": { + "AWS": { + "Preferences": { + "AWSAttributionEnabled": true, + "AWSAttributionDelaySeconds": 30, + } + } + } + })"); + + EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(1); + EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1); + + // WHEN + manager.MetricCheck(); + + // THEN + m_settingsRegistry->MergeSettingsFile(m_resolvedSettingsPath.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + AZ::u64 timeStamp = 0; + m_settingsRegistry->Get(timeStamp, "/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp"); + ASSERT_TRUE(timeStamp > 0); + + + RemoveFile(m_resolvedSettingsPath.data()); + } + + TEST_F(AttributionManagerTest, AttributionEnabled_ValidPreviousTimeStamp_SendSuccess) + { + // GIVEN + AWSAttributionManagerMock manager; + manager.Init(); + + CreateFile(m_resolvedSettingsPath.data(), R"({ + "Amazon": { + "AWS": { + "Preferences": { + "AWSAttributionEnabled": true, + "AWSAttributionDelaySeconds": 30, + "AWSAttributionLastTimeStamp": 629400 + } + } + } + })"); + + EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(1); + EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1); + + // WHEN + manager.MetricCheck(); + + // THEN + m_settingsRegistry->MergeSettingsFile(m_resolvedSettingsPath.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + AZ::u64 timeStamp = 0; + m_settingsRegistry->Get(timeStamp, "/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp"); + ASSERT_TRUE(timeStamp > 0); + + RemoveFile(m_resolvedSettingsPath.data()); + } + + TEST_F(AttributionManagerTest, AttributionEnabled_DelayNotSatisfied_SendFail) + { + // GIVEN + AWSAttributionManagerMock manager; + manager.Init(); + + + CreateFile(m_resolvedSettingsPath.data(), R"({ + "Amazon": { + "AWS": { + "Preferences": { + "AWSAttributionEnabled": true, + "AWSAttributionDelaySeconds": 300, + "AWSAttributionLastTimeStamp": 0 + } + } + } + })"); + + AZ::u64 delayInSeconds = AZStd::chrono::duration_cast(AZStd::chrono::system_clock::now().time_since_epoch()).count(); + ASSERT_TRUE(m_settingsRegistry->Set("/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp", delayInSeconds)); + + EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(1); + EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1); + + // WHEN + manager.MetricCheck(); + + // THEN + m_settingsRegistry->MergeSettingsFile(m_resolvedSettingsPath.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + AZ::u64 timeStamp = 0; + m_settingsRegistry->Get(timeStamp, "/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp"); + ASSERT_TRUE(timeStamp == delayInSeconds); + + RemoveFile(m_resolvedSettingsPath.data()); + } + + TEST_F(AttributionManagerTest, AttributionEnabledNotFound_SendSuccess) + { + // GIVEN + AWSAttributionManagerMock manager; + manager.Init(); + + CreateFile(m_resolvedSettingsPath.data(), R"({ + "Amazon": { + "AWS": { + "Preferences": { + } + } + } + })"); + + EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(1); + EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1); + + // WHEN + manager.MetricCheck(); + + // THEN + m_settingsRegistry->MergeSettingsFile(m_resolvedSettingsPath.data(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, ""); + AZ::u64 timeStamp = 0; + m_settingsRegistry->Get(timeStamp, "/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp"); + ASSERT_TRUE(timeStamp != 0); + + RemoveFile(m_resolvedSettingsPath.data()); + } + + TEST_F(AttributionManagerTest, SetApiEndpointAndRegion_Success) + { + // GIVEN + AWSAttributionManagerMock manager; + AWSCore::ServiceAPI::AWSAttributionRequestJob::Config* config = aznew AWSCore::ServiceAPI::AWSAttributionRequestJob::Config(); + + // WHEN + manager.SetApiEndpointAndRegion(config); + + // THEN + ASSERT_TRUE(config->region == Aws::Region::US_WEST_2); + ASSERT_TRUE(config->endpointOverride->find("execute-api.us-west-2.amazonaws.com") != Aws::String::npos); + + delete config; + } + + TEST_F(AttributionManagerTest, UpdateMetric_Success) + { + // GIVEN + AWSAttributionManagerMock manager; + AttributionMetric metric; + + AZStd::array engineJsonPath; + m_localFileIO->ResolvePath("@user@/Registry/engine.json", engineJsonPath.data(), engineJsonPath.size()); + CreateFile(engineJsonPath.data(), R"({"O3DEVersion": "1.0.0.0"})"); + + m_localFileIO->ResolvePath("@user@/Registry/", engineJsonPath.data(), engineJsonPath.size()); + m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_EngineRootFolder, engineJsonPath.data()); + + EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1); + + // WHEN + manager.UpdateMetric(metric); + + // THEN + AZStd::string serializedMetricValue = metric.SerializeToJson(); + ASSERT_TRUE(serializedMetricValue.find("\"o3de_version\":\"1.0.0.0\"") != AZStd::string::npos); + ASSERT_TRUE(serializedMetricValue.find(AZ::GetPlatformName(AZ::g_currentPlatform)) != AZStd::string::npos); + ASSERT_TRUE(serializedMetricValue.find("AWSCore.Editor") != AZStd::string::npos); + ASSERT_TRUE(serializedMetricValue.find("AWSClientAuth") != AZStd::string::npos); + + RemoveFile(engineJsonPath.data()); + } + +} // namespace AWSCoreUnitTest diff --git a/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionMetricTest.cpp b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionMetricTest.cpp new file mode 100644 index 0000000000..c93b8bb08e --- /dev/null +++ b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionMetricTest.cpp @@ -0,0 +1,50 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include + +#include + +namespace AWSCore +{ + using AttributionMetricTest = UnitTest::ScopedAllocatorSetupFixture; + + TEST_F(AttributionMetricTest, Contruction_Test) + { + AZStd::string timestamp = AttributionMetric::GenerateTimeStamp(); + AttributionMetric metric(timestamp); + + AZStd::string serializedMetric = AZStd::string::format( + "{\"version\":\"1.1\",\"o3de_version\":\"\",\"platform\":\"\",\"platform_version\":\"\",\"timestamp\":\"%s\"}", timestamp.c_str()); + ASSERT_EQ(metric.SerializeToJson(), serializedMetric); + } + + TEST_F(AttributionMetricTest, AddActiveGems) + { + AZStd::string timestamp = AttributionMetric::GenerateTimeStamp(); + AttributionMetric metric(timestamp); + + AZStd::string gem1 = "AWSGem1"; + AZStd::string gem2 = "AWSGem2"; + + metric.AddActiveGem(gem1); + metric.AddActiveGem(gem2); + + AZStd::string serializedMetric = AZStd::string::format( + "{\"version\":\"1.1\",\"o3de_version\":\"\",\"platform\":\"\",\"platform_version\":\"\",\"aws_gems\":[\"%s\",\"%s\"],\"timestamp\":\"%s\"}", + gem1.c_str(), gem2.c_str(), timestamp.c_str()); + + AZStd::string actualValue = metric.SerializeToJson(); + ASSERT_EQ(actualValue, serializedMetric); + } + +} // namespace AWSCore diff --git a/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionSystemComponentTest.cpp b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionSystemComponentTest.cpp new file mode 100644 index 0000000000..4419c78467 --- /dev/null +++ b/Gems/AWSCore/Code/Tests/Editor/Attribution/AWSCoreAttributionSystemComponentTest.cpp @@ -0,0 +1,132 @@ +/* + * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or + * its licensors. + * + * For complete copyright and license terms please see the LICENSE at the root of this + * distribution (the "License"). All use of this software is governed by the License, + * or, if provided, by the license below or the license accompanying this file. Do not + * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace AWSCore; + +namespace AWSCoreUnitTest +{ + class AWSCoreSystemComponentMock : public AZ::Component + { + public: + AZ_COMPONENT(AWSCoreSystemComponentMock, "{5F48030D-EB59-4820-BC65-69EC7CC6C119}"); + + static void Reflect(AZ::ReflectContext* context) + { + if (AZ::SerializeContext* serialize = azrtti_cast(context)) + { + serialize->Class()->Version(0); + + if (AZ::EditContext* ec = serialize->GetEditContext()) + { + ec->Class("AWSCoreMock", "Adds core support for working with AWS") + ->ClassElement(AZ::Edit::ClassElements::EditorData, "") + ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System")) + ->Attribute(AZ::Edit::Attributes::AutoExpand, true); + } + } + } + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("AWSCoreService")); + } + + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + AZ_UNUSED(incompatible); + } + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) + { + AZ_UNUSED(required); + } + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + AZ_UNUSED(dependent); + } + + ~AWSCoreSystemComponentMock() = default; + + MOCK_METHOD0(Init, void()); + MOCK_METHOD0(Activate, void()); + MOCK_METHOD0(Deactivate, void()); + }; + + class AWSAttributionSystemComponentTest : public AWSCoreFixture + { + void SetUp() override + { + AWSCoreFixture::SetUp(); + m_serializeContext = AZStd::make_unique(); + m_serializeContext->CreateEditContext(); + m_behaviorContext = AZStd::make_unique(); + + m_awsCoreComponentDescriptor.reset(AWSCoreSystemComponentMock::CreateDescriptor()); + m_awsCoreComponentDescriptor->Reflect(m_serializeContext.get()); + m_awsCoreComponentDescriptor->Reflect(m_behaviorContext.get()); + + m_componentDescriptor.reset(AWSAttributionSystemComponent::CreateDescriptor()); + m_componentDescriptor->Reflect(m_serializeContext.get()); + m_componentDescriptor->Reflect(m_behaviorContext.get()); + + m_entity = aznew AZ::Entity(); + m_awsCoreSystemComponentMock = aznew testing::NiceMock(); + m_entity->AddComponent(m_awsCoreSystemComponentMock); + m_attributionSystemsComponent.reset(m_entity->CreateComponent()); + } + + void TearDown() override + { + m_entity->Deactivate(); + m_entity->RemoveComponent(m_attributionSystemsComponent.get()); + m_entity->RemoveComponent(m_awsCoreSystemComponentMock); + delete m_entity; + m_entity = nullptr; + + m_attributionSystemsComponent.reset(); + delete m_awsCoreSystemComponentMock; + m_awsCoreComponentDescriptor.reset(); + m_componentDescriptor.reset(); + m_behaviorContext.reset(); + m_serializeContext.reset(); + AWSCoreFixture::TearDown(); + } + + public: + AZStd::unique_ptr m_attributionSystemsComponent; + testing::NiceMock* m_awsCoreSystemComponentMock; + AZ::Entity* m_entity; + + private: + AZStd::unique_ptr m_serializeContext; + AZStd::unique_ptr m_behaviorContext; + AZStd::unique_ptr m_componentDescriptor; + AZStd::unique_ptr m_awsCoreComponentDescriptor; + }; + + TEST_F(AWSAttributionSystemComponentTest, SystemComponentInitActivate_Success) + { + m_entity->Init(); + m_entity->Activate(); + } +} + + diff --git a/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h b/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h index 1810af9c82..d921ffaf07 100644 --- a/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h +++ b/Gems/AWSCore/Code/Tests/TestFramework/AWSCoreFixture.h @@ -127,13 +127,40 @@ public: void TearDown() override { AZ::IO::FileIOBase::SetInstance(nullptr); - delete m_localFileIO; - AZ::IO::FileIOBase::SetInstance(m_otherFileIO); + + if (m_otherFileIO) + { + delete m_localFileIO; + AZ::IO::FileIOBase::SetInstance(m_otherFileIO); + } AZ::AllocatorInstance::Destroy(); AZ::AllocatorInstance::Destroy(); } + bool CreateFile(const AZStd::string& filePath, const AZStd::string& content) + { + AZ::IO::HandleType fileHandle; + if (!m_localFileIO->Open(filePath.c_str(), AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeText, fileHandle)) + { + return false; + } + + m_localFileIO->Write(fileHandle, content.c_str(), content.size()); + m_localFileIO->Close(fileHandle); + return true; + } + + bool RemoveFile(const AZStd::string& filePath) + { + if (m_localFileIO->Exists(filePath.c_str())) + { + return m_localFileIO->Remove(filePath.c_str()); + } + + return true; + } + AZ::IO::FileIOBase* m_localFileIO = nullptr; private: diff --git a/Gems/AWSCore/Code/awscore_editor_files.cmake b/Gems/AWSCore/Code/awscore_editor_files.cmake index 13bfbb6102..6e16fc7d27 100644 --- a/Gems/AWSCore/Code/awscore_editor_files.cmake +++ b/Gems/AWSCore/Code/awscore_editor_files.cmake @@ -11,6 +11,11 @@ set(FILES Include/Private/AWSCoreEditorSystemComponent.h + Include/Private/Editor/Attribution/AWSCoreAttributionConstant.h + Include/Private/Editor/Attribution/AWSCoreAttributionMetric.h + Include/Private/Editor/Attribution/AWSCoreAttributionManager.h + Include/Private/Editor/Attribution/AWSCoreAttributionSystemComponent.h + Include/Private/Editor/Attribution/AWSAttributionServiceApi.h Include/Private/Editor/AWSCoreEditorManager.h Include/Private/Editor/Constants/AWSCoreEditorMenuLinks.h Include/Private/Editor/Constants/AWSCoreEditorMenuNames.h @@ -18,6 +23,10 @@ set(FILES Include/Private/Editor/UI/AWSCoreResourceMappingToolAction.h Source/AWSCoreEditorSystemComponent.cpp Source/Editor/AWSCoreEditorManager.cpp + Source/Editor/Attribution/AWSCoreAttributionMetric.cpp + Source/Editor/Attribution/AWSCoreAttributionManager.cpp + Source/Editor/Attribution/AWSCoreAttributionSystemComponent.cpp + Source/Editor/Attribution/AWSAttributionServiceApi.cpp Source/Editor/UI/AWSCoreEditorMenu.cpp Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp ) diff --git a/Gems/AWSCore/Code/awscore_editor_tests_files.cmake b/Gems/AWSCore/Code/awscore_editor_tests_files.cmake index ff89b6b5bd..bba830d5d1 100644 --- a/Gems/AWSCore/Code/awscore_editor_tests_files.cmake +++ b/Gems/AWSCore/Code/awscore_editor_tests_files.cmake @@ -11,6 +11,10 @@ set(FILES Tests/AWSCoreEditorSystemComponentTest.cpp + Tests/Editor/Attribution/AWSCoreAttributionManagerTest.cpp + Tests/Editor/Attribution/AWSCoreAttributionMetricTest.cpp + Tests/Editor/Attribution/AWSCoreAttributionSystemComponentTest.cpp + Tests/Editor/Attribution/AWSAttributionServiceApiTest.cpp Tests/Editor/UI/AWSCoreEditorMenuTest.cpp Tests/Editor/UI/AWSCoreEditorUIFixture.h Tests/Editor/UI/AWSCoreResourceMappingToolActionTest.cpp