]> git.ipfire.org Git - pbs.git/commitdiff
monitorings: Implement creating/editing/deleting monitorings
authorMichael Tremer <michael.tremer@ipfire.org>
Tue, 23 May 2023 15:14:35 +0000 (15:14 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Tue, 23 May 2023 15:14:35 +0000 (15:14 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
Makefile.am
src/buildservice/releasemonitoring.py
src/templates/monitorings/delete.html [new file with mode: 0644]
src/templates/monitorings/edit.html [new file with mode: 0644]
src/templates/monitorings/show.html
src/templates/packages/name.html
src/web/__init__.py
src/web/monitorings.py

index 3ccfb95d703c9a717439bcba5c06fdc6a3053a80..9974a933ae1d5a5a63d8c656ebae414743fbc614 100644 (file)
@@ -233,6 +233,8 @@ dist_templates_distros_modules_DATA = \
 templates_distros_modulesdir = $(templates_distrosdir)/modules
 
 dist_templates_monitorings_DATA = \
+       src/templates/monitorings/delete.html \
+       src/templates/monitorings/edit.html \
        src/templates/monitorings/show.html
 
 templates_monitoringsdir = $(templatesdir)/monitorings
index 84a5bc1bdcd5e8e5fd66ea70ce735f0c8de21946..05e9fedcdaffbc2585547d4c3d9b9978e4dbe31a 100644 (file)
@@ -100,17 +100,11 @@ class Monitorings(base.Object):
 
                return body
 
-       def _get_monitoring(self, query, *args):
-               res = self.db.get(query, *args)
+       def _get_monitoring(self, query, *args, **kwargs):
+               return self.db.fetch_one(Monitoring, query, *args, **kwargs)
 
-               if res:
-                       return Monitoring(self.backend, res.id, res)
-
-       def _get_monitorings(self, query, *args):
-               res = self.db.query(query, *args)
-
-               for row in res:
-                       yield Monitoring(self.backend, row.id, row)
+       def _get_monitorings(self, query, *args, **kwargs):
+               return self.db.fetch_many(Monitoring, query, *args, **kwargs)
 
        def get_by_id(self, id):
                return self._get_monitoring("""
@@ -135,11 +129,11 @@ class Monitorings(base.Object):
                                distro_id = %s
                        AND
                                name = %s
-                       """, distro, name,
+                       """, distro, name, distro=distro,
                )
 
        async def create(self, distro, name, created_by, project_id,
-                       follow="mainline", create_builds=True):
+                       follow="mainline", create_builds=True, check=True):
                monitoring = self._get_monitoring("""
                        INSERT INTO
                                release_monitorings
@@ -156,11 +150,13 @@ class Monitorings(base.Object):
                        )
                        RETURNING
                                *
-                       """, distro, name, created_by, project_id, follow,
+                       """, distro, name, created_by, project_id, follow, create_builds,
+                       distro=distro,
                )
 
-               # Schedule the first check in the background
-               self.backend.run_task(monitoring.check)
+               # Perform the first check immediately
+               if check:
+                       await monitoring.check()
 
                return monitoring
 
@@ -248,10 +244,16 @@ class Monitoring(base.DataObject):
        def follow(self):
                return self.data.follow
 
-       @property
-       def create_builds(self):
+       # Create Builds
+
+       def get_create_builds(self):
                return self.data.create_builds
 
+       def set_create_builds(self, value):
+               self._set_attribute("create_builds", value)
+
+       create_builds = property(get_create_builds, set_create_builds)
+
        # Permissions
 
        def has_perm(self, user=None):
@@ -262,6 +264,19 @@ class Monitoring(base.DataObject):
                # Users must be admins
                return user.is_admin()
 
+       # Delete
+
+       async def delete(self, user=None):
+               # Mark as deleted
+               self._set_attribute_now("deleted_at")
+               if user:
+                       self._set_attribute("deleted_by", user)
+
+               # Delete all releases
+               async with asyncio.TaskGroup() as tasks:
+                       for release in self.releases:
+                               tasks.create_task(release.delete())
+
        # Check
 
        async def check(self):
@@ -405,8 +420,10 @@ class Monitoring(base.DataObject):
                        Returns True if a build with this version already exists
                """
                # XXX needs to check if we already have a newer version
+               # XXX packages don't have version separately
+               # XXX How do we get this from EVR?
 
-               return version in [build.package.version for build in self.builds]
+               #return version in [build.pkg.version for build in self.builds]
 
        @property
        def builds(self):
@@ -444,6 +461,25 @@ class Release(base.DataObject):
        def created_at(self):
                return self.data.created_at
 
+       # Delete
+
+       async def delete(self, user=None):
+               """
+                       Deletes this release
+               """
+               async with asyncio.TaskGroup() as tasks:
+                       # Delete the build
+                       if self.build:
+                               tasks.create_task(self.build.delete(user=user))
+
+                       # Close the bug
+                       tasks.create_task(
+                               self._close_bug(
+                                       resolution="WONTFIX",
+                                       comment="Release Monitoring for this package has been terminated",
+                               ),
+                       )
+
        # Bug
 
        async def _create_bug(self):
@@ -492,10 +528,24 @@ class Release(base.DataObject):
 
                return bug
 
+       async def _close_bug(self, *args, **kwargs):
+               # Fetch the bug
+               bug = await self.get_bug()
+
+               if bug and not bug.is_closed():
+                       await bug.close(*args, **kwargs)
+
        @property
        def bug_id(self):
                return self.data.bug_id
 
+       async def get_bug(self):
+               """
+                       Fetches the bug from Bugzilla
+               """
+               if self.bug_id:
+                       return await self.backend.bugzilla.get_bug(self.bug_id)
+
        # Build
 
        def get_build(self):
diff --git a/src/templates/monitorings/delete.html b/src/templates/monitorings/delete.html
new file mode 100644 (file)
index 0000000..d8b367f
--- /dev/null
@@ -0,0 +1,47 @@
+{% extends "../modal.html" %}
+
+{% block title %}{{ _("Delete Release Monitoring") }} - {{ monitoring }}{% end block %}
+
+{% block breadcrumbs %}
+       <nav class="breadcrumb" aria-label="breadcrumbs">
+               <ul>
+                       <li>
+                               <a href="/distros">{{ _("Distributions") }}</a>
+                       </li>
+                       <li>
+                               <a href="/distros/{{ monitoring.distro.slug }}">{{ monitoring.distro }}</a>
+                       </li>
+                       <li>
+                               <a href="#" disabled>{{ _("Monitorings") }}</a>
+                       </li>
+                       <li class="is-active">
+                               <a href="/distros/{{ monitoring.distro.slug }}/monitorings/{{ monitoring.name }}"
+                                       aria-current="page">{{ monitoring.name }}</a>
+                       </li>
+               </ul>
+       </nav>
+{% end block %}
+
+{% block modal_title %}
+       <h4 class="title is-4">{{ _("Delete Release Monitoring") }}</h4>
+       <h6 class="subtitle is-6">{{ monitoring }}</h6>
+{% end block %}
+
+{% block modal %}
+       <form method="POST" action="">
+               {% raw xsrf_form_html() %}
+
+               <div class="content">
+                       <p>
+                               {{ _("Are you sure you want to delete %s?") % monitoring }}
+                       </p>
+               </div>
+
+               {# Submit! #}
+               <div class="field">
+                       <button type="submit" class="button is-danger is-fullwidth">
+                               {{ _("Delete") }}
+                       </button>
+               </div>
+       </form>
+{% end block %}
diff --git a/src/templates/monitorings/edit.html b/src/templates/monitorings/edit.html
new file mode 100644 (file)
index 0000000..251687d
--- /dev/null
@@ -0,0 +1,126 @@
+{% extends "../modal.html" %}
+
+{% block title %}
+       {% if monitoring %}
+               {{ _("Edit Release Monitoring") }} - {{ monitoring }}
+       {% else %}
+               {{ _("Create Release Monitoring") }}
+       {% end %}
+{% end block %}
+
+{% block breadcrumbs %}
+       <nav class="breadcrumb" aria-label="breadcrumbs">
+               <ul>
+                       <li>
+                               <a href="/distros">{{ _("Distributions") }}</a>
+                       </li>
+                       <li>
+                               <a href="/distros/{{ distro.slug }}">{{ distro }}</a>
+                       </li>
+                       <li>
+                               <a href="#" disabled>{{ _("Monitorings") }}</a>
+                       </li>
+                       <li class="is-active">
+                               <a href="/distros/{{ distro.slug }}/monitorings/{{ name }}"
+                                       aria-current="page">{{ name }}</a>
+                       </li>
+                       {% if monitoring %}
+                               <li class="is-active">
+                                       <a href="#" aria-current="page">{{ _("Edit") }}</a>
+                               </li>
+                       {% else %}
+                               <li class="is-active">
+                                       <a href="#" aria-current="page">{{ _("Create") }}</a>
+                               </li>
+                       {% end %}
+               </ul>
+       </nav>
+{% end block %}
+
+{% block modal_title %}
+       {% if monitoring %}
+               <h4 class="title is-4">{{ _("Edit Release Monitoring") }}</h4>
+               <h6 class="subtitle is-6">{{ monitoring }}</h6>
+       {% else %}
+               <h4 class="title is-4">{{ _("Create A New Release Monitoring") }}</h4>
+       {% end %}
+{% end block %}
+
+{% block modal %}
+       <form method="POST" action="">
+               {% raw xsrf_form_html() %}
+
+               {# Project & Follow can only be set once #}
+               {% if not monitoring %}
+                       {# Project #}
+                       <div class="field">
+                               <label class="label">{{ _("Project") }}</label>
+                               <div class="control">
+                                       <div class="select">
+                                               <select name="project_id" required>
+                                                       {% for project in projects %}
+                                                               <option value="{{ project.id }}">
+                                                                       {{ project.name }}
+                                                                       - {{ project.homepage or project.ecosystem }}
+                                                                       - {{ project.version }}
+                                                               </option>
+                                                       {% end %}
+                                               </select>
+                                       </div>
+                               </div>
+                               <p class="help">
+                                       {{ _("Select an upstream project that matches this package.") }}
+                               </p>
+                       </div>
+
+                       {% set follows = {
+                               "mainline" : _("Mainline - Follow the latest releases"),
+                       } %}
+
+                       {# Follow #}
+                       <div class="field">
+                               <label class="label">{{ _("Which Release Path To Follow?") }}</label>
+                               <div class="control">
+                                       <div class="select">
+                                               <select name="follow" required>
+                                                       {% for follow in follows %}
+                                                               <option value="{{ follow }}"
+                                                                               {% if monitoring and monitoring.follow == follow %}selected{% end %}>
+                                                                       {{ follows[follow] }}
+                                                               </option>
+                                                       {% end %}
+                                               </select>
+                                       </div>
+                               </div>
+                               <p class="help">
+                                       {{ _("Select which releases to follow.") }}
+                               </p>
+                       </div>
+               {% end %}
+
+               {# Create Builds? #}
+               <div class="field">
+                       <label class="label">{{ _("Builds") }}</label>
+                       <div class="control">
+                               <label class="checkbox">
+                                       <input type="checkbox" name="create_builds"
+                                               {% if (monitoring and monitoring.create_builds) or not monitoring %}checked{% end %}>
+                                       {{ _("Create Builds For Each New Release") }}
+                               </label>
+                       </div>
+               </div>
+
+               {# Submit! #}
+               <div class="field">
+                       {% if monitoring %}
+                               <button type="submit" class="button is-warning is-fullwidth">
+                                       {{ _("Save") }}
+                               </button>
+                       {% else %}
+                               <button type="submit" class="button is-success is-fullwidth">
+                                       {{ _("Create") }}
+                               </button>
+                       {% end %}
+               </div>
+       </form>
+{% end block %}
index ffc292d07cb365d86d8dda568bd6fa42e28c3cde..100a1c809e8925cc012c16f8b7a4c40c3cf4c47e 100644 (file)
                                                <div>
                                                        <p class="heading">{{ _("Last Check") }}</p>
                                                        <p>
-                                                               {{ locale.format_date(monitoring.last_check_at, shorter=True) }}
+                                                               {% if monitoring.last_check_at %}
+                                                                       {{ locale.format_date(monitoring.last_check_at, shorter=True) }}
+                                                               {% else %}
+                                                                       {{ _("N/A") }}
+                                                               {% end %}
                                                        </p>
                                                </div>
                                        </div>
index 4cb594ef7ec72c3d005a1e6f15254b4259dec6f3..b146446882c3d8d493e6558bc3cc46bc3738a7ea 100644 (file)
@@ -65,7 +65,7 @@
                                                        </a>
                                                </div>
                                        {% else %}
-                                               <a class="button is-success" href="{{ monitoring.url }}/create">
+                                               <a class="button is-success" href="/distros/{{ distro.slug }}/monitorings/{{ package.name }}/create">
                                                        {{ _("Enable Release Monitoring") }}
                                                </a>
                                        {% end %}
index 1dba2b1afe46ecf50a997c774c325d79a7d48ef9..e82dfb822d96f1f905f2c868ebf2502f5496e5a4 100644 (file)
@@ -205,6 +205,9 @@ class Application(tornado.web.Application):
                        # Distro Monitorings
                        (r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)", monitorings.ShowHandler),
                        (r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/check", monitorings.CheckHandler),
+                       (r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/create", monitorings.CreateHandler),
+                       (r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/delete", monitorings.DeleteHandler),
+                       (r"/distros/([A-Za-z0-9\-\.]+)/monitorings/([\w\-_]+)/edit", monitorings.EditHandler),
 
                        # Mirrors
                        (r"/mirrors",                                    mirrors.IndexHandler),
index c7b3ac5cfbc3c76d03616d4ea47472a50b72c0a3..ed9ff761af3f48551a7d5c97614848c0c76fbe3c 100644 (file)
@@ -38,7 +38,128 @@ class ShowHandler(base.BaseHandler):
                self.render("monitorings/show.html", monitoring=monitoring)
 
 
-class CheckHandler(base.BaseHandler):
+class CreateHandler(base.BaseHandler):
+       @tornado.web.authenticated
+       async def get(self, slug, name):
+               # Fetch the distribution
+               distro = self.backend.distros.get_by_slug(slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro %s" % slug)
+
+               # Fetch the monitoring
+               monitoring = self.backend.monitorings.get_by_distro_and_name(distro, name)
+               if monitoring:
+                       raise tornado.web.HTTPError(400, "Monitoring for %s in %s already exists" % (name, distro))
+
+               # Check permissions
+               if not self.current_user.is_admin():
+                       raise tornado.web.HTTPError(403)
+
+               # Fetch the search query
+               q = self.get_argument("q", name)
+
+               # Search for projects
+               projects = await self.backend.monitorings.search(q)
+
+               self.render("monitorings/edit.html", monitoring=None, distro=distro, name=name,
+                       projects=projects)
+
+       @tornado.web.authenticated
+       async def post(self, slug, name):
+               # Fetch the distribution
+               distro = self.backend.distros.get_by_slug(slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro %s" % slug)
+
+               # Fetch the monitoring
+               monitoring = self.backend.monitorings.get_by_distro_and_name(distro, name)
+               if monitoring:
+                       raise tornado.web.HTTPError(400, "Monitoring for %s in %s already exists" % (name, distro))
+
+               # Check permissions
+               if not self.current_user.is_admin():
+                       raise tornado.web.HTTPError(403)
+
+               # Collect parameters
+               project_id    = self.get_argument_int("project_id")
+               follow        = self.get_argument("follow")
+               create_builds = self.get_argument_bool("create_builds")
+
+               with self.db.transaction():
+                       try:
+                               # Create a new monitoring
+                               monitoring = await self.backend.monitorings.create(distro, name,
+                                       created_by=self.current_user, project_id=project_id,
+                                       follow=follow, create_builds=create_builds,
+                               )
+
+                       except ValueError as e:
+                               raise tornado.web.HTTPError(400, "%s" % e) from e
+
+               # Redirect to the newly created monitoring
+               self.redirect(monitoring.url)
+
+
+class EditHandler(base.BaseHandler):
+       @tornado.web.authenticated
+       def get(self, slug, name):
+               # Fetch the distribution
+               distro = self.backend.distros.get_by_slug(slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro %s" % slug)
+
+               # Fetch the monitoring
+               monitoring = self.backend.monitorings.get_by_distro_and_name(distro, name)
+               if not monitoring:
+                       raise tornado.web.HTTPError(404, "Could not find monitoring for %s in %s" % (name, distro))
+
+               # Check permissions
+               if not monitoring.has_perm(self.current_user):
+                       raise tornado.web.HTTPError(403)
+
+               self.render("monitorings/edit.html", monitoring=monitoring, distro=distro, name=name)
+
+       @tornado.web.authenticated
+       def post(self, slug, name):
+               # Fetch the distribution
+               distro = self.backend.distros.get_by_slug(slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro %s" % slug)
+
+               # Fetch the monitoring
+               monitoring = self.backend.monitorings.get_by_distro_and_name(distro, name)
+               if not monitoring:
+                       raise tornado.web.HTTPError(404, "Could not find monitoring for %s in %s" % (name, distro))
+
+               # Check permissions
+               if not monitoring.has_perm(self.current_user):
+                       raise tornado.web.HTTPError(403)
+
+               with self.db.transaction():
+                       monitoring.create_builds = self.get_argument_bool("create_builds")
+
+               self.redirect(monitoring.url)
+
+
+class DeleteHandler(base.BaseHandler):
+       @tornado.web.authenticated
+       def get(self, slug, name):
+               # Fetch the distribution
+               distro = self.backend.distros.get_by_slug(slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro %s" % slug)
+
+               # Fetch the monitoring
+               monitoring = self.backend.monitorings.get_by_distro_and_name(distro, name)
+               if not monitoring:
+                       raise tornado.web.HTTPError(404, "Could not find monitoring for %s in %s" % (name, distro))
+
+               # Check permissions
+               if not monitoring.has_perm(self.current_user):
+                       raise tornado.web.HTTPError(403)
+
+               self.render("monitorings/delete.html", monitoring=monitoring)
+
        @tornado.web.authenticated
        async def post(self, slug, name):
                # Fetch the distribution
@@ -55,9 +176,31 @@ class CheckHandler(base.BaseHandler):
                if not monitoring.has_perm(self.current_user):
                        raise tornado.web.HTTPError(403)
 
-               # Perform check
                with self.db.transaction():
-                       await monitoring.check()
+                       await monitoring.delete(self.current_user)
+
+               self.redirect("/packages/%s" % monitoring.name)
+
+
+class CheckHandler(base.BaseHandler):
+       @tornado.web.authenticated
+       async def post(self, slug, name):
+               # Fetch the distribution
+               distro = self.backend.distros.get_by_slug(slug)
+               if not distro:
+                       raise tornado.web.HTTPError(404, "Could not find distro %s" % slug)
+
+               # Fetch the monitoring
+               monitoring = self.backend.monitorings.get_by_distro_and_name(distro, name)
+               if not monitoring:
+                       raise tornado.web.HTTPError(404, "Could not find monitoring for %s in %s" % (name, distro))
+
+               # Check permissions
+               if not monitoring.has_perm(self.current_user):
+                       raise tornado.web.HTTPError(403)
+
+               # Perform check (it is already starting its own transaction)
+               await monitoring.check()
 
                # Redirect back
                self.redirect(monitoring.url)