Prevent change visibility of a repo in the UI when disallowed by billing plan

Fixes #486

- Extracts out the check plan logic and UI from the new repo page into its own directive (repo-count-checker)
- Adds the new directive to the repo settings panel
- Some additional UI improvements for the repo settings panel
This commit is contained in:
Joseph Schorr 2015-09-16 14:00:06 -04:00
parent fbfe7fdb54
commit 2739cf47ba
8 changed files with 173 additions and 109 deletions

View file

@ -29,6 +29,18 @@
margin-top: -7px !important;
}
.repo-panel-settings-element .repo-count-checker {
margin-top: 20px;
}
.repo-panel-settings-element .co-alert {
margin-bottom: 0px;
}
.repo-panel-settings-element .panel-body {
border-bottom: 0px;
}
@media (max-width: 767px) {
.repo-panel-settings-element .delete-btn {
float: none;

View file

@ -0,0 +1,26 @@
.repo-count-checker .btn {
margin-top: 0px !important;
}
.repo-count-checker .co-alert {
margin-bottom: 6px !important;
padding-right: 120px;
}
.repo-count-checker .co-alert .btn {
position: absolute;
top: 10px;
right: 10px;
}
@media (max-width: 767px) {
.repo-count-checker .co-alert {
padding-right: 10px;
}
.repo-count-checker .co-alert .btn {
position: relative;
margin-top: 20px;
margin-bottom: 10px;
}
}

View file

@ -0,0 +1,24 @@
<div class="repo-count-checker-element">
<div class="required-plan" ng-show="isEnabled && planRequired && planRequired.title">
<div class="co-alert co-alert-info">
In order to make this repository private under
<strong ng-if="isUserNamespace">your personal namespace</strong>
<strong ng-if="!isUserNamespace">organization <b>{{ repo.namespace }}</b></strong>, you will need to upgrade your plan to
<b style="border-bottom: 1px dotted black;" data-html="true"
data-title="{{ '<b>' + planRequired.title + '</b><br>' + planRequired.privateRepos + ' private repositories' }}" bs-tooltip>
{{ planRequired.title }}
</b>.
This will cost $<span>{{ planRequired.price / 100 }}</span>/month.
<a class="btn btn-primary" ng-click="upgradePlan()" ng-show="!planChanging">Upgrade now</a>
</div>
<span ng-if="isUserNamespace && user.organizations.length == 1" style="margin-left: 6px; display: inline-block;">or did you mean to have this repository under the <b>{{ user.organizations[0].name }}</b> namespace?</span>
<div class="cor-loader-inline" ng-show="planChanging"></div>
</div>
<div class="cor-loader-inline" ng-show="isEnabled && checkingPlan"></div>
<div class="required-plan" ng-show="isEnabled && planRequired && !isUserNamespace && !planRequired.title">
<div class="co-alert co-alert-warning">
This organization has reached its private repository limit. Please contact your administrator.
</div>
</div>
</div>
</div>

View file

@ -22,12 +22,10 @@
<div class="repository-events-table" repository="repository"
is-enabled="isEnabled"></div>
<!-- Other settings -->
<!-- Visibility settings -->
<div class="co-panel">
<div class="co-panel-heading"><i class="fa fa-gears"></i> Repository Settings</div>
<div class="co-panel-heading"><i class="fa fa-unlock-alt"></i> Repository Visibility</div>
<div class="cor-loader" ng-show="!repository"></div>
<div ng-show="repository">
<!-- Public/Private -->
<div class="panel-body panel-section lock-section" ng-if="!repository.is_public">
@ -44,12 +42,23 @@
<div>This repository is currently <b>public</b> and is visible to all users, and may be pulled by all users.</div>
<button class="btn btn-default" ng-click="askChangeAccess('private')">
<button class="btn btn-default" ng-click="askChangeAccess('private')" ng-show="!planRequired">
<i class="fa fa-lock"></i>Make Private
</button>
<!-- Payment -->
<div class="repo-count-checker" namespace="repository.namespace" plan-required="planRequired"
is-enabled="repository.is_public">
</div>
</div>
</div>
</div>
<!-- Delete Repository -->
<!-- Delete repository -->
<div class="co-panel">
<div class="co-panel-heading"><i class="fa fa-trash"></i> Delete Repository</div>
<div class="cor-loader" ng-show="!repository"></div>
<div ng-show="repository">
<div class="panel-body panel-section">
<div class="co-alert co-alert-danger">
<button class="btn btn-danger delete-btn" ng-click="askDelete()">
@ -60,10 +69,16 @@
Deleting a repository <b>cannot be undone</b>. Here be dragons!
</div>
</div>
</div>
</div>
<!-- Build Status Badge -->
<div class="panel-body panel-section hidden-xs">
<div class="co-panel hidden-xs">
<div class="co-panel-heading"><i class="fa fa-tasks"></i> Build Status Badge</div>
<div class="cor-loader" ng-show="!repository"></div>
<div ng-show="repository">
<div class="panel-body panel-section">
<!-- Token Info Banner -->
<div class="co-alert co-alert-info" ng-if="!repository.is_public">
Note: This badge contains a token so the badge can be seen by external users. The token does not grant any other access and is safe to share!

View file

@ -0,0 +1,81 @@
/**
* An element which displays a message when the maximum number of private repositories has been
* reached.
*/
angular.module('quay').directive('repoCountChecker', function () {
var directiveDefinitionObject = {
priority: 0,
templateUrl: '/static/directives/repo-count-checker.html',
replace: false,
transclude: true,
restrict: 'C',
scope: {
'namespace': '=namespace',
'planRequired': '=planRequired',
'isEnabled': '=isEnabled'
},
controller: function($scope, $element, ApiService, UserService, PlanService, Features) {
var refresh = function() {
$scope.planRequired = null;
if (!$scope.isEnabled || !$scope.namespace || !Features.BILLING) {
return;
}
$scope.checkingPlan = true;
$scope.isUserNamespace = UserService.isUserNamespace($scope.namespace);
ApiService.getPrivateAllowed($scope.isUserNamespace ? null : $scope.namespace).then(function(resp) {
$scope.checkingPlan = false;
if (resp['privateAllowed']) {
$scope.planRequired = null;
return;
}
if (resp['privateCount'] == null) {
// Organization where we are not the admin.
$scope.planRequired = {};
return;
}
// Otherwise, lookup the matching plan.
PlanService.getMinimumPlan(resp['privateCount'] + 1, !$scope.isUserNamespace, function(minimum) {
$scope.planRequired = minimum;
});
});
};
var subscribedToPlan = function(sub) {
$scope.planChanging = false;
$scope.subscription = sub;
PlanService.getPlan(sub.plan, function(subscribedPlan) {
$scope.subscribedPlan = subscribedPlan;
refresh();
});
};
$scope.$watch('namespace', refresh);
$scope.$watch('isEnabled', refresh);
$scope.upgradePlan = function() {
var callbacks = {
'started': function() { $scope.planChanging = true; },
'opened': function() { $scope.planChanging = true; },
'closed': function() { $scope.planChanging = false; },
'success': subscribedToPlan,
'failure': function(resp) {
$('#couldnotsubscribeModal').modal();
$scope.planChanging = false;
}
};
var isUserNamespace = UserService.isUserNamespace($scope.namespace);
var namespace = isUserNamespace ? null : $scope.namespace;
PlanService.changePlan($scope, namespace, $scope.planRequired.stripeId, callbacks);
};
}
};
return directiveDefinitionObject;
});

View file

@ -22,22 +22,6 @@
'initialize': ''
};
// Watch the namespace on the repo. If it changes, we update the plan and the public/private
// accordingly.
$scope.isUserNamespace = true;
$scope.$watch('repo.namespace', function(namespace) {
// Note: Can initially be undefined.
if (!namespace) { return; }
var isUserNamespace = (namespace == $scope.user.username);
$scope.planRequired = null;
$scope.isUserNamespace = isUserNamespace;
// Determine whether private repositories are allowed for the namespace.
checkPrivateAllowed();
});
$scope.changeNamespace = function(namespace) {
$scope.repo.namespace = namespace;
};
@ -108,65 +92,5 @@
});
});
};
$scope.upgradePlan = function() {
var callbacks = {
'started': function() { $scope.planChanging = true; },
'opened': function() { $scope.planChanging = true; },
'closed': function() { $scope.planChanging = false; },
'success': subscribedToPlan,
'failure': function(resp) {
$('#couldnotsubscribeModal').modal();
$scope.planChanging = false;
}
};
var namespace = $scope.isUserNamespace ? null : $scope.repo.namespace;
PlanService.changePlan($scope, namespace, $scope.planRequired.stripeId, callbacks);
};
var checkPrivateAllowed = function() {
if (!$scope.repo || !$scope.repo.namespace) { return; }
if (!Features.BILLING) {
$scope.checkingPlan = false;
$scope.planRequired = null;
return;
}
$scope.checkingPlan = true;
var isUserNamespace = $scope.isUserNamespace;
ApiService.getPrivateAllowed(isUserNamespace ? null : $scope.repo.namespace).then(function(resp) {
$scope.checkingPlan = false;
if (resp['privateAllowed']) {
$scope.planRequired = null;
return;
}
if (resp['privateCount'] == null) {
// Organization where we are not the admin.
$scope.planRequired = {};
return;
}
// Otherwise, lookup the matching plan.
PlanService.getMinimumPlan(resp['privateCount'] + 1, !isUserNamespace, function(minimum) {
$scope.planRequired = minimum;
});
});
};
var subscribedToPlan = function(sub) {
$scope.planChanging = false;
$scope.subscription = sub;
PlanService.getPlan(sub.plan, function(subscribedPlan) {
$scope.subscribedPlan = subscribedPlan;
$scope.planRequired = null;
checkPrivateAllowed();
});
};
}
})();

