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:
parent
132873826a
commit
002bc9b083
14 changed files with 114 additions and 8 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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()
|
||||
|
|
4
static/css/slug-suggestions.css
Normal file
4
static/css/slug-suggestions.css
Normal file
|
@ -0,0 +1,4 @@
|
|||
.slug-help-block code {
|
||||
color: var(--text-muted);
|
||||
background: var(--pre-bg);
|
||||
}
|
|
@ -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(" "));
|
||||
|
|
40
static/js/slug-suggestions.js
Normal file
40
static/js/slug-suggestions.js
Normal 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()));
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -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%}">
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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 %}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue