From fff0db10e3a26d5403cdca3496df04d8a81b030d Mon Sep 17 00:00:00 2001
From: Cole Helbling <cole.e.helbling@outlook.com>
Date: Wed, 28 Apr 2021 12:16:13 -0700
Subject: [PATCH 1/3] ToJSON: allow custom as_json function

This allows us to modify what the API responds with, which in turn lets us unify
the OpenAPI specification and the actual API's responses.
---
 src/lib/Hydra/Component/ToJSON.pm | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/src/lib/Hydra/Component/ToJSON.pm b/src/lib/Hydra/Component/ToJSON.pm
index e8f439de..727a5b1f 100644
--- a/src/lib/Hydra/Component/ToJSON.pm
+++ b/src/lib/Hydra/Component/ToJSON.pm
@@ -10,6 +10,10 @@ use JSON;
 sub TO_JSON {
     my $self = shift;
 
+    if ($self->can("as_json")) {
+        return $self->as_json();
+    }
+
     my $hint = $self->json_hint;
 
     my %json = ();

From d23f4318899328da91f87ceb428d1f1222d66cd1 Mon Sep 17 00:00:00 2001
From: Cole Helbling <cole.e.helbling@outlook.com>
Date: Wed, 28 Apr 2021 12:32:44 -0700
Subject: [PATCH 2/3] JobsetInputs: update schema to align with the API

`PUT /jobsets/{project-id}/{jobset-id}` expects a JSON object `inputs` which
maps a name to a name, a type, a value, and a boolean that enables emailing
responsible parties. However, `GET /jobsets/{project-id}/{jobset-id}` responds
with an object that doesn't contain a value, but does contain a jobsetinputalts
(which is old and should be unused).

This commit aligns the two by removing the old and unused `jobsetinputalts` from
the response and replaces it with `value`.
---
 hydra-api.yaml                       |  8 +++-----
 src/lib/Hydra/Schema/JobsetInputs.pm | 29 ++++++++++++++--------------
 2 files changed, 18 insertions(+), 19 deletions(-)

diff --git a/hydra-api.yaml b/hydra-api.yaml
index 766cfd0b..a0b56822 100644
--- a/hydra-api.yaml
+++ b/hydra-api.yaml
@@ -589,17 +589,15 @@ components:
         name:
           description: name of the input
           type: string
+        value:
+          description: value of the input
+          type: string
         type:
           description: type of input
           type: string
         emailresponsible:
           description: whether or not to email responsible parties
           type: boolean
-        jobsetinputalts:
-          type: array
-          description: ???
-          items:
-            type: string
 
     Jobset:
       type: object
diff --git a/src/lib/Hydra/Schema/JobsetInputs.pm b/src/lib/Hydra/Schema/JobsetInputs.pm
index d0d1665d..8ac2a560 100644
--- a/src/lib/Hydra/Schema/JobsetInputs.pm
+++ b/src/lib/Hydra/Schema/JobsetInputs.pm
@@ -134,21 +134,22 @@ __PACKAGE__->has_many(
 # Created by DBIx::Class::Schema::Loader v0.07049 @ 2020-02-06 12:22:36
 # DO NOT MODIFY THIS OR ANYTHING ABOVE! md5sum:5uKwEhDXso4IR1TFmwRxiA
 
-my %hint = (
-    string_columns => [
-        "name",
-        "type"
-    ],
-    boolean_columns => [
-        "emailresponsible"
-    ],
-    relations => {
-        "jobsetinputalts" => "value"
-    }
-);
+sub as_json {
+    my $self = shift;
 
-sub json_hint {
-    return \%hint;
+    my ($input) = $self->jobsetinputalts;
+
+    my %json = (
+        # string_columns
+        "name" => $self->get_column("name") // "",
+        "type" => $self->get_column("type") // "",
+        "value" => $input->value // "",
+
+        # boolean_columns
+        "emailresponsible" => $self->get_column("emailresponsible") ? JSON::true : JSON::false,
+    );
+
+    return \%json;
 }
 
 1;

From f1dd5d202ec8962def534088ad1f86e801ec5ba7 Mon Sep 17 00:00:00 2001
From: Cole Helbling <cole.e.helbling@outlook.com>
Date: Wed, 28 Apr 2021 12:41:05 -0700
Subject: [PATCH 3/3] Jobsets: update schema to align with the API

To further align with the API, we return custom JSON in order to display a
`visible` field rather than `hidden` -- a `PUT` request expects `visible`, while
a `GET` request returns `hidden`.

This also allows us to rename the `jobsetinputs` field to `inputs` for the same
reason: `PUT` expects `inputs`, while `GET` returns `jobsetinputs`.
---
 hydra-api.yaml                     |  8 ++--
 src/lib/Hydra/Controller/Jobset.pm |  4 +-
 src/lib/Hydra/Schema/Jobsets.pm    | 66 +++++++++++++++---------------
 src/root/edit-jobset.tt            | 16 ++++----
 t/Controller/Jobset/http.t         | 18 ++++----
 t/api-test.t                       |  6 +--
 6 files changed, 58 insertions(+), 60 deletions(-)

diff --git a/hydra-api.yaml b/hydra-api.yaml
index a0b56822..6b67a9fe 100644
--- a/hydra-api.yaml
+++ b/hydra-api.yaml
@@ -642,8 +642,8 @@ components:
         enableemail:
           description: when true the jobset sends emails when previously-successful builds fail
           type: boolean
-        hidden:
-          description: when false the jobset is visible in the web frontend
+        visible:
+          description: when true the jobset is visible in the web frontend
           type: boolean
         emailoverride:
           description: email address to send notices to instead of the package maintainer (can be a comma separated list)
@@ -667,12 +667,12 @@ components:
           type: integer
         type:
           description: the type of the jobset
-          type: string
+          type: integer
         flake:
           nullable: true
           description: the flake uri to evaluate
           type: string
-        jobsetinputs:
+        inputs:
           description: inputs configured for this jobset
           type: object
           additionalProperties:
diff --git a/src/lib/Hydra/Controller/Jobset.pm b/src/lib/Hydra/Controller/Jobset.pm
index b391e231..24776994 100644
--- a/src/lib/Hydra/Controller/Jobset.pm
+++ b/src/lib/Hydra/Controller/Jobset.pm
@@ -270,8 +270,8 @@ sub updateJobset {
     $jobset->jobsetinputs->delete;
 
     if ($type == 0) {
-        foreach my $name (keys %{$c->stash->{params}->{jobsetinputs}}) {
-            my $inputData = $c->stash->{params}->{jobsetinputs}->{$name};
+        foreach my $name (keys %{$c->stash->{params}->{inputs}}) {
+            my $inputData = $c->stash->{params}->{inputs}->{$name};
             my $type = $inputData->{type};
             my $value = $inputData->{value};
             my $emailresponsible = defined $inputData->{emailresponsible} ? 1 : 0;
diff --git a/src/lib/Hydra/Schema/Jobsets.pm b/src/lib/Hydra/Schema/Jobsets.pm
index ee93f349..fcb5772e 100644
--- a/src/lib/Hydra/Schema/Jobsets.pm
+++ b/src/lib/Hydra/Schema/Jobsets.pm
@@ -410,40 +410,40 @@ __PACKAGE__->add_column(
     "+id" => { retrieve_on_insert => 1 }
 );
 
-my %hint = (
-    columns => [
-        "errortime",
-        "lastcheckedtime",
-        "triggertime",
-        "enabled",
-        "keepnr",
-        "checkinterval",
-        "schedulingshares",
-        "starttime"
-    ],
-    string_columns => [
-        "name",
-        "project",
-        "description",
-        "nixexprinput",
-        "nixexprpath",
-        "errormsg",
-        "emailoverride",
-        "fetcherrormsg",
-        "type",
-        "flake"
-    ],
-    boolean_columns => [
-        "enableemail",
-        "hidden"
-    ],
-    eager_relations => {
-        jobsetinputs => "name"
-    }
-);
+sub as_json {
+    my $self = shift;
 
-sub json_hint {
-    return \%hint;
+    my %json = (
+        # columns
+        "errortime" => $self->get_column("errortime"),
+        "lastcheckedtime" => $self->get_column("lastcheckedtime"),
+        "triggertime" => $self->get_column("triggertime"),
+        "enabled" => $self->get_column("enabled"),
+        "keepnr" => $self->get_column("keepnr"),
+        "checkinterval" => $self->get_column("checkinterval"),
+        "schedulingshares" => $self->get_column("schedulingshares"),
+        "starttime" => $self->get_column("starttime"),
+
+        # string_columns
+        "name" => $self->get_column("name") // "",
+        "project" => $self->get_column("project") // "",
+        "description" => $self->get_column("description") // "",
+        "nixexprinput" => $self->get_column("nixexprinput") // "",
+        "nixexprpath" => $self->get_column("nixexprpath") // "",
+        "errormsg" => $self->get_column("errormsg") // "",
+        "emailoverride" => $self->get_column("emailoverride") // "",
+        "fetcherrormsg" => $self->get_column("fetcherrormsg") // "",
+        "type" => $self->get_column("type") // "",
+        "flake" => $self->get_column("flake") // "",
+
+        # boolean_columns
+        "enableemail" => $self->get_column("enableemail") ? JSON::true : JSON::false,
+        "visible" => $self->get_column("hidden") ? JSON::false : JSON::true,
+
+        "inputs" => { map { $_->name => $_ } $self->jobsetinputs }
+    );
+
+    return \%json;
 }
 
 1;
diff --git a/src/root/edit-jobset.tt b/src/root/edit-jobset.tt
index 4243f1fa..22874815 100644
--- a/src/root/edit-jobset.tt
+++ b/src/root/edit-jobset.tt
@@ -46,8 +46,8 @@
     <thead>
       <tr><th></th><th>Input name</th><th>Type</th><th style="width: 50%">Value</th><th>Notify committers</th></tr>
     </thead>
-    <tbody class="jobsetinputs">
-      [% jobsetinputs = createFromEval ? eval.jobsetevalinputs : jobset.jobsetinputs; FOREACH input IN jobsetinputs %]
+    <tbody class="inputs">
+      [% inputs = createFromEval ? eval.jobsetevalinputs : jobset.jobsetinputs; FOREACH input IN inputs %]
         [% INCLUDE renderJobsetInput input=input baseName="input-$input.name" %]
       [% END %]
       <tr>
@@ -220,8 +220,8 @@
 
   $("#submit-jobset").click(function() {
     var formElements = $(this).parents("form").serializeArray();
-    var data = { 'jobsetinputs': {} };
-    var jobsetinputs = {};
+    var data = { 'inputs': {} };
+    var inputs = {};
     for (var i = 0; formElements.length > i; i++) {
       var elem = formElements[i];
       var match = elem.name.match(/^input-([\w-]+)-(\w+)$/);
@@ -233,13 +233,13 @@
 
         if (baseName === "template") continue;
 
-        if (!(baseName in jobsetinputs))
-            jobsetinputs[baseName] = {};
+        if (!(baseName in inputs))
+            inputs[baseName] = {};
 
         if (param === "name")
-            data.jobsetinputs[elem.value] = jobsetinputs[baseName];
+            data.inputs[elem.value] = inputs[baseName];
         else
-            jobsetinputs[baseName][param] = elem.value;
+            inputs[baseName][param] = elem.value;
       }
     }
     redirectJSON({
diff --git a/t/Controller/Jobset/http.t b/t/Controller/Jobset/http.t
index 0561f3ac..dc11c7a2 100644
--- a/t/Controller/Jobset/http.t
+++ b/t/Controller/Jobset/http.t
@@ -46,7 +46,7 @@ subtest 'Create new jobset "job" as flake type' => sub {
       Cookie => $cookie,
       Content => encode_json({
         enabled => 2,
-        visible => 1,
+        visible => JSON::true,
         name => "job",
         type => 1,
         description => "test jobset",
@@ -76,8 +76,8 @@ subtest 'Read newly-created jobset "job"' => sub {
      errormsg => "",
      fetcherrormsg => "",
      flake => "github:nixos/nix",
-     hidden => JSON::false,
-     jobsetinputs => {},
+     visible => JSON::true,
+     inputs => {},
      keepnr => 3,
      lastcheckedtime => undef,
      name => "job",
@@ -99,12 +99,12 @@ subtest 'Update jobset "job" to legacy type' => sub {
       Cookie => $cookie,
       Content => encode_json({
         enabled => 3,
-        visible => 1,
+        visible => JSON::true,
         name => "job",
         type => 0,
         nixexprinput => "ofborg",
         nixexprpath => "release.nix",
-        jobsetinputs => {
+        inputs => {
           ofborg => {
             name => "ofborg",
             type => "git",
@@ -134,15 +134,13 @@ subtest 'Update jobset "job" to legacy type' => sub {
     errormsg => "",
     fetcherrormsg => "",
     flake => "",
-    hidden => JSON::false,
-    jobsetinputs => {
+    visible => JSON::true,
+    inputs => {
       ofborg => {
         name => "ofborg",
         type => "git",
         emailresponsible => JSON::false,
-        jobsetinputalts => [
-          "https://github.com/NixOS/ofborg.git released"
-        ]
+        value => "https://github.com/NixOS/ofborg.git released"
       }
     },
     keepnr => 1,
diff --git a/t/api-test.t b/t/api-test.t
index cca28243..97e87bcd 100644
--- a/t/api-test.t
+++ b/t/api-test.t
@@ -81,14 +81,14 @@ subtest "projects" => sub {
 };
 
 subtest "jobsets" => sub {
-    my $result = request_json({ uri => '/jobset/sample/default', method => 'PUT', data => { nixexprpath => "default.nix", nixexprinput => "my-src", jobsetinputs => { "my-src" => { type => "path", value => $jobsetdir } }, enabled => "1", visible => "1", checkinterval => "3600"} });
+    my $result = request_json({ uri => '/jobset/sample/default', method => 'PUT', data => { nixexprpath => "default.nix", nixexprinput => "my-src", inputs => { "my-src" => { type => "path", value => $jobsetdir } }, enabled => "1", visible => "1", checkinterval => "3600"} });
     is($result->code(), 201, "PUTting a new jobset creates it");
 
     my $jobset = decode_json(request_json({ uri => '/jobset/sample/default' })->content());
 
-    ok(exists $jobset->{jobsetinputs}->{"my-src"}, "The new jobset has a 'my-src' input");
+    ok(exists $jobset->{inputs}->{"my-src"}, "The new jobset has a 'my-src' input");
 
-    is($jobset->{jobsetinputs}->{"my-src"}->{"jobsetinputalts"}->[0], $jobsetdir, "The 'my-src' input is in $jobsetdir");
+    is($jobset->{inputs}->{"my-src"}->{value}, $jobsetdir, "The 'my-src' input is in $jobsetdir");
 };
 
 subtest "evaluation" => sub {