0
0
Fork 0
mirror of https://github.com/healthchecks/healthchecks.git synced 2025-04-03 04:15:29 +00:00

Decouple check's name from slug, allow users to set hand-picked slugs

This commit is contained in:
Pēteris Caune 2023-06-14 15:06:37 +03:00
parent 132873826a
commit 002bc9b083
No known key found for this signature in database
GPG key ID: E28D7679E9A9EDE2
14 changed files with 114 additions and 8 deletions

View file

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Improvements
- Configure logging to log unhandled exceptions to console even when DEBUG=False (#835)
- Make hc.lib.emails raise exceptions when EMAIL_ settings are not set
- Decouple check's name from slug, allow users to set hand-picked slugs
## v2.9.2 - 2023-06-05

View file

@ -69,6 +69,7 @@ class HeadersField(forms.Field):
class NameTagsForm(forms.Form):
name = forms.CharField(max_length=100, required=False)
slug = forms.SlugField(max_length=100, required=False)
tags = forms.CharField(max_length=500, required=False)
desc = forms.CharField(required=False)

View file

@ -14,6 +14,7 @@ class AddCheckTestCase(BaseTestCase):
def _payload(self, **kwargs):
payload = {
"name": "Test",
"slug": "custom-slug",
"tags": "foo bar",
"kind": "simple",
"timeout": "120",
@ -31,7 +32,7 @@ class AddCheckTestCase(BaseTestCase):
check = Check.objects.get()
self.assertEqual(check.project, self.project)
self.assertEqual(check.name, "Test")
self.assertEqual(check.slug, "test")
self.assertEqual(check.slug, "custom-slug")
self.assertEqual(check.tags, "foo bar")
self.assertEqual(check.kind, "simple")
self.assertEqual(check.timeout.total_seconds(), 120)

View file

@ -14,12 +14,13 @@ class UpdateNameTestCase(BaseTestCase):
def test_it_works(self):
self.client.login(username="alice@example.org", password="password")
r = self.client.post(self.url, data={"name": "Alice Was Here"})
form = {"name": "Alice Was Here", "slug": "custom-slug"}
r = self.client.post(self.url, data=form)
self.assertRedirects(r, self.redirect_url)
self.check.refresh_from_db()
self.assertEqual(self.check.name, "Alice Was Here")
self.assertEqual(self.check.slug, "alice-was-here")
self.assertEqual(self.check.slug, "custom-slug")
def test_redirect_preserves_querystring(self):
referer = self.redirect_url + "?tag=foo"

View file

@ -481,7 +481,8 @@ def add_check(request, code):
return HttpResponseBadRequest()
check = Check(project=project)
check.set_name_slug(form.cleaned_data["name"])
check.name = form.cleaned_data["name"]
check.slug = form.cleaned_data["slug"]
check.tags = form.cleaned_data["tags"]
check.kind = form.cleaned_data["kind"]
check.timeout = form.cleaned_data["timeout"]
@ -504,7 +505,8 @@ def update_name(request, code):
form = forms.NameTagsForm(request.POST)
if form.is_valid():
check.set_name_slug(form.cleaned_data["name"])
check.name = form.cleaned_data["name"]
check.slug = form.cleaned_data["slug"]
check.tags = form.cleaned_data["tags"]
check.desc = form.cleaned_data["desc"]
check.save()

View file

@ -0,0 +1,4 @@
.slug-help-block code {
color: var(--text-muted);
background: var(--pre-bg);
}

View file

@ -7,6 +7,7 @@ $(function () {
$("#update-name-form").attr("action", url);
$("#update-name-input").val(this.dataset.name);
$("#update-slug-input").val(this.dataset.slug);
var tagsSelectize = document.getElementById("update-tags-input").selectize;
tagsSelectize.setValue(this.dataset.tags.split(" "));

View file

@ -0,0 +1,40 @@
$(function () {
function slugify(text) {
return text
.normalize("NFKD")
.split("")
.map(ch => ch.charCodeAt(0) < 256 ? ch : "")
.join("")
.toLowerCase()
.replace(/[^\w\s-]/g, "")
.replace(/[-\s]+/g, "-")
.replace(/^-+/, "")
.replace(/-+$/, "");
}
$(".with-slug-suggestions").each(function() {
var nameInput = $("input[name='name']", this);
var slugInput = $("input[name='slug']", this);
var btn = $(".use-suggested-slug", this);
var help = $(".slug-help-block", this);
function update() {
var suggested = slugify(nameInput.val());
if (suggested) {
help.html(`Suggested value: <code>${suggested}</code>`);
} else {
help.text("Allowed characters: a-z, A-Z, 0-9, hyphens, underscores.");
}
btn.attr("disabled", !suggested);
}
$(nameInput).on("keyup change", update);
$(this).on("shown.bs.modal", update);
btn.click(function() {
slugInput.val(slugify(nameInput.val()));
});
});
});

View file

@ -59,6 +59,7 @@
<link rel="stylesheet" href="{% static 'css/signal_form.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/project.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/signup.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/slug-suggestions.css' %}" type="text/css">
{% endcompress %}
</head>
<body class="page-{{ page }}{% if request.user.is_authenticated and request.profile.theme == 'dark' %} dark{% endif%}">

View file

@ -1,4 +1,4 @@
<div id="add-check-modal" class="modal simple">
<div id="add-check-modal" class="modal simple with-slug-suggestions">
<div class="modal-dialog">
<form class="form-horizontal" method="post" action="{% url 'hc-add-check' project.code %}">
{% csrf_token %}
@ -28,6 +28,30 @@
</div>
</div>
<div class="form-group">
<label for="add-check-slug" class="col-sm-3 control-label">
Slug
</label>
<div class="col-sm-9">
<div class="input-group">
<input
id="add-check-slug"
name="slug"
type="text"
maxlength="100"
value="{{ check.slug }}"
pattern="[a-zA-Z0-9_-]+"
class="form-control" />
<span class="input-group-btn">
<button
class="btn btn-default use-suggested-slug"
type="button">Use Suggested</button>
</span>
</div>
<span class="help-block slug-help-block"></span>
</div>
</div>
<div class="form-group">
<label for="add-check-tags" class="col-sm-3 control-label">
Tags

View file

@ -363,5 +363,6 @@
<script src="{% static 'js/ping_details.js' %}"></script>
<script src="{% static 'js/details.js' %}"></script>
<script src="{% static 'js/initialize-timezone-selects.js' %}"></script>
<script src="{% static 'js/slug-suggestions.js' %}"></script>
{% endcompress %}
{% endblock %}

View file

@ -102,5 +102,6 @@
<script src="{% static 'js/checks.js' %}"></script>
<script src="{% static 'js/add-check-modal.js' %}"></script>
<script src="{% static 'js/initialize-timezone-selects.js' %}"></script>
<script src="{% static 'js/slug-suggestions.js' %}"></script>
{% endcompress %}
{% endblock %}

View file

@ -66,6 +66,7 @@
</td>
<td>
<div data-name="{{ check.name }}"
data-slug="{{ check.slug }}"
data-tags="{{ check.tags }}"
data-desc="{{ check.desc }}"
class="my-checks-name {% if not check.name %}unnamed{% endif %}">
@ -77,7 +78,7 @@
</td>
<td class="hidden-xs hidden-sm hidden-md">
{% if project.show_slugs and not check.slug %}
<span class="unavailable">unavailable, set name first</span>
<span class="unavailable">unavailable, slug not set</span>
{% else %}
<span class="my-checks-url">{{ check.url|format_ping_endpoint }}</span>
{% if project.show_slugs and check.slug in ambiguous %}

View file

@ -1,4 +1,4 @@
<div id="update-name-modal" class="modal">
<div id="update-name-modal" class="modal with-slug-suggestions">
<div class="modal-dialog">
<form
id="update-name-form"
@ -33,6 +33,33 @@
</div>
</div>
<div class="form-group">
<label for="update-slug-input" class="col-sm-2 control-label">
Slug
</label>
<div class="col-sm-10">
<div class="input-group">
<input
id="update-slug-input"
name="slug"
type="text"
maxlength="100"
value="{{ check.slug }}"
pattern="[a-zA-Z0-9_-]+"
data-src-id="update-name-input"
data-btn-id="use-suggested"
data-preview-id="slug-help"
class="form-control" />
<span class="input-group-btn">
<button
class="btn btn-default use-suggested-slug"
type="button">Use Suggested</button>
</span>
</div>
<span class="help-block slug-help-block"></span>
</div>
</div>
<div class="form-group">
<label for="update-tags-input" class="col-sm-2 control-label">
Tags