Merge branch 'CURA-8112_Inform_the_user_about_their_subscription_limits'

This commit is contained in:
Ghostkeeper 2021-07-16 10:04:43 +02:00
commit cc82ff97a7
No known key found for this signature in database
GPG key ID: D2A8871EE34EC59A
6 changed files with 82 additions and 8 deletions

View file

@ -20,7 +20,7 @@ Item
width: parent.width
height: parent.height
property alias createNewProjectButtonVisible: createNewProjectButton.visible
property bool createNewProjectButtonVisible: true
anchors
{
@ -61,6 +61,7 @@ Item
id: createNewProjectButton
text: "New Library project"
visible: createNewProjectButtonVisible && manager.userAccountCanCreateNewLibraryProject && (manager.retrievingProjectsStatus == DF.RetrievalStatus.Success || manager.retrievingProjectsStatus == DF.RetrievalStatus.Failed)
onClicked:
{
@ -68,6 +69,20 @@ Item
}
busy: manager.creatingNewProjectStatus == DF.RetrievalStatus.InProgress
}
Cura.SecondaryButton
{
id: upgradePlanButton
text: "Upgrade plan"
iconSource: UM.Theme.getIcon("LinkExternal")
visible: createNewProjectButtonVisible && !manager.userAccountCanCreateNewLibraryProject && (manager.retrievingProjectsStatus == DF.RetrievalStatus.Success || manager.retrievingProjectsStatus == DF.RetrievalStatus.Failed)
tooltip: "You have reached the maximum number of projects allowed by your subscription. Please upgrade to the Professional subscription to create more projects."
tooltipWidth: parent.width * 0.5
onClicked: Qt.openUrlExternally("https://ultimaker.com/software/ultimaker-essentials/sign-up-cura?utm_source=cura&utm_medium=software&utm_campaign=lib-max")
}
}
Item

View file

@ -55,6 +55,7 @@ class DigitalFactoryApiClient:
self._http = HttpRequestManager.getInstance()
self._on_error = on_error
self._file_uploader = None # type: Optional[DFFileUploader]
self._library_max_private_projects: Optional[int] = None
self._projects_pagination_mgr = PaginationManager(limit = projects_limit_per_page) if projects_limit_per_page else None # type: Optional[PaginationManager]
@ -69,6 +70,7 @@ class DigitalFactoryApiClient:
callback(
response.library_max_private_projects == -1 or # Note: -1 is unlimited
response.library_max_private_projects > 0)
self._library_max_private_projects = response.library_max_private_projects
else:
Logger.warning(f"Digital Factory: Response is not a feature budget, likely an error: {str(response)}")
callback(False)
@ -79,6 +81,41 @@ class DigitalFactoryApiClient:
error_callback = callbackWrap,
timeout = self.DEFAULT_REQUEST_TIMEOUT)
def checkUserCanCreateNewLibraryProject(self, callback: Callable) -> None:
"""
Checks if the user is allowed to create new library projects.
A user is allowed to create new library projects if the haven't reached their maximum allowed private projects.
"""
def callbackWrap(response: Optional[Any] = None, *args, **kwargs) -> None:
if response is not None:
if isinstance(response, DigitalFactoryProjectResponse): # The user has only one private project
callback(True)
elif isinstance(response, list) and all(isinstance(r, DigitalFactoryProjectResponse) for r in response):
callback(len(response) < cast(int, self._library_max_private_projects))
else:
Logger.warning(f"Digital Factory: Incorrect response type received when requesting private projects: {str(response)}")
callback(False)
else:
Logger.warning(f"Digital Factory: Response is empty, likely an error: {str(response)}")
callback(False)
if self._library_max_private_projects is not None and self._library_max_private_projects > 0:
# The user has a limit in the number of private projects they can create. Check whether they have already
# reached that limit.
# Note: Set the pagination manager to None when doing this get request, or else the next/previous links
# of the pagination will become corrupted
url = f"{self.CURA_API_ROOT}/projects?shared=false&limit={self._library_max_private_projects}"
self._http.get(url,
scope = self._scope,
callback = self._parseCallback(callbackWrap, DigitalFactoryProjectResponse, callbackWrap, pagination_manager = None),
error_callback = callbackWrap,
timeout = self.DEFAULT_REQUEST_TIMEOUT)
else:
# If the limit is -1, then the user is allowed unlimited projects. If its 0 then they are not allowed to
# create any projects
callback(self._library_max_private_projects == -1)
def getProject(self, library_project_id: str, on_finished: Callable[[DigitalFactoryProjectResponse], Any], failed: Callable) -> None:
"""
Retrieves a digital factory project by its library project id.

View file

@ -94,6 +94,9 @@ class DigitalFactoryController(QObject):
"""Signal to inform about the state of user access."""
userAccessStateChanged = pyqtSignal(bool)
"""Signal to inform whether the user is allowed to create more Library projects."""
userCanCreateNewLibraryProjectChanged = pyqtSignal(bool)
def __init__(self, application: CuraApplication) -> None:
super().__init__(parent = None)
@ -143,6 +146,7 @@ class DigitalFactoryController(QObject):
self._application.initializationFinished.connect(self._applicationInitializationFinished)
self._user_has_access = False
self._user_account_can_create_new_project = False
def clear(self) -> None:
self._project_model.clearProjects()
@ -169,10 +173,8 @@ class DigitalFactoryController(QObject):
:return: True if the user account has Digital Library access, else False
"""
if self._account.userProfile:
subscriptions = self._account.userProfile.get("subscriptions", [])
if len(subscriptions) > 0:
return True
if self._user_has_access:
self._api.checkUserCanCreateNewLibraryProject(callback = self.setCanCreateNewLibraryProject)
return self._user_has_access
def initialize(self, preselected_project_id: Optional[str] = None) -> None:
@ -556,6 +558,7 @@ class DigitalFactoryController(QObject):
self._project_model.clearProjects()
self.setSelectedProjectIndex(-1)
self._api.getProjectsFirstPage(search_filter = self._project_filter, on_finished = self._onGetProjectsFirstPageFinished, failed = self._onGetProjectsFailed)
self._api.checkUserCanCreateNewLibraryProject(callback = self.setCanCreateNewLibraryProject)
self.setRetrievingProjectsStatus(RetrievalStatus.InProgress)
self._has_preselected_project = new_has_preselected_project
self.preselectedProjectChanged.emit()
@ -564,6 +567,14 @@ class DigitalFactoryController(QObject):
def hasPreselectedProject(self) -> bool:
return self._has_preselected_project
def setCanCreateNewLibraryProject(self, can_create_new_library_project: bool) -> None:
self._user_account_can_create_new_project = can_create_new_library_project
self.userCanCreateNewLibraryProjectChanged.emit(self._user_account_can_create_new_project)
@pyqtProperty(bool, fset = setCanCreateNewLibraryProject, notify = userCanCreateNewLibraryProjectChanged)
def userAccountCanCreateNewLibraryProject(self) -> bool:
return self._user_account_can_create_new_project
@pyqtSlot(str, "QStringList")
def saveFileToSelectedProject(self, filename: str, formats: List[str]) -> None:
"""

View file

@ -16,9 +16,9 @@ class DigitalFactoryFeatureBudgetResponse(BaseModel):
library_can_use_status: Optional[bool] = False,
library_can_use_tags: Optional[bool] = False,
library_can_use_technical_requirements: Optional[bool] = False,
library_max_organization_shared_projects: Optional[int] = False, # -1 means unlimited
library_max_private_projects: Optional[int] = False, # -1 means unlimited
library_max_team_shared_projects: Optional[int] = False, # -1 means unlimited
library_max_organization_shared_projects: Optional[int] = None, # -1 means unlimited
library_max_private_projects: Optional[int] = None, # -1 means unlimited
library_max_team_shared_projects: Optional[int] = None, # -1 means unlimited
**kwargs) -> None:
self.library_can_use_business_value = library_can_use_business_value

View file

@ -18,6 +18,7 @@ Button
property alias textFont: buttonText.font
property alias cornerRadius: backgroundRect.radius
property alias tooltip: tooltip.tooltipText
property alias tooltipWidth: tooltip.width
property color color: UM.Theme.getColor("primary")
property color hoverColor: UM.Theme.getColor("primary_hover")

View file

@ -417,6 +417,7 @@ UM.MainWindow
Cura.PrimaryButton
{
text: model.name
iconSource: UM.Theme.getIcon(model.icon)
height: UM.Theme.getSize("message_action_button").height
}
}
@ -426,6 +427,7 @@ UM.MainWindow
Cura.SecondaryButton
{
text: model.name
iconSource: UM.Theme.getIcon(model.icon)
height: UM.Theme.getSize("message_action_button").height
}
}
@ -434,6 +436,14 @@ UM.MainWindow
Cura.TertiaryButton
{
text: model.name
iconSource:
{
if (model.icon == null || model.icon == "")
{
return UM.Theme.getIcon("LinkExternal")
}
return UM.Theme.getIcon(model.icon)
}
height: UM.Theme.getSize("message_action_button").height
}
}