From ad13d134366a161d1bbbe3e9de7cc38645be54f3 Mon Sep 17 00:00:00 2001
From: Cole Helbling <cole.e.helbling@outlook.com>
Date: Sat, 1 May 2021 23:27:47 -0700
Subject: [PATCH] Project: add declfile, decltype, declvalue to API

This makes it possible to create declarative projects via a PUT request, and
also exposes the currently-configured values to GET requests.
---
 hydra-api.yaml                      | 21 +++++++++++++++
 src/lib/Hydra/Controller/Project.pm |  6 ++---
 src/lib/Hydra/Schema/Projects.pm    | 42 ++++++++++++++++-------------
 src/root/edit-project.tt            | 17 +++++++++++-
 t/Controller/projects.t             |  7 ++++-
 5 files changed, 70 insertions(+), 23 deletions(-)

diff --git a/hydra-api.yaml b/hydra-api.yaml
index a2fb1bc3..2a454942 100644
--- a/hydra-api.yaml
+++ b/hydra-api.yaml
@@ -181,6 +181,10 @@ paths:
                 visible:
                   description: when set to true the project is displayed in the web interface
                   type: boolean
+                declarative:
+                  description: declarative input configured for this project
+                  type: object
+                  $ref: '#/components/schemas/DeclarativeInput'
       responses:
         '400':
           description: bad request
@@ -577,12 +581,29 @@ components:
         enabled:
           description: when set to true the project gets scheduled for evaluation
           type: boolean
+        declarative:
+          description: declarative input configured for this project
+          type: object
+          $ref: '#/components/schemas/DeclarativeInput'
         jobsets:
           description: list of jobsets belonging to this project
           type: array
           items:
             type: string
 
+    DeclarativeInput:
+      type: object
+      properties:
+        file:
+          description: The file in `value` which contains the declarative spec file. Relative to the root of `value`.
+          type: string
+        type:
+          description: The type of the declarative input.
+          type: string
+        value:
+          description: The value of the declarative input.
+          type: string
+
     JobsetInput:
       type: object
       properties:
diff --git a/src/lib/Hydra/Controller/Project.pm b/src/lib/Hydra/Controller/Project.pm
index 961ce837..e6a2dba8 100644
--- a/src/lib/Hydra/Controller/Project.pm
+++ b/src/lib/Hydra/Controller/Project.pm
@@ -157,9 +157,9 @@ sub updateProject {
         , enabled => defined $c->stash->{params}->{enabled} ? 1 : 0
         , hidden => defined $c->stash->{params}->{visible} ? 0 : 1
         , owner => $owner
-        , declfile => trim($c->stash->{params}->{declfile})
-        , decltype => trim($c->stash->{params}->{decltype})
-        , declvalue => trim($c->stash->{params}->{declvalue})
+        , declfile => trim($c->stash->{params}->{declarative}->{file})
+        , decltype => trim($c->stash->{params}->{declarative}->{type})
+        , declvalue => trim($c->stash->{params}->{declarative}->{value})
         });
     if (length($project->declfile)) {
         $project->jobsets->update_or_create(
diff --git a/src/lib/Hydra/Schema/Projects.pm b/src/lib/Hydra/Schema/Projects.pm
index e9857766..2b8e01e7 100644
--- a/src/lib/Hydra/Schema/Projects.pm
+++ b/src/lib/Hydra/Schema/Projects.pm
@@ -246,25 +246,31 @@ __PACKAGE__->many_to_many("usernames", "projectmembers", "username");
 # Created by DBIx::Class::Schema::Loader v0.07049 @ 2021-01-25 14:38:14
 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:+4yWd9UjCyxxLZYDrVUAxA
 
-my %hint = (
-    string_columns => [
-        "name",
-        "displayname",
-        "description",
-        "homepage",
-        "owner"
-    ],
-    boolean_columns => [
-      "enabled",
-      "hidden"
-    ],
-    relations => {
-        jobsets => "name"
-    }
-);
+sub as_json {
+    my $self = shift;
 
-sub json_hint {
-    return \%hint;
+    my %json = (
+        # string_columns
+        "name" => $self->get_column("name") // "",
+        "displayname" => $self->get_column("displayname") // "",
+        "description" => $self->get_column("description") // "",
+        "homepage" => $self->get_column("homepage") // "",
+        "owner" => $self->get_column("owner") // "",
+
+        # boolean_columns
+        "enabled" => $self->get_column("enabled") ? JSON::true : JSON::false,
+        "hidden" => $self->get_column("hidden") ? JSON::true : JSON::false,
+
+        "declarative" => {
+            "file" => $self->get_column("declfile") // "",
+            "type" => $self->get_column("decltype") // "",
+            "value" => $self->get_column("declvalue") // ""
+        },
+
+        "jobsets" => [ map { $_->name } $self->jobsets ]
+    );
+
+    return \%json;
 }
 
 1;
diff --git a/src/root/edit-project.tt b/src/root/edit-project.tt
index 72e3c0bc..6149ec1d 100644
--- a/src/root/edit-project.tt
+++ b/src/root/edit-project.tt
@@ -79,13 +79,28 @@
 
 <script type="text/javascript">
   $("#submit-project").click(function() {
+    var formElements = $(this).parents("form").serializeArray();
+    var data = { 'declarative': {} };
+    var decl = {};
+    for (var i = 0; formElements.length > i; i++) {
+      var elem = formElements[i];
+      var match = elem.name.match(/^decl(file|type|value)$/);
+      if (match === null) {
+        data[elem.name] = elem.value;
+      } else {
+        var param = match[1];
+        decl[param] = elem.value;
+      }
+    }
+    data.declarative = decl;
     redirectJSON({
       [% IF create %]
         url: "[% c.uri_for('/project' '.new') %]",
       [% ELSE %]
         url: "[% c.uri_for('/project' project.name) %]",
       [% END %]
-      data: $(this).parents("form").serialize(),
+      data: JSON.stringify(data),
+      contentType: 'application/json',
       type: 'PUT'
     });
     return false;
diff --git a/t/Controller/projects.t b/t/Controller/projects.t
index 02e60b45..71a37ab4 100644
--- a/t/Controller/projects.t
+++ b/t/Controller/projects.t
@@ -32,7 +32,12 @@ is(decode_json($projectinfo->content), {
     homepage => "",
     jobsets => [],
     name => "tests",
-    owner => "root"
+    owner => "root",
+    declarative => {
+        file => "",
+        type => "",
+        value => ""
+    }
 });
 
 done_testing;