[AWS][Attribution] Consent dialog is generated on first launch (#1593)

* Adding AWSAttribution Consent panel

* Remove commented code

* Create AWSCoreAttributionConsentDialog class. Move hard coded strings to static const variables

* Remove #pragma from cpp
This commit is contained in:
amzn-hdoke
2021-06-25 16:36:52 -07:00
committed by GitHub
parent ce955715a8
commit 4a3f4f6f14
8 changed files with 245 additions and 44 deletions
@@ -27,13 +27,13 @@ void CEditorPreferencesPage_AWS::Reflect(AZ::SerializeContext& serialize)
if (editContext)
{
editContext->Class<UsageOptions>("Options", "")
->DataElement(AZ::Edit::UIHandlers::CheckBox, &UsageOptions::m_awsAttributionEnabled, "Send Metrics usage to AWS",
"Reports Gem usage to AWS on Editor launch");
->DataElement(AZ::Edit::UIHandlers::CheckBox, &UsageOptions::m_awsAttributionEnabled, "Allow O3DE to send information about your use of AWS Core Gem to AWS",
"");
editContext->Class<CEditorPreferencesPage_AWS>("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");
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_AWS::m_usageOptions, "AWS Data Collection and Use", "AWS Data Collection and Use");
}
}
@@ -52,7 +52,7 @@ CEditorPreferencesPage_AWS::~CEditorPreferencesPage_AWS()
const char* CEditorPreferencesPage_AWS::GetTitle()
{
return "AWS";
return "Cloud";
}
QIcon& CEditorPreferencesPage_AWS::GetIcon()
@@ -0,0 +1,26 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/SystemAllocator.h>
#include <QMessageBox>
namespace AWSCore
{
//! Defines AWSCoreAttributionConsent QT dialog as QT message box.
class AWSCoreAttributionConsentDialog :
public QMessageBox
{
public:
AZ_CLASS_ALLOCATOR(AWSCoreAttributionConsentDialog, AZ::SystemAllocator, 0);
AWSCoreAttributionConsentDialog();
virtual ~AWSCoreAttributionConsentDialog() = default;
};
} // namespace AWSCore
@@ -11,11 +11,13 @@
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <Editor/Attribution/AWSAttributionServiceApi.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
namespace AWSCore
{
//! Manages operational metrics for AWS gems
class AWSAttributionManager
: private AzToolsFramework::EditorEvents::Bus::Handler
{
public:
AWSAttributionManager();
@@ -32,16 +34,21 @@ namespace AWSCore
virtual void UpdateMetric(AttributionMetric& metric);
void UpdateLastSend();
void SetApiEndpointAndRegion(ServiceAPI::AWSAttributionRequestJob::Config* config);
virtual void ShowConsentDialog();
private:
bool ShouldGenerateMetric() const;
bool CheckAWSCredentialsConfigured();
bool CheckConsentShown();
AZStd::string GetEngineVersion() const;
AZStd::string GetPlatform() const;
void GetActiveAWSGems(AZStd::vector<AZStd::string>& gemNames);
void SaveSettingsRegistryFile();
// AzToolsFramework::EditorEvents interface implementation
void NotifyMainWindowInitialized(QMainWindow* mainWindow) override;
AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_settingsRegistry;
};
@@ -0,0 +1,44 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Editor/Attribution/AWSCoreAttributionConsentDialog.h>
#include <QCheckBox>
#include <QLayout>
namespace AWSCore
{
constexpr const char* AWSAttributionConsentDialogTitle = "AWS Core Gem Usage Agreement";
constexpr const char* AWSAttributionConsentDialogMessage = "<nobr>The AWS Core Gem has detected credentials for an Amazon Web Services account for this</nobr><br>\
<nobr>instance of O3DE. <a href=\"https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-core/configuring-credentials\">Click here</a> to learn more about AWS integration, including how to</nobr><br>\
<nobr>manage your AWS credentials.</nobr><br><br>\
<nobr>Please note: when credentials are detected, AWS Core Gem sends telemetry data to AWS,</nobr><br>\
<nobr>which helps us improve AWS services for O3DE. You can change this setting below, and at</nobr><br>\
<nobr>any time in Settings: Global Preferences. Data sent is subject to the <a href=\"https://aws.amazon.com/privacy\">AWS Privacy Policy</a>.</nobr><br>\
<nobr><a href=\"https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-core/telemetry-data-collection\">Click here</a> to learn more about what data is sent to AWS.</nobr>";
constexpr const char* AWSAttributionConsentDialogCheckboxText = "Please share the information about my use of AWS Core Gem with AWS.";
AWSCoreAttributionConsentDialog::AWSCoreAttributionConsentDialog()
{
this->setWindowTitle(AWSAttributionConsentDialogTitle);
this->setText(AWSAttributionConsentDialogMessage);
QCheckBox* checkBox = new QCheckBox(AWSAttributionConsentDialogCheckboxText);
checkBox->setChecked(true);
this->setCheckBox(checkBox);
this->setStandardButtons(QMessageBox::Save | QMessageBox::Cancel);
this->setDefaultButton(QMessageBox::Save);
this->button(QMessageBox::Cancel)->hide();
this->setIcon(QMessageBox::Information);
QGridLayout* layout = (QGridLayout*)this->layout();
if (layout)
{
layout->setVerticalSpacing(20);
layout->setHorizontalSpacing(10);
}
}
} // namespace AWSCore
@@ -7,6 +7,7 @@
#include <Editor/Attribution/AWSCoreAttributionMetric.h>
#include <Editor/Attribution/AWSCoreAttributionManager.h>
#include <Editor/Attribution/AWSCoreAttributionConsentDialog.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/IO/FileIO.h>
@@ -19,10 +20,10 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/Module/ModuleManagerBus.h>
#include <ResourceMapping/AWSResourceMappingUtils.h>
#include <Credential/AWSCredentialBus.h>
#include <QSysInfo>
#include <QString>
#include <QMessageBox>
namespace AWSCore
@@ -34,6 +35,7 @@ namespace AWSCore
constexpr char AWSAttributionEnabledKey[] = "/Amazon/AWS/Preferences/AWSAttributionEnabled";
constexpr char AWSAttributionDelaySecondsKey[] = "/Amazon/AWS/Preferences/AWSAttributionDelaySeconds";
constexpr char AWSAttributionLastTimeStampKey[] = "/Amazon/AWS/Preferences/AWSAttributionLastTimeStamp";
constexpr char AWSAttributionConsentShown[] = "/Amazon/AWS/Preferences/AWSAttributionConsentShown";
constexpr char AWSAttributionApiId[] = "2zxvvmv8d7";
constexpr char AWSAttributionChinaApiId[] = "";
constexpr char AWSAttributionApiStage[] = "prod";
@@ -42,19 +44,49 @@ namespace AWSCore
AWSAttributionManager::AWSAttributionManager()
{
m_settingsRegistry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
}
AWSAttributionManager::~AWSAttributionManager()
{
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
m_settingsRegistry.reset();
}
void AWSAttributionManager::Init()
{
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<char, AZ::IO::MaxPathLength> 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, "");
}
}
void AWSAttributionManager::MetricCheck()
{
if (!CheckAWSCredentialsConfigured())
{
return;
}
if (!CheckConsentShown())
{
ShowConsentDialog();
}
if (ShouldGenerateMetric())
{
// Gather metadata and assemble metric
@@ -67,30 +99,12 @@ namespace AWSCore
}
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<char, AZ::IO::MaxPathLength> 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;
AZ_Warning("AWSAttributionManager", false, "Key %s should be set by consent window", AWSAttributionEnabledKey);
return false;
}
if (!awsAttributionEnabled)
@@ -124,6 +138,50 @@ namespace AWSCore
return false;
}
bool AWSAttributionManager::CheckAWSCredentialsConfigured()
{
AWSCore::AWSCredentialResult credentialResult;
AWSCore::AWSCredentialRequestBus::BroadcastResult(credentialResult, &AWSCore::AWSCredentialRequests::GetCredentialsProvider);
if (credentialResult.result)
{
std::shared_ptr<Aws::Auth::AWSCredentialsProvider> provider = credentialResult.result;
auto creds = provider->GetAWSCredentials();
if (!creds.IsEmpty())
{
return true;
}
}
return false;
}
void AWSAttributionManager::ShowConsentDialog()
{
AWSCoreAttributionConsentDialog* msgBox = aznew AWSCoreAttributionConsentDialog();
int ret = msgBox->exec();
m_settingsRegistry->Set(AWSAttributionConsentShown, true);
switch (ret)
{
case QMessageBox::Save:
m_settingsRegistry->Set(AWSAttributionEnabledKey, msgBox->checkBox());
break;
case QMessageBox::Cancel:
default:
m_settingsRegistry->Set(AWSAttributionEnabledKey, false);
break;
}
SaveSettingsRegistryFile();
delete msgBox;
}
// Waiting on Editor QT main window to be initialized before showing consent window.
// This will have the Editor loading screen in the background when showing consent dialog.
void AWSAttributionManager::NotifyMainWindowInitialized(QMainWindow* mainWindow)
{
AZ_UNUSED(mainWindow);
MetricCheck();
}
void AWSAttributionManager::SaveSettingsRegistryFile()
{
AZ::Job* job = AZ::CreateJobFunction(
@@ -199,6 +257,13 @@ namespace AWSCore
AWSResourceMappingUtils::FormatRESTApiUrl(apiId, config->region.value().c_str(), AWSAttributionApiStage).c_str();
}
bool AWSAttributionManager::CheckConsentShown()
{
bool consentShown = false;
m_settingsRegistry->Get(consentShown, AWSAttributionConsentShown);
return consentShown;
}
AZStd::string AWSAttributionManager::GetEngineVersion() const
{
AZStd::string engineVersion;
@@ -64,7 +64,6 @@ namespace AWSCore
void AWSAttributionSystemComponent::Activate()
{
m_manager->MetricCheck();
}
void AWSAttributionSystemComponent::Deactivate()
@@ -7,6 +7,7 @@
#include <Editor/Attribution/AWSCoreAttributionManager.h>
#include <Editor/Attribution/AWSCoreAttributionMetric.h>
#include <Credential/AWSCredentialBus.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
@@ -102,6 +103,30 @@ namespace AWSAttributionUnitTest
MOCK_METHOD1(IsModuleLoaded, bool(const char* modulePath));
};
class AWSCredentialRquestsBusMock
: public AWSCore::AWSCredentialRequestBus::Handler
{
public:
AWSCredentialRquestsBusMock()
{
m_provider = std::make_shared<Aws::Auth::SimpleAWSCredentialsProvider>("TestAccessKey", "TestSecreKey", "TestSession");
AWSCore::AWSCredentialRequestBus::Handler::BusConnect();
ON_CALL(*this, GetCredentialsProvider()).WillByDefault(testing::Return(m_provider));
ON_CALL(*this, GetCredentialHandlerOrder()).WillByDefault(testing::Return(CredentialHandlerOrder::DEFAULT_CREDENTIAL_HANDLER));
}
~AWSCredentialRquestsBusMock()
{
AWSCore::AWSCredentialRequestBus::Handler::BusDisconnect();
m_provider.reset();
}
MOCK_CONST_METHOD0(GetCredentialHandlerOrder, int());
MOCK_METHOD0(GetCredentialsProvider, std::shared_ptr<Aws::Auth::AWSCredentialsProvider>());
std::shared_ptr<Aws::Auth::AWSCredentialsProvider> m_provider;
};
class AWSAttributionManagerMock
: public AWSAttributionManager
{
@@ -109,7 +134,7 @@ namespace AWSAttributionUnitTest
using AWSAttributionManager::SubmitMetric;
using AWSAttributionManager::UpdateMetric;
using AWSAttributionManager::SetApiEndpointAndRegion;
using AWSAttributionManager::ShowConsentDialog;
AWSAttributionManagerMock()
{
@@ -117,6 +142,7 @@ namespace AWSAttributionUnitTest
}
MOCK_METHOD1(SubmitMetric, void(AttributionMetric& metric));
MOCK_METHOD0(ShowConsentDialog, void());
void SubmitMetricMock(AttributionMetric& metric)
{
@@ -141,6 +167,7 @@ namespace AWSAttributionUnitTest
AZStd::unique_ptr<AZ::JobManager> m_jobManager;
AZStd::array<char, AZ::IO::MaxPathLength> m_resolvedSettingsPath;
ModuleManagerRequestBusMock m_moduleManagerRequestBusMock;
AWSCredentialRquestsBusMock m_credentialRequestBusMock;
void SetUp() override
{
@@ -199,16 +226,16 @@ namespace AWSAttributionUnitTest
}
};
TEST_F(AttributionManagerTest, MetricsSettings_AttributionDisabled_SkipsSend)
TEST_F(AttributionManagerTest, MetricsSettings_ConsentShown_AttributionDisabled_SkipsSend)
{
// GIVEN
AWSAttributionManagerMock manager;
manager.Init();
CreateFile(m_resolvedSettingsPath.data(), R"({
"Amazon": {
"AWS": {
"Preferences": {
"AWSAttributionConsentShown": true,
"AWSAttributionEnabled": false,
"AWSAttributionDelaySeconds": 30
}
@@ -216,8 +243,11 @@ namespace AWSAttributionUnitTest
}
})");
manager.Init();
EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(0);
EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(0);
EXPECT_CALL(m_credentialRequestBusMock, GetCredentialsProvider()).Times(1);
// WHEN
manager.MetricCheck();
@@ -231,25 +261,27 @@ namespace AWSAttributionUnitTest
RemoveFile(m_resolvedSettingsPath.data());
}
TEST_F(AttributionManagerTest, AttributionEnabled_NoPreviousTimeStamp_SendSuccess)
TEST_F(AttributionManagerTest, AttributionEnabled_ContentShown_NoPreviousTimeStamp_SendSuccess)
{
// GIVEN
AWSAttributionManagerMock manager;
manager.Init();
CreateFile(m_resolvedSettingsPath.data(), R"({
"Amazon": {
"AWS": {
"Preferences": {
"AWSAttributionConsentShown": true,
"AWSAttributionEnabled": true,
"AWSAttributionDelaySeconds": 30,
}
}
}
})");
manager.Init();
EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(1);
EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1);
EXPECT_CALL(m_credentialRequestBusMock, GetCredentialsProvider()).Times(1);
// WHEN
manager.MetricCheck();
@@ -264,16 +296,16 @@ namespace AWSAttributionUnitTest
RemoveFile(m_resolvedSettingsPath.data());
}
TEST_F(AttributionManagerTest, AttributionEnabled_ValidPreviousTimeStamp_SendSuccess)
TEST_F(AttributionManagerTest, AttributionEnabled_ContentShown_ValidPreviousTimeStamp_SendSuccess)
{
// GIVEN
AWSAttributionManagerMock manager;
manager.Init();
CreateFile(m_resolvedSettingsPath.data(), R"({
"Amazon": {
"AWS": {
"Preferences": {
"AWSAttributionConsentShown": true,
"AWSAttributionEnabled": true,
"AWSAttributionDelaySeconds": 30,
"AWSAttributionLastTimeStamp": 629400
@@ -282,8 +314,11 @@ namespace AWSAttributionUnitTest
}
})");
manager.Init();
EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(1);
EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1);
EXPECT_CALL(m_credentialRequestBusMock, GetCredentialsProvider()).Times(1);
// WHEN
manager.MetricCheck();
@@ -297,17 +332,16 @@ namespace AWSAttributionUnitTest
RemoveFile(m_resolvedSettingsPath.data());
}
TEST_F(AttributionManagerTest, AttributionEnabled_DelayNotSatisfied_SendFail)
TEST_F(AttributionManagerTest, AttributionEnabled_ContentShown_DelayNotSatisfied_SendFail)
{
// GIVEN
AWSAttributionManagerMock manager;
manager.Init();
CreateFile(m_resolvedSettingsPath.data(), R"({
"Amazon": {
"AWS": {
"Preferences": {
"AWSAttributionConsentShown": true,
"AWSAttributionEnabled": true,
"AWSAttributionDelaySeconds": 300,
"AWSAttributionLastTimeStamp": 0
@@ -316,11 +350,14 @@ namespace AWSAttributionUnitTest
}
})");
manager.Init();
AZ::u64 delayInSeconds = AZStd::chrono::duration_cast<AZStd::chrono::seconds>(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);
EXPECT_CALL(m_credentialRequestBusMock, GetCredentialsProvider()).Times(1);
// WHEN
manager.MetricCheck();
@@ -334,23 +371,26 @@ namespace AWSAttributionUnitTest
RemoveFile(m_resolvedSettingsPath.data());
}
TEST_F(AttributionManagerTest, AttributionEnabledNotFound_SendSuccess)
TEST_F(AttributionManagerTest, AttributionEnabledNotFound_ContentShown_SendFail)
{
// GIVEN
AWSAttributionManagerMock manager;
manager.Init();
CreateFile(m_resolvedSettingsPath.data(), R"({
"Amazon": {
"AWS": {
"Preferences": {
"AWSAttributionConsentShown": true
}
}
}
})");
EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(1);
EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(1);
manager.Init();
EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(0);
EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(0);
EXPECT_CALL(m_credentialRequestBusMock, GetCredentialsProvider()).Times(1);
// WHEN
manager.MetricCheck();
@@ -359,11 +399,29 @@ namespace AWSAttributionUnitTest
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);
ASSERT_TRUE(timeStamp == 0);
RemoveFile(m_resolvedSettingsPath.data());
}
TEST_F(AttributionManagerTest, AttributionEnabledNotFound_ContentNotShown_SendFail)
{
// GIVEN
AWSAttributionManagerMock manager;
manager.Init();
EXPECT_CALL(manager, SubmitMetric(testing::_)).Times(0);
EXPECT_CALL(m_moduleManagerRequestBusMock, EnumerateModules(testing::_)).Times(0);
EXPECT_CALL(m_credentialRequestBusMock, GetCredentialsProvider()).Times(1);
EXPECT_CALL(manager, ShowConsentDialog()).Times(1);
// WHEN
manager.MetricCheck();
// THEN
ASSERT_FALSE(m_localFileIO->Exists(m_resolvedSettingsPath.data()));
}
TEST_F(AttributionManagerTest, SetApiEndpointAndRegion_Success)
{
// GIVEN
@@ -12,6 +12,7 @@ set(FILES
Include/Private/Editor/Attribution/AWSCoreAttributionManager.h
Include/Private/Editor/Attribution/AWSCoreAttributionSystemComponent.h
Include/Private/Editor/Attribution/AWSAttributionServiceApi.h
Include/Private/Editor/Attribution/AWSCoreAttributionConsentDialog.h
Include/Private/Editor/AWSCoreEditorManager.h
Include/Private/Editor/Constants/AWSCoreEditorMenuLinks.h
Include/Private/Editor/Constants/AWSCoreEditorMenuNames.h
@@ -23,6 +24,7 @@ set(FILES
Source/Editor/Attribution/AWSCoreAttributionManager.cpp
Source/Editor/Attribution/AWSCoreAttributionSystemComponent.cpp
Source/Editor/Attribution/AWSAttributionServiceApi.cpp
Source/Editor/Attribution/AWSCoreAttributionConsentDialog.cpp
Source/Editor/UI/AWSCoreEditorMenu.cpp
Source/Editor/UI/AWSCoreResourceMappingToolAction.cpp
)