mirror of
https://github.com/healthchecks/healthchecks.git
synced 2025-04-11 15:51:19 +00:00
All plans now have team access, but different team size limits.
This commit is contained in:
parent
1bae89e405
commit
0723476a0c
11 changed files with 78 additions and 44 deletions
|
@ -1,6 +1,7 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin
|
||||
from django.contrib.auth.models import User
|
||||
from django.db.models import Count
|
||||
from django.template.loader import render_to_string
|
||||
from django.urls import reverse
|
||||
from django.utils.safestring import mark_safe
|
||||
|
@ -25,7 +26,7 @@ class ProfileFieldset(Fieldset):
|
|||
|
||||
class TeamFieldset(Fieldset):
|
||||
name = "Team"
|
||||
fields = ("team_name", "team_access_allowed", "check_limit",
|
||||
fields = ("team_name", "team_limit", "check_limit",
|
||||
"ping_log_limit", "sms_limit", "sms_sent", "last_sms_date",
|
||||
"bill_to")
|
||||
|
||||
|
@ -41,17 +42,23 @@ class ProfileAdmin(admin.ModelAdmin):
|
|||
readonly_fields = ("user", "email")
|
||||
raw_id_fields = ("current_team", )
|
||||
list_select_related = ("user", )
|
||||
list_display = ("id", "users", "checks", "team_access_allowed",
|
||||
list_display = ("id", "users", "checks", "invited",
|
||||
"reports_allowed", "ping_log_limit", "sms")
|
||||
search_fields = ["id", "user__email"]
|
||||
list_filter = ("team_access_allowed", "reports_allowed",
|
||||
list_filter = ("team_access_allowed", "team_limit", "reports_allowed",
|
||||
"check_limit", "next_report_date")
|
||||
|
||||
fieldsets = (ProfileFieldset.tuple(), TeamFieldset.tuple())
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(ProfileAdmin, self).get_queryset(request)
|
||||
qs = qs.annotate(Count("member", distinct=True))
|
||||
qs = qs.annotate(Count("user__check", distinct=True))
|
||||
return qs
|
||||
|
||||
@mark_safe
|
||||
def users(self, obj):
|
||||
if obj.member_set.count() == 0:
|
||||
if obj.member__count == 0:
|
||||
return obj.user.email
|
||||
else:
|
||||
return render_to_string("admin/profile_list_team.html", {
|
||||
|
@ -60,7 +67,7 @@ class ProfileAdmin(admin.ModelAdmin):
|
|||
|
||||
@mark_safe
|
||||
def checks(self, obj):
|
||||
num_checks = Check.objects.filter(user=obj.user).count()
|
||||
num_checks = obj.user__check__count
|
||||
pct = 100 * num_checks / max(obj.check_limit, 1)
|
||||
pct = min(100, int(pct))
|
||||
|
||||
|
@ -69,6 +76,9 @@ class ProfileAdmin(admin.ModelAdmin):
|
|||
%d of %d
|
||||
""" % (pct, num_checks, obj.check_limit)
|
||||
|
||||
def invited(self, obj):
|
||||
return "%d of %d" % (obj.member__count, obj.team_limit)
|
||||
|
||||
def sms(self, obj):
|
||||
return "%d of %d" % (obj.sms_sent, obj.sms_limit)
|
||||
|
||||
|
|
20
hc/accounts/migrations/0010_profile_team_limit.py
Normal file
20
hc/accounts/migrations/0010_profile_team_limit.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.4 on 2017-09-02 11:52
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0009_auto_20170714_1734'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='profile',
|
||||
name='team_limit',
|
||||
field=models.IntegerField(default=2),
|
||||
),
|
||||
]
|
|
@ -28,6 +28,7 @@ class ProfileManager(models.Manager):
|
|||
# If not using payments, set high limits
|
||||
profile.check_limit = 500
|
||||
profile.sms_limit = 500
|
||||
profile.team_limit = 500
|
||||
|
||||
profile.save()
|
||||
return profile
|
||||
|
@ -49,6 +50,7 @@ class Profile(models.Model):
|
|||
last_sms_date = models.DateTimeField(null=True, blank=True)
|
||||
sms_limit = models.IntegerField(default=0)
|
||||
sms_sent = models.IntegerField(default=0)
|
||||
team_limit = models.IntegerField(default=2)
|
||||
|
||||
objects = ProfileManager()
|
||||
|
||||
|
@ -121,6 +123,9 @@ class Profile(models.Model):
|
|||
|
||||
emails.report(self.user.email, ctx)
|
||||
|
||||
def can_invite(self):
|
||||
return self.member_set.count() < self.team_limit
|
||||
|
||||
def invite(self, user):
|
||||
member = Member(team=self, user=user)
|
||||
member.save()
|
||||
|
|
|
@ -75,15 +75,18 @@ class ProfileTestCase(BaseTestCase):
|
|||
|
||||
# And an email should have been sent
|
||||
subj = ('You have been invited to join'
|
||||
' alice@example.org on {0}'.format(getattr(settings, "SITE_NAME")))
|
||||
' alice@example.org on %s' % settings.SITE_NAME)
|
||||
self.assertEqual(mail.outbox[0].subject, subj)
|
||||
|
||||
def test_add_team_member_checks_team_access_allowed_flag(self):
|
||||
self.client.login(username="charlie@example.org", password="password")
|
||||
def test_it_checks_team_size(self):
|
||||
self.profile.team_limit = 0
|
||||
self.profile.save()
|
||||
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
|
||||
form = {"invite_team_member": "1", "email": "frank@example.org"}
|
||||
r = self.client.post("/accounts/profile/", form)
|
||||
assert r.status_code == 403
|
||||
self.assertEqual(r.status_code, 403)
|
||||
|
||||
def test_it_removes_team_member(self):
|
||||
self.client.login(username="alice@example.org", password="password")
|
||||
|
@ -107,13 +110,6 @@ class ProfileTestCase(BaseTestCase):
|
|||
self.alice.profile.refresh_from_db()
|
||||
self.assertEqual(self.alice.profile.team_name, "Alpha Team")
|
||||
|
||||
def test_set_team_name_checks_team_access_allowed_flag(self):
|
||||
self.client.login(username="charlie@example.org", password="password")
|
||||
|
||||
form = {"set_team_name": "1", "team_name": "Charlies Team"}
|
||||
r = self.client.post("/accounts/profile/", form)
|
||||
assert r.status_code == 403
|
||||
|
||||
def test_it_switches_to_own_team(self):
|
||||
self.client.login(username="bob@example.org", password="password")
|
||||
|
||||
|
|
|
@ -189,7 +189,7 @@ def profile(request):
|
|||
elif "show_api_key" in request.POST:
|
||||
ctx["show_api_key"] = True
|
||||
elif "invite_team_member" in request.POST:
|
||||
if not profile.team_access_allowed:
|
||||
if not profile.can_invite():
|
||||
return HttpResponseForbidden()
|
||||
|
||||
form = InviteTeamMemberForm(request.POST)
|
||||
|
@ -220,9 +220,6 @@ def profile(request):
|
|||
ctx["team_member_removed"] = email
|
||||
ctx["team_status"] = "info"
|
||||
elif "set_team_name" in request.POST:
|
||||
if not profile.team_access_allowed:
|
||||
return HttpResponseForbidden()
|
||||
|
||||
form = TeamNameForm(request.POST)
|
||||
if form.is_valid():
|
||||
profile.team_name = form.cleaned_data["team_name"]
|
||||
|
|
|
@ -34,5 +34,5 @@ class CancelPlanTestCase(BaseTestCase):
|
|||
profile = Profile.objects.get(user=self.alice)
|
||||
self.assertEqual(profile.ping_log_limit, 100)
|
||||
self.assertEqual(profile.check_limit, 20)
|
||||
self.assertEqual(profile.team_limit, 2)
|
||||
self.assertEqual(profile.sms_limit, 0)
|
||||
self.assertFalse(profile.team_access_allowed)
|
||||
|
|
|
@ -28,7 +28,6 @@ class CreatePlanTestCase(BaseTestCase):
|
|||
def test_it_works(self, mock):
|
||||
self._setup_mock(mock)
|
||||
|
||||
self.profile.team_access_allowed = False
|
||||
self.profile.sms_limit = 0
|
||||
self.profile.sms_sent = 1
|
||||
self.profile.save()
|
||||
|
@ -47,9 +46,9 @@ class CreatePlanTestCase(BaseTestCase):
|
|||
self.profile.refresh_from_db()
|
||||
self.assertEqual(self.profile.ping_log_limit, 1000)
|
||||
self.assertEqual(self.profile.check_limit, 500)
|
||||
self.assertEqual(self.profile.team_limit, 9)
|
||||
self.assertEqual(self.profile.sms_limit, 50)
|
||||
self.assertEqual(self.profile.sms_sent, 0)
|
||||
self.assertTrue(self.profile.team_access_allowed)
|
||||
|
||||
# braintree.Subscription.cancel should have not been called
|
||||
assert not mock.Subscription.cancel.called
|
||||
|
|
|
@ -110,6 +110,7 @@ def create_plan(request):
|
|||
if plan_id == "P5":
|
||||
profile.ping_log_limit = 1000
|
||||
profile.check_limit = 500
|
||||
profile.team_limit = 9
|
||||
profile.sms_limit = 50
|
||||
profile.sms_sent = 0
|
||||
profile.team_access_allowed = True
|
||||
|
@ -117,6 +118,7 @@ def create_plan(request):
|
|||
elif plan_id == "P50":
|
||||
profile.ping_log_limit = 1000
|
||||
profile.check_limit = 500
|
||||
profile.team_limit = 500
|
||||
profile.sms_limit = 500
|
||||
profile.sms_sent = 0
|
||||
profile.team_access_allowed = True
|
||||
|
@ -169,6 +171,7 @@ def cancel_plan(request):
|
|||
profile = request.user.profile
|
||||
profile.ping_log_limit = 100
|
||||
profile.check_limit = 20
|
||||
profile.team_limit = 2
|
||||
profile.sms_limit = 0
|
||||
profile.team_access_allowed = False
|
||||
profile.save()
|
||||
|
|
|
@ -15,7 +15,6 @@ class BaseTestCase(TestCase):
|
|||
self.alice.save()
|
||||
|
||||
self.profile = Profile(user=self.alice, api_key="abc")
|
||||
self.profile.team_access_allowed = True
|
||||
self.profile.sms_limit = 50
|
||||
self.profile.save()
|
||||
|
||||
|
|
|
@ -134,23 +134,25 @@
|
|||
Share access to your checks and configured integrations
|
||||
without having to share a login.
|
||||
</p>
|
||||
{% if not profile.team_access_allowed %}
|
||||
<p>
|
||||
To enable team access, please upgrade to
|
||||
one of the <a href="{% url 'hc-pricing' %}">paid plans</a>.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<br />
|
||||
|
||||
{% if profile.team_access_allowed %}
|
||||
{% if not profile.can_invite %}
|
||||
<div class="alert alert-info">
|
||||
<strong>Team size limit reached.</strong>
|
||||
To invite more members to your team, please
|
||||
<a href="{% url 'hc-pricing' %}">upgrade your account!</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<a
|
||||
href="#"
|
||||
class="btn btn-default"
|
||||
data-toggle="modal"
|
||||
data-target="#set-team-name-modal">Set Team Name</a>
|
||||
|
||||
{% if profile.can_invite %}
|
||||
<a
|
||||
href="#"
|
||||
class="btn btn-primary pull-right"
|
||||
|
|
|
@ -70,7 +70,7 @@
|
|||
<li class="list-group-item"><i class="fa fa-check"></i> 20 Checks</li>
|
||||
<li class="list-group-item">100 log entries per check</li>
|
||||
<li class="list-group-item"><i class="fa fa-check"></i> Personal or Commercial use</li>
|
||||
<li class="list-group-item">Single User</li>
|
||||
<li class="list-group-item">3 Team Members</li>
|
||||
<li class="list-group-item"> </li>
|
||||
<li class="list-group-item"> </li>
|
||||
</ul>
|
||||
|
@ -105,7 +105,7 @@
|
|||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">Personal or Commercial use</li>
|
||||
<li class="list-group-item">Team Access</li>
|
||||
<li class="list-group-item">10 Team Members</li>
|
||||
<li class="list-group-item">50 SMS alerts per month</li>
|
||||
<li class="list-group-item">Email Support</li>
|
||||
</ul>
|
||||
|
@ -147,7 +147,7 @@
|
|||
<li class="list-group-item">Unlimited Checks</li>
|
||||
<li class="list-group-item">1000 log entries per check</li>
|
||||
<li class="list-group-item">Personal or Commercial use</li>
|
||||
<li class="list-group-item">Team Access</li>
|
||||
<li class="list-group-item">Unlimited Team Members</li>
|
||||
<li class="list-group-item">500 SMS alerts per month</li>
|
||||
<li class="list-group-item">Priority Email Support</li>
|
||||
</ul>
|
||||
|
@ -213,6 +213,19 @@
|
|||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h1>Premium Features</h1>
|
||||
<h2>What's "3 / 10 / Unlimited Team Members"?</h2>
|
||||
<p>
|
||||
Invite your colleagues
|
||||
to your account so they can access your checks,
|
||||
logs, and configured integrations. Inviting team members
|
||||
is <strong>more convenient and more secure</strong>
|
||||
than sharing a single login and password.
|
||||
</p>
|
||||
<p>
|
||||
Each plan has a specific team size limit. When you reach
|
||||
the limit, you cannot invite more team members.
|
||||
</p>
|
||||
|
||||
<h2>What is the "log entries per check" number?</h2>
|
||||
<p>
|
||||
For each of your checks, healthchecks.io keeps a
|
||||
|
@ -236,16 +249,6 @@
|
|||
log entries will only cover 8 hours.
|
||||
</p>
|
||||
|
||||
<h2>What's Team Access?</h2>
|
||||
<p>
|
||||
With Team Access enabled, you can "invite" your colleagues
|
||||
to your account. They will be able to access your checks,
|
||||
logs, and configured integrations.
|
||||
</p>
|
||||
<p>
|
||||
Team Access is more convenient and more secure than
|
||||
sharing a single login and password.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
Loading…
Add table
Reference in a new issue