[LYN-4288] Adding error page if resource mapping tool has invalid setup (#1219)

main
Vincent Liu 5 years ago committed by GitHub
parent 463e0cfff3
commit ac8ee00aff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,33 @@
"""
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.
"""
from PySide2.QtCore import (QCoreApplication, QObject)
from manager.view_manager import ViewManager
from view.error_page import ErrorPage
class ErrorController(QObject):
"""
ErrorPage Controller
"""
def __init__(self) -> None:
super(ErrorController, self).__init__()
# Initialize manager references
self._view_manager: ViewManager = ViewManager.get_instance()
# Initialize view references
self._error_page: ErrorPage = self._view_manager.get_error_page()
def _ok(self) -> None:
QCoreApplication.instance().quit()
def setup(self) -> None:
self._error_page.ok_button.clicked.connect(self._ok)

@ -48,11 +48,15 @@ class ConfigurationManager(object):
def configuration(self, new_configuration: ConfigurationManager) -> None:
self._configuration = new_configuration
def setup(self, config_path: str) -> bool:
def setup(self, profile_name: str, config_path: str) -> bool:
result: bool = True
logger.info("Setting up default configuration ...")
logger.debug("Setting up default configuration ...")
try:
normalized_config_path: str = file_utils.normalize_file_path(config_path);
logger.debug("Setting up boto3 default session ...")
aws_utils.setup_default_session(profile_name)
logger.debug("Setting up config directory and files ...")
normalized_config_path: str = file_utils.normalize_file_path(config_path)
if normalized_config_path:
self._configuration.config_directory = normalized_config_path
else:
@ -61,6 +65,7 @@ class ConfigurationManager(object):
file_utils.find_files_with_suffix_under_directory(self._configuration.config_directory,
constants.RESOURCE_MAPPING_CONFIG_FILE_NAME_SUFFIX)
logger.debug("Setting up aws account id and region ...")
self._configuration.account_id = aws_utils.get_default_account_id()
self._configuration.region = aws_utils.get_default_region()
except (RuntimeError, FileNotFoundError) as e:

@ -12,6 +12,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
from __future__ import annotations
import logging
from controller.error_controller import ErrorController
from controller.import_resources_controller import ImportResourcesController
from controller.view_edit_controller import ViewEditController
from model import error_messages
@ -33,8 +34,9 @@ class ControllerManager(object):
def __init__(self) -> None:
if ControllerManager.__instance is None:
self._view_edit_controller: ViewEditController = ViewEditController()
self._import_resources_controller: ImportResourcesController = ImportResourcesController()
self._error_controller: ErrorController = None
self._view_edit_controller: ViewEditController = None
self._import_resources_controller: ImportResourcesController = None
ControllerManager.__instance = self
else:
raise AssertionError(error_messages.SINGLETON_OBJECT_ERROR_MESSAGE.format("ControllerManager"))
@ -47,9 +49,17 @@ class ControllerManager(object):
def view_edit_controller(self) -> ViewEditController:
return self._view_edit_controller
def setup(self) -> None:
logger.info("Setting up ViewEdit and ImportResource controllers ...")
self._view_edit_controller.setup()
self._import_resources_controller.setup()
self._import_resources_controller.add_import_resources_sender.connect(
self._view_edit_controller.add_import_resources_receiver)
def setup(self, setup_error: bool) -> None:
if setup_error:
logger.debug("Setting up Error controllers ...")
self._error_controller = ErrorController()
self._error_controller.setup()
else:
logger.debug("Setting up ViewEdit and ImportResource controllers ...")
self._view_edit_controller = ViewEditController()
self._import_resources_controller = ImportResourcesController()
self._view_edit_controller.setup()
self._import_resources_controller.setup()
self._import_resources_controller.add_import_resources_sender.connect(
self._view_edit_controller.add_import_resources_receiver)

@ -37,10 +37,13 @@ class ThreadManager(object):
else:
raise AssertionError(error_messages.SINGLETON_OBJECT_ERROR_MESSAGE.format("ThreadManager"))
def setup(self, thread_count: int = 1) -> None:
# Based on prototype use case, we just need 1 thread
logger.info(f"Setting up thread pool with MaxThreadCount={thread_count} ...")
self._thread_pool.setMaxThreadCount(thread_count)
def setup(self, setup_error: bool, thread_count: int = 1) -> None:
if setup_error:
logger.debug("Skip thread pool creation, as there is major setup error.")
else:
# Based on prototype use case, we just need 1 thread
logger.debug(f"Setting up thread pool with MaxThreadCount={thread_count} ...")
self._thread_pool.setMaxThreadCount(thread_count)
"""Reserves a thread and uses it to run runnable worker, unless this thread will make
the current thread count exceed max thread count. In that case, runnable is added to a run queue instead."""

@ -16,6 +16,7 @@ from PySide2.QtGui import QIcon
from PySide2.QtWidgets import (QMainWindow, QStackedWidget, QWidget)
from model import (error_messages, view_size_constants)
from view.error_page import ErrorPage
from view.import_resources_page import ImportResourcesPage
from view.view_edit_page import ViewEditPage
@ -27,6 +28,10 @@ class ViewManagerConstants(object):
IMPORT_RESOURCES_PAGE_INDEX: int = 1
# Error page will be a single page
ERROR_PAGE_INDEX: int = 0
class ViewManager(object):
"""
View manager maintains the main stacked pages for this tool, which
@ -58,21 +63,32 @@ class ViewManager(object):
ViewManager.__instance = self
else:
raise AssertionError(error_messages.SINGLETON_OBJECT_ERROR_MESSAGE.format("ViewManager"))
def get_error_page(self) -> QWidget:
return self._resource_mapping_stacked_pages.widget(ERROR_PAGE_INDEX)
def get_view_edit_page(self) -> QWidget:
return self._resource_mapping_stacked_pages.widget(ViewManagerConstants.VIEW_AND_EDIT_PAGE_INDEX)
def get_import_resources_page(self) -> QWidget:
return self._resource_mapping_stacked_pages.widget(ViewManagerConstants.IMPORT_RESOURCES_PAGE_INDEX)
def setup(self) -> None:
logger.debug("Setting up ViewEdit and ImportResources view pages ...")
self._resource_mapping_stacked_pages.addWidget(ViewEditPage())
self._resource_mapping_stacked_pages.addWidget(ImportResourcesPage())
def setup(self, setup_error: bool) -> None:
if setup_error:
logger.debug("Setting up Error view pages ...")
self._resource_mapping_stacked_pages.addWidget(ErrorPage())
self._main_window.adjustSize() # fit error page size
else:
logger.debug("Setting up ViewEdit and ImportResources view pages ...")
self._resource_mapping_stacked_pages.addWidget(ViewEditPage())
self._resource_mapping_stacked_pages.addWidget(ImportResourcesPage())
def show(self) -> None:
def show(self, setup_error: bool) -> None:
"""Show up the tool view by setting default page index and showing main widget"""
self._resource_mapping_stacked_pages.setCurrentIndex(ViewManagerConstants.VIEW_AND_EDIT_PAGE_INDEX)
if setup_error:
self._resource_mapping_stacked_pages.setCurrentIndex(ERROR_PAGE_INDEX)
else:
self._resource_mapping_stacked_pages.setCurrentIndex(ViewManagerConstants.VIEW_AND_EDIT_PAGE_INDEX)
self._main_window.show()
def switch_to_view_edit_page(self) -> None:

@ -9,6 +9,12 @@ remove or modify any license notices. This file is distributed on an "AS IS" BAS
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
ERROR_PAGE_TOOL_SETUP_ERROR_MESSAGE: str = \
"AWS credentials are missing or invalid. See " \
"<a href=\"https://docs.o3de.org/docs/user-guide/gems/reference/aws/aws-core/configuring-credentials/\">"\
"<span style=\"color:#4A90E2;\">documentation</span></a> for details." \
"<br>Check log file under Gems/AWSCore/Code/Tool/ResourceMappingTool for further information."
VIEW_EDIT_PAGE_SAVING_FAILED_WITH_INVALID_ROW_ERROR_MESSAGE: str = \
"Row {} have errors. Please correct errors or delete the row to proceed."
VIEW_EDIT_PAGE_READ_FROM_JSON_FAILED_WITH_UNEXPECTED_FILE_ERROR_MESSAGE: str = \

@ -11,6 +11,8 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
NOTIFICATION_LOADING_MESSAGE: str = "Loading..."
ERROR_PAGE_OK_TEXT: str = "OK"
VIEW_EDIT_PAGE_CONFIG_FILE_TEXT: str = "Config File"
VIEW_EDIT_PAGE_CONFIG_LOCATION_TEXT: str = "Config Location:"
VIEW_EDIT_PAGE_ADD_ROW_TEXT: str = "Add Row"

@ -17,6 +17,18 @@ MAIN_PAGE_LAYOUT_MARGIN_TOPBOTTOM: int = 15
INTERACTION_COMPONENT_HEIGHT: int = 25
"""error page related constants"""
ERROR_PAGE_LAYOUT_MARGIN_LEFTRIGHT: int = 10
ERROR_PAGE_LAYOUT_MARGIN_TOPBOTTOM: int = 10
ERROR_PAGE_MAIN_WINDOW_WIDTH: int = 600
ERROR_PAGE_MAIN_WINDOW_HEIGHT: int = 145
ERROR_PAGE_NOTIFICATION_AREA_HEIGHT: int = 100
ERROR_PAGE_FOOTER_AREA_HEIGHT: int = 45
OK_BUTTON_WIDTH: int = 90
"""view edit page related constants"""
VIEW_EDIT_PAGE_HEADER_AREA_HEIGHT: int = 65
VIEW_EDIT_PAGE_CENTER_AREA_HEIGHT: int = 500

@ -73,32 +73,22 @@ if __name__ == "__main__":
except FileNotFoundError:
logger.warning("Failed to load style sheet for resource mapping tool")
logger.info("Initializing boto3 default session ...")
try:
aws_utils.setup_default_session(arguments.profile)
except RuntimeError as error:
logger.error(error)
environment_utils.cleanup_qt_environment()
exit(-1)
logger.info("Initializing configuration manager ...")
configuration_manager: ConfigurationManager = ConfigurationManager()
if not configuration_manager.setup(arguments.config_path):
environment_utils.cleanup_qt_environment()
exit(-1)
configuration_error: bool = not configuration_manager.setup(arguments.profile, arguments.config_path)
logger.info("Initializing thread manager ...")
thread_manager: ThreadManager = ThreadManager()
thread_manager.setup()
thread_manager.setup(configuration_error)
logger.info("Initializing view manager ...")
view_manager: ViewManager = ViewManager()
view_manager.setup()
view_manager.setup(configuration_error)
logger.info("Initializing controller manager ...")
controller_manager: ControllerManager = ControllerManager()
controller_manager.setup()
controller_manager.setup(configuration_error)
view_manager.show()
view_manager.show(configuration_error)
sys.exit(app.exec_())

@ -368,3 +368,18 @@ QFrame#NotificationFrame
margin: 4px;
padding: 4px;
}
QFrame#ErrorPage
{
background-color: #2d2d2d;
border: 1px solid #4A90E2;
border-radius: 2px;
margin: 0px;
padding: 15px;
}
QFrame#ErrorPage QLabel#NotificationIcon
{
padding-left: 15px;
padding-right: 15px;
}

@ -33,6 +33,7 @@ class TestConfigurationManager(TestCase):
def test_get_instance_raise_exception(self) -> None:
self.assertRaises(Exception, ConfigurationManager)
@patch("utils.aws_utils.setup_default_session")
@patch("utils.aws_utils.get_default_region", return_value=_expected_region)
@patch("utils.aws_utils.get_default_account_id", return_value=_expected_account_id)
@patch("utils.file_utils.find_files_with_suffix_under_directory", return_value=_expected_config_files)
@ -42,8 +43,10 @@ class TestConfigurationManager(TestCase):
mock_check_path_exists: MagicMock,
mock_find_files_with_suffix_under_directory: MagicMock,
mock_get_default_account_id: MagicMock,
mock_get_default_region: MagicMock) -> None:
TestConfigurationManager._expected_configuration_manager.setup("")
mock_get_default_region: MagicMock,
mock_setup_default_session: MagicMock) -> None:
TestConfigurationManager._expected_configuration_manager.setup("", "")
mock_setup_default_session.assert_called_once()
mock_get_current_directory_path.assert_called_once()
mock_check_path_exists.assert_called_once_with(TestConfigurationManager._expected_directory_path)
mock_find_files_with_suffix_under_directory.assert_called_once_with(
@ -59,6 +62,7 @@ class TestConfigurationManager(TestCase):
assert TestConfigurationManager._expected_configuration_manager.configuration.region == \
TestConfigurationManager._expected_region
@patch("utils.aws_utils.setup_default_session")
@patch("utils.aws_utils.get_default_region", return_value=_expected_region)
@patch("utils.aws_utils.get_default_account_id", return_value=_expected_account_id)
@patch("utils.file_utils.find_files_with_suffix_under_directory", return_value=_expected_config_files)
@ -68,8 +72,11 @@ class TestConfigurationManager(TestCase):
mock_check_path_exists: MagicMock,
mock_find_files_with_suffix_under_directory: MagicMock,
mock_get_default_account_id: MagicMock,
mock_get_default_region: MagicMock) -> None:
TestConfigurationManager._expected_configuration_manager.setup(TestConfigurationManager._expected_directory_path)
mock_get_default_region: MagicMock,
mock_setup_default_session: MagicMock) -> None:
TestConfigurationManager._expected_configuration_manager.setup(
"", TestConfigurationManager._expected_directory_path)
mock_setup_default_session.assert_called_once()
mock_normalize_file_path.assert_called_once()
mock_check_path_exists.assert_called_once_with(TestConfigurationManager._expected_directory_path)
mock_find_files_with_suffix_under_directory.assert_called_once_with(

@ -25,6 +25,9 @@ class TestControllerManager(TestCase):
@classmethod
def setUpClass(cls) -> None:
error_controller_patcher: patch = patch("manager.controller_manager.ErrorController")
cls._mock_error_controller = error_controller_patcher.start()
import_resources_controller_patcher: patch = patch("manager.controller_manager.ImportResourcesController")
cls._mock_import_resources_controller = import_resources_controller_patcher.start()
@ -52,8 +55,14 @@ class TestControllerManager(TestCase):
mocked_import_resources_controller: MagicMock = \
TestControllerManager._mock_import_resources_controller.return_value
TestControllerManager._expected_controller_manager.setup()
TestControllerManager._expected_controller_manager.setup(False)
mocked_view_edit_controller.setup.assert_called_once()
mocked_import_resources_controller.setup.assert_called_once()
mocked_import_resources_controller.add_import_resources_sender.connect.assert_called_once_with(
mocked_view_edit_controller.add_import_resources_receiver)
def test_setup_error_controller_setup_gets_invoked(self) -> None:
mocked_error_controller: MagicMock = TestControllerManager._mock_error_controller.return_value
TestControllerManager._expected_controller_manager.setup(True)
mocked_error_controller.setup.assert_called_once()

@ -45,9 +45,15 @@ class TestThreadManager(TestCase):
def test_setup_thread_pool_setup_with_expected_configuration(self) -> None:
mocked_thread_pool: MagicMock = TestThreadManager._mock_thread_pool.return_value
TestThreadManager._expected_thread_manager.setup()
TestThreadManager._expected_thread_manager.setup(False)
mocked_thread_pool.setMaxThreadCount.assert_called_once_with(1)
def test_setup_thread_pool_skip_setup(self) -> None:
mocked_thread_pool: MagicMock = TestThreadManager._mock_thread_pool.return_value
TestThreadManager._expected_thread_manager.setup(True)
mocked_thread_pool.setMaxThreadCount.asset_not_called()
def test_start_thread_pool_start_expected_worker(self) -> None:
mocked_thread_pool: MagicMock = TestThreadManager._mock_thread_pool.return_value
expected_mocked_worker: MagicMock = MagicMock()

@ -13,13 +13,14 @@ from typing import List
from unittest import TestCase
from unittest.mock import (call, MagicMock, patch)
from manager.view_manager import (ViewManager, ViewManagerConstants)
from manager.view_manager import (ERROR_PAGE_INDEX, ViewManager, ViewManagerConstants)
class TestViewManager(TestCase):
"""
ViewManager unit test cases
"""
_mock_error_page: MagicMock
_mock_import_resources_page: MagicMock
_mock_view_edit_page: MagicMock
_mock_main_window: MagicMock
@ -28,6 +29,9 @@ class TestViewManager(TestCase):
@classmethod
def setUpClass(cls) -> None:
error_page_patcher: patch = patch("manager.view_manager.ErrorPage")
cls._mock_error_page = error_page_patcher.start()
import_resources_page_patcher: patch = patch("manager.view_manager.ImportResourcesPage")
cls._mock_import_resources_page = import_resources_page_patcher.start()
@ -60,6 +64,15 @@ class TestViewManager(TestCase):
def test_get_instance_raise_exception(self) -> None:
self.assertRaises(Exception, ViewManager)
def test_get_error_page_return_expected_page(self) -> None:
expected_page: MagicMock = TestViewManager._mock_error_page.return_value
mocked_stacked_pages: MagicMock = TestViewManager._mock_stacked_pages.return_value
mocked_stacked_pages.widget.return_value = expected_page
actual_page: MagicMock = TestViewManager._expected_view_manager.get_error_page()
mocked_stacked_pages.widget.assert_called_once_with(ERROR_PAGE_INDEX)
assert actual_page == expected_page
def test_get_import_resources_page_return_expected_page(self) -> None:
expected_page: MagicMock = TestViewManager._mock_import_resources_page.return_value
mocked_stacked_pages: MagicMock = TestViewManager._mock_stacked_pages.return_value
@ -83,18 +96,34 @@ class TestViewManager(TestCase):
mocked_import_resources_page: MagicMock = TestViewManager._mock_import_resources_page.return_value
mocked_view_edit_page: MagicMock = TestViewManager._mock_view_edit_page.return_value
TestViewManager._expected_view_manager.setup()
TestViewManager._expected_view_manager.setup(False)
mocked_calls: List[call] = [call(mocked_view_edit_page), call(mocked_import_resources_page)]
mocked_stacked_pages.addWidget.assert_has_calls(mocked_calls)
def test_setup_error_page_only(self) -> None:
mocked_stacked_pages: MagicMock = TestViewManager._mock_stacked_pages.return_value
mocked_error_page: MagicMock = TestViewManager._mock_error_page.return_value
TestViewManager._expected_view_manager.setup(True)
mocked_calls: List[call] = [call(mocked_error_page)]
mocked_stacked_pages.addWidget.assert_has_calls(mocked_calls)
def test_show_stacked_pages_show_with_expected_index(self) -> None:
mocked_stacked_pages: MagicMock = TestViewManager._mock_stacked_pages.return_value
mocked_main_window: MagicMock = TestViewManager._mock_main_window.return_value
TestViewManager._expected_view_manager.show()
TestViewManager._expected_view_manager.show(False)
mocked_stacked_pages.setCurrentIndex.assert_called_once_with(ViewManagerConstants.VIEW_AND_EDIT_PAGE_INDEX)
mocked_main_window.show.assert_called_once()
def test_show_stacked_pages_show_error_plage(self) -> None:
mocked_stacked_pages: MagicMock = TestViewManager._mock_stacked_pages.return_value
mocked_main_window: MagicMock = TestViewManager._mock_main_window.return_value
TestViewManager._expected_view_manager.show(True)
mocked_stacked_pages.setCurrentIndex.assert_called_once_with(ERROR_PAGE_INDEX)
mocked_main_window.show.assert_called_once()
def test_switch_to_view_edit_page_stacked_pages_switch_to_expected_index(self) -> None:
mocked_stacked_pages: MagicMock = TestViewManager._mock_stacked_pages.return_value

@ -37,10 +37,12 @@ class NotificationFrame(QFrame):
self.setFrameStyle(QFrame.StyledPanel | QFrame.Plain)
icon_label: QLabel = QLabel(self)
icon_label.setObjectName("NotificationIcon")
icon_label.setPixmap(pixmap)
self._title_label: QLabel = QLabel(title, self)
self._title_label.setObjectName("Title")
self._title_label.setOpenExternalLinks(True)
self._title_label.setObjectName("NotificationTitle")
self._title_label.setSizePolicy(QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred))
self._title_label.setWordWrap(True)

@ -0,0 +1,89 @@
"""
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.
"""
from PySide2.QtGui import QPixmap
from PySide2.QtWidgets import (QHBoxLayout, QLayout, QPushButton, QSizePolicy, QSpacerItem, QVBoxLayout, QWidget)
from model import (error_messages, notification_label_text, view_size_constants)
from view.common_view_components import NotificationFrame
class ErrorPage(QWidget):
"""
Error Page
"""
def __init__(self) -> None:
super().__init__()
self.setGeometry(0, 0,
view_size_constants.ERROR_PAGE_MAIN_WINDOW_WIDTH,
view_size_constants.ERROR_PAGE_MAIN_WINDOW_HEIGHT)
page_vertical_layout: QVBoxLayout = QVBoxLayout(self)
page_vertical_layout.setSizeConstraint(QLayout.SetMinimumSize)
page_vertical_layout.setMargin(0)
self._setup_notification_area()
page_vertical_layout.addWidget(self._notification_area)
self._setup_footer_area()
page_vertical_layout.addWidget(self._footer_area)
def _setup_notification_area(self) -> None:
self._notification_area: QWidget = QWidget(self)
self._notification_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
self._notification_area.setMinimumSize(view_size_constants.ERROR_PAGE_MAIN_WINDOW_WIDTH,
view_size_constants.ERROR_PAGE_NOTIFICATION_AREA_HEIGHT)
notification_area_layout: QVBoxLayout = QVBoxLayout(self._notification_area)
notification_area_layout.setSizeConstraint(QLayout.SetMinimumSize)
notification_area_layout.setContentsMargins(
view_size_constants.ERROR_PAGE_LAYOUT_MARGIN_LEFTRIGHT,
view_size_constants.MAIN_PAGE_LAYOUT_MARGIN_TOPBOTTOM,
view_size_constants.ERROR_PAGE_LAYOUT_MARGIN_LEFTRIGHT, 0)
notification_frame: NotificationFrame = \
NotificationFrame(self, QPixmap(":/error_report_warning.svg"),
error_messages.ERROR_PAGE_TOOL_SETUP_ERROR_MESSAGE, False)
notification_frame.setObjectName("ErrorPage")
notification_frame.setMinimumSize(view_size_constants.ERROR_PAGE_MAIN_WINDOW_WIDTH,
view_size_constants.ERROR_PAGE_NOTIFICATION_AREA_HEIGHT)
notification_frame.setVisible(True)
notification_area_layout.addWidget(notification_frame)
def _setup_footer_area(self) -> None:
self._footer_area: QWidget = QWidget(self)
self._footer_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum)
self._footer_area.setMaximumSize(view_size_constants.TOOL_APPLICATION_MAIN_WINDOW_WIDTH,
view_size_constants.ERROR_PAGE_FOOTER_AREA_HEIGHT)
footer_area_layout: QHBoxLayout = QHBoxLayout(self._footer_area)
footer_area_layout.setSizeConstraint(QLayout.SetMinimumSize)
footer_area_layout.setContentsMargins(
view_size_constants.ERROR_PAGE_LAYOUT_MARGIN_LEFTRIGHT,
view_size_constants.ERROR_PAGE_LAYOUT_MARGIN_TOPBOTTOM,
view_size_constants.ERROR_PAGE_LAYOUT_MARGIN_LEFTRIGHT,
view_size_constants.ERROR_PAGE_LAYOUT_MARGIN_TOPBOTTOM)
footer_area_spacer: QSpacerItem = QSpacerItem(view_size_constants.ERROR_PAGE_MAIN_WINDOW_WIDTH,
view_size_constants.INTERACTION_COMPONENT_HEIGHT,
QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
footer_area_layout.addItem(footer_area_spacer)
self._ok_button: QPushButton = QPushButton(self._footer_area)
self._ok_button.setObjectName("Secondary")
self._ok_button.setText(notification_label_text.ERROR_PAGE_OK_TEXT)
self._ok_button.setMinimumSize(view_size_constants.OK_BUTTON_WIDTH,
view_size_constants.INTERACTION_COMPONENT_HEIGHT)
footer_area_layout.addWidget(self._ok_button)
@property
def ok_button(self) -> QPushButton:
return self._ok_button
Loading…
Cancel
Save