diff --git a/CHANGELOG.md b/CHANGELOG.md index bc543051..bdb0954d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ All notable changes to this project will be documented in this file. - Autofocus the email field in the signup form, and submit on enter key - Add support for OpsGenie EU region (#294) - Update OpsGenie logo and setup illustrations +- Add a "Create a Copy" function for cloning checks (#288) ### Bug Fixes - Prevent double-clicking the submit button in signup form diff --git a/hc/front/tests/test_copy.py b/hc/front/tests/test_copy.py new file mode 100644 index 00000000..da8ff8d8 --- /dev/null +++ b/hc/front/tests/test_copy.py @@ -0,0 +1,18 @@ +from hc.api.models import Channel, Check +from hc.test import BaseTestCase + + +class CopyCheckTestCase(BaseTestCase): + def setUp(self): + super(CopyCheckTestCase, self).setUp() + self.check = Check(project=self.project) + self.check.name = "Foo" + self.check.save() + + self.copy_url = "/checks/%s/copy/" % self.check.code + + def test_it_works(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.post(self.copy_url, follow=True) + self.assertContains(r, "This is a brand new check") + self.assertContains(r, "Foo (copy)") diff --git a/hc/front/urls.py b/hc/front/urls.py index f0e3fc68..a74f3183 100644 --- a/hc/front/urls.py +++ b/hc/front/urls.py @@ -13,6 +13,7 @@ check_urls = [ path("status/", views.status_single, name="hc-status-single"), path("last_ping/", views.ping_details, name="hc-last-ping"), path("transfer/", views.transfer, name="hc-transfer"), + path("copy/", views.copy, name="hc-copy"), path( "channels/<uuid:channel_code>/enabled", views.switch_channel, diff --git a/hc/front/views.py b/hc/front/views.py index 07bcf56e..5dc2184c 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -483,6 +483,7 @@ def details(request, code): "timezones": pytz.all_timezones, "downtimes": check.downtimes(months=3), "is_new": "new" in request.GET, + "is_copied": "copied" in request.GET, } return render(request, "front/details.html", ctx) @@ -513,6 +514,27 @@ def transfer(request, code): return render(request, "front/transfer_modal.html", ctx) +@require_POST +@login_required +def copy(request, code): + check = _get_check_for_user(request, code) + + copied = Check(project=check.project) + copied.name = check.name + " (copy)" + copied.desc, copied.tags = check.desc, check.tags + copied.subject = check.subject + + copied.kind = check.kind + copied.timeout, copied.grace = check.timeout, check.grace + copied.schedule, copied.tz = check.schedule, check.tz + copied.save() + + copied.channel_set.add(*check.channel_set.all()) + + url = reverse("hc-details", args=[copied.code]) + return redirect(url + "?copied") + + @login_required def status_single(request, code): check = _get_check_for_user(request, code) diff --git a/static/css/details.css b/static/css/details.css index 92066b0d..218112bf 100644 --- a/static/css/details.css +++ b/static/css/details.css @@ -103,3 +103,25 @@ text-align: center; padding: 32px; } + + +ul.checkmarks { + padding-left: 20px; + list-style: none; + color: #117a3f; +} + +ul.checkmarks li:before { + content: '✔ '; +} + + +ul.crosses { + padding-left: 20px; + list-style: none; + color: #aa413e; +} + +ul.crosses li:before { + content: '✘ '; +} \ No newline at end of file diff --git a/templates/front/copy_modal.html b/templates/front/copy_modal.html new file mode 100644 index 00000000..91c7924c --- /dev/null +++ b/templates/front/copy_modal.html @@ -0,0 +1,32 @@ +<div id="copy-modal" class="modal"> + <div class="modal-dialog"> + <form action="{% url 'hc-copy' check.code %}" method="post"> + {% csrf_token %} + <div class="modal-content"> + <div class="modal-header"> + <button type="button" class="close" data-dismiss="modal">×</button> + <h4>Create a Copy of This Check</h4> + </div> + <div class="modal-body"> + <p>You are about to <strong>create a new check based on this + check's configuration</strong>. The following items will + get copied:</p> + <ul class="checkmarks"> + <li>Name, tags and description</li> + <li>Schedule</li> + <li>Assigned notification methods</li> + </ul> + <p>The following items <em>will not</em> be copied:</p> + <ul class="crosses"> + <li>Its URL (a new URL will be generated)</li> + <li>The log of already received pings</li> + </ul> + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> + <button type="submit" class="btn btn-primary">Create a Copy</button> + </div> + </div> + </form> + </div> +</div> \ No newline at end of file diff --git a/templates/front/details.html b/templates/front/details.html index ffa13f90..17c26238 100644 --- a/templates/front/details.html +++ b/templates/front/details.html @@ -11,12 +11,23 @@ <div class="col-sm-12"> <p id="new-check-alert" class="alert alert-success"> <strong>Your new check is ready!</strong> - You can now - <a data-target="edit-name" href="#" >give it a name</a> - or + You can now + <a data-target="edit-name" href="#" >give it a name</a> + or <a data-target="edit-timeout" href="#" >set its schedule</a>. </p> - </div> + </div> + {% endif %} + + {% if is_copied %} + <div class="col-sm-12"> + <p id="new-check-alert" class="alert alert-success"> + <strong>Copy created!</strong> + This is a brand new check, with details copied over from your existing check. + You might now want to + <a data-target="edit-name" href="#">update its name and tags</a>. + </p> + </div> {% endif %} {% if messages %} @@ -196,15 +207,21 @@ <div class="details-block"> <h2>Danger Zone</h2> - <p>Transfer to a different project, or permanently remove this check.</p> + <p>Copy, Transfer, or permanently remove this check.</p> <div class="text-right"> + <button + id="copy-btn" + data-toggle="modal" + data-target="#copy-modal" + class="btn btn-sm btn-default">Create a Copy…</button> <button id="transfer-btn" data-toggle="modal" data-target="#transfer-modal" data-url="{% url 'hc-transfer' check.code %}" class="btn btn-sm btn-default">Transfer to Another Project…</button> + <button id="details-remove-check" data-toggle="modal" @@ -223,13 +240,13 @@ <label class="btn btn-default btn-xs" data-format="UTC"> <input type="radio" name="date-format"> UTC - </label> + </label> {% if check.kind == "cron" and check.tz != "UTC" %} <label class="btn btn-default btn-xs" data-format="{{ check.tz }}"> <input type="radio" name="date-format"> {{ check.tz }} - </label> + </label> {% endif %} <label class="btn btn-default btn-xs active" data-format="local"> @@ -263,6 +280,7 @@ {% include "front/show_usage_modal.html" %} {% include "front/remove_check_modal.html" %} {% include "front/email_settings_modal.html" %} +{% include "front/copy_modal.html" %} {% endblock %} diff --git a/templates/front/remove_check_modal.html b/templates/front/remove_check_modal.html index e967ccf7..79bf8f76 100644 --- a/templates/front/remove_check_modal.html +++ b/templates/front/remove_check_modal.html @@ -1,10 +1,6 @@ <div id="remove-check-modal" class="modal"> <div class="modal-dialog"> - <form - id="remove-check-form" - {% if check %}action="{% url 'hc-remove-check' check.code %}"{% endif %} - method="post"> - + <form action="{% url 'hc-remove-check' check.code %}" method="post"> {% csrf_token %} <div class="modal-content"> <div class="modal-header">