View file

@ -126,6 +126,10 @@ function(ApiService, CookieService, $rootScope, Config) {
return userResponse;
};
userService.isUserNamespace = function(namespace) {
return namespace == userResponse.username;
};
// Update the user in the root scope.
userService.updateUserIn($rootScope);

View file

@ -88,30 +88,8 @@
</div>
<!-- Payment -->
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && planRequired.title">
<div class="co-alert co-alert-warning">
In order to make this repository private under
<strong ng-if="isUserNamespace">your personal namespace</strong>
<strong ng-if="!isUserNamespace">organization <b>{{ repo.namespace }}</b></strong>, you will need to upgrade your plan to
<b style="border-bottom: 1px dotted black;" data-html="true"
data-title="{{ '<b>' + planRequired.title + '</b><br>' + planRequired.privateRepos + ' private repositories' }}" bs-tooltip>
{{ planRequired.title }}
</b>.
This will cost $<span>{{ planRequired.price / 100 }}</span>/month.
</div>
<a class="btn btn-primary" ng-click="upgradePlan()" ng-show="!planChanging">Upgrade now</a>
<span ng-if="isUserNamespace && user.organizations.length == 1" style="margin-left: 6px; display: inline-block;">or did you mean to create this repository
under <a href="javascript:void(0)" ng-click="changeNamespace(user.organizations[0].name)"><b>{{ user.organizations[0].name }}</b></a>?</span>
<div class="cor-loader-inline" ng-show="planChanging"></div>
</div>
<div class="cor-loader-inline" ng-show="repo.is_public == '0' && checkingPlan"></div>
<div class="required-plan" ng-show="repo.is_public == '0' && planRequired && !isUserNamespace && !planRequired.title">
<div class="co-alert co-alert-warning">
This organization has reached its private repository limit. Please contact your administrator.
</div>
</div>
<div class="repo-count-checker" namespace="repo.namespace" plan-required="planRequired"
is-enabled="repo.is_public == '0'">
</div>
</div>
</div>