From e5a461989f6d66e7a89559e192b328976ed7ad55 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Wed, 12 Mar 2014 19:19:39 -0400 Subject: [PATCH] Add a check_repository_usage method which adds (or removes) a notification on the user/org when they go over their plan usage --- endpoints/api.py | 5 ++- endpoints/common.py | 11 ++++-- static/directives/header-bar.html | 3 +- static/directives/notification-view.html | 2 +- static/js/app.js | 46 +++++++++++++---------- test/data/test.db | Bin 483328 -> 483328 bytes 6 files changed, 39 insertions(+), 28 deletions(-) diff --git a/endpoints/api.py b/endpoints/api.py index edd1a9b87..5c86e8710 100644 --- a/endpoints/api.py +++ b/endpoints/api.py @@ -28,7 +28,7 @@ from auth.permissions import (ReadRepositoryPermission, ViewTeamPermission, UserPermission) from endpoints.common import (common_login, get_route_data, truthy_param, - start_build, add_notification) + start_build, check_repository_usage) from endpoints.trigger import (BuildTrigger, TriggerActivationException, TriggerDeactivationException, EmptyRepositoryException) @@ -2197,6 +2197,7 @@ def subscribe(user, plan, token, require_business_plan): cus = stripe.Customer.create(email=user.email, plan=plan, card=card) user.stripe_id = cus.id user.save() + check_repository_usage(user, plan_found) log_action('account_change_plan', user.username, {'plan': plan}) except stripe.CardError as e: return carderror_response(e) @@ -2213,6 +2214,7 @@ def subscribe(user, plan, token, require_business_plan): # We only have to cancel the subscription if they actually have one cus.cancel_subscription() cus.save() + check_repository_usage(user, plan_found) log_action('account_change_plan', user.username, {'plan': plan}) else: @@ -2228,6 +2230,7 @@ def subscribe(user, plan, token, require_business_plan): return carderror_response(e) response_json = subscription_view(cus.subscription, private_repos) + check_repository_usage(user, plan_found) log_action('account_change_plan', user.username, {'plan': plan}) resp = jsonify(response_json) diff --git a/endpoints/common.py b/endpoints/common.py index 0f0e68df7..064d29461 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -126,11 +126,14 @@ def render_page_template(name, **kwargs): return resp -def add_notification(kind, metadata=None, user=None): - if not user and current_user: - user = current_user.db_user() +def check_repository_usage(user_or_org, plan_found): + private_repos = model.get_private_repo_count(user_or_org.username) + repos_allowed = plan_found['privateRepos'] - return model.create_notification(kind, user, metadata or {}) + if private_repos > repos_allowed: + model.create_notification('over_private_usage', user_or_org, {'namespace': user_or_org.username}) + else: + model.delete_notifications_by_kind(user_or_org, 'over_private_usage') def start_build(repository, dockerfile_id, tags, build_name, subdir, manual, diff --git a/static/directives/header-bar.html b/static/directives/header-bar.html index 688eb8411..6d31cf951 100644 --- a/static/directives/header-bar.html +++ b/static/directives/header-bar.html @@ -43,8 +43,7 @@ ng-show="notificationService.notifications.length" ng-class="notificationService.notificationClasses" bs-tooltip="" - title="{{ notificationService.notificationSummaries }}" - data-html="true" + title="User Notifications" data-placement="left" data-container="body"> {{ notificationService.notifications.length }} diff --git a/static/directives/notification-view.html b/static/directives/notification-view.html index 5adb81261..6327a5df8 100644 --- a/static/directives/notification-view.html +++ b/static/directives/notification-view.html @@ -1,7 +1,7 @@
-
{{ getMessage(notification) }}
+
{{ notification.organization }} diff --git a/static/js/app.js b/static/js/app.js index 62734d089..07b0a53ff 100644 --- a/static/js/app.js +++ b/static/js/app.js @@ -479,8 +479,8 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu return userService; }]); - $provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', - function($rootScope, $interval, UserService, ApiService, StringBuilderService) { + $provide.factory('NotificationService', ['$rootScope', '$interval', 'UserService', 'ApiService', 'StringBuilderService', 'PlanService', 'UserService', + function($rootScope, $interval, UserService, ApiService, StringBuilderService, PlanService, UserService) { var notificationService = { 'user': null, 'notifications': [], @@ -493,20 +493,35 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu var notificationKinds = { 'test_notification': { 'level': 'primary', - 'summary': 'This is a test notification', 'message': 'This notification is a long message for testing', 'page': '/about/' }, 'password_required': { 'level': 'error', - 'summary': 'A password is needed for your account', 'message': 'In order to begin pushing and pulling repositories to Quay.io, a password must be set for your account', 'page': '/user?tab=password' + }, + 'over_private_usage': { + 'level': 'error', + 'message': 'Namespace {namespace} is over its allowed private repository count. ' + + '

Please upgrade your plan to avoid disruptions in service.', + 'page': function(metadata) { + var organization = UserService.getOrganization(metadata['namespace']); + if (organization) { + return '/organization/' + metadata['namespace'] + '/admin'; + } else { + return '/user'; + } + } } }; notificationService.getPage = function(notification) { - return notificationKinds[notification['kind']]['page']; + var page = notificationKinds[notification['kind']]['page']; + if (typeof page != 'string') { + page = page(notification['metadata']); + } + return page; }; notificationService.getMessage = function(notification) { @@ -514,20 +529,6 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu return StringBuilderService.buildString(kindInfo['message'], notification['metadata']); }; - notificationService.getSummary = function(notification) { - var kindInfo = notificationKinds[notification['kind']]; - return StringBuilderService.buildString(kindInfo['summary'], notification['metadata']); - }; - - notificationService.getSummaries = function(notifications) { - var summaries = []; - for (var i = 0; i < notifications.length; ++i) { - var notification = notifications[i]; - summaries.push(notificationService.getSummary(notification)); - } - return summaries.join('
'); - }; - notificationService.getClass = function(notification) { return 'notification-' + notificationKinds[notification['kind']]['level']; }; @@ -550,7 +551,6 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu ApiService.listUserNotifications().then(function(resp) { notificationService.notifications = resp['notifications']; notificationService.notificationClasses = notificationService.getClasses(notificationService.notifications); - notificationService.notificationSummaries = notificationService.getSummaries(notificationService.notifications); }); }; @@ -559,6 +559,12 @@ quayApp = angular.module('quay', ['ngRoute', 'chieffancypants.loadingBar', 'angu pollTimerHandle = $interval(notificationService.update, 5 * 60 * 1000 /* five minutes */); }; + // Watch for plan changes and update. + PlanService.registerListener(this, function(plan) { + notificationService.reset(); + notificationService.update(); + }); + // Watch for user changes and update. $rootScope.$watch(function() { return UserService.currentUser(); }, function(currentUser) { notificationService.reset(); diff --git a/test/data/test.db b/test/data/test.db index e4f9f044f9453ed6c0cd3467f4a90b56f5babb2d..ff059481c2a7476689903306e0764ffa8b0aa51c 100644 GIT binary patch delta 4762 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 delta 4748 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 RsYVQG!yTEek2X=|{|g^%hQt5>