~sircmpwn/sr.ht-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
3 3

[PATCH builds.sr.ht] Implement job visibility

Details
Message ID
<20230313162930.26065-1-adnan@maolood.com>
DKIM signature
missing
Download raw message
Patch: +356 -31
This implements visibility for build jobs. The visibility can be set
when submitting a build, and can also be changed retroactively from a
new job settings page.
---
Now that we have a settings page, we could also add a way to delete
jobs.

 api/graph/model/job.go                        | 16 ++--
 api/graph/schema.graphqls                     |  9 ++-
 api/graph/schema.resolvers.go                 | 30 ++++++--
 api/loaders/middleware.go                     | 10 ++-
 .../ae3544d6450a_add_visibility_to_job.py     | 41 ++++++++++
 buildsrht/app.py                              |  2 +
 buildsrht/blueprints/jobs.py                  | 33 ++++++--
 buildsrht/blueprints/settings.py              | 42 +++++++++++
 buildsrht/templates/job-details.html          | 75 +++++++++++++++++++
 buildsrht/templates/job.html                  | 41 ++++++++--
 buildsrht/templates/settings.html             | 31 ++++++++
 buildsrht/templates/submit.html               | 40 ++++++++++
 buildsrht/types/__init__.py                   |  2 +-
 buildsrht/types/job.py                        |  6 ++
 schema.sql                                    |  9 ++-
 15 files changed, 356 insertions(+), 31 deletions(-)
 create mode 100644 buildsrht/alembic/versions/ae3544d6450a_add_visibility_to_job.py
 create mode 100644 buildsrht/blueprints/settings.py
 create mode 100644 buildsrht/templates/job-details.html
 create mode 100644 buildsrht/templates/settings.html

diff --git a/api/graph/model/job.go b/api/graph/model/job.go
index aa465a1..379aaeb 100644
--- a/api/graph/model/job.go
+++ b/api/graph/model/job.go
@@ -15,13 +15,14 @@ import (
)

type Job struct {
	ID       int       `json:"id"`
	Created  time.Time `json:"created"`
	Updated  time.Time `json:"updated"`
	Manifest string    `json:"manifest"`
	Note     *string   `json:"note"`
	Image    string    `json:"image"`
	Runner   *string   `json:"runner"`
	ID         int        `json:"id"`
	Created    time.Time  `json:"created"`
	Updated    time.Time  `json:"updated"`
	Manifest   string     `json:"manifest"`
	Note       *string    `json:"note"`
	Image      string     `json:"image"`
	Runner     *string    `json:"runner"`
	Visibility Visibility `json:"visibility"`

	OwnerID    int
	JobGroupID *int
@@ -75,6 +76,7 @@ func (j *Job) Fields() *database.ModelFields {
			{"tags", "tags", &j.RawTags},
			{"status", "status", &j.RawStatus},
			{"image", "image", &j.Image},
			{"visibility", "visibility", &j.Visibility},

			// Always fetch:
			{"id", "", &j.ID},
diff --git a/api/graph/schema.graphqls b/api/graph/schema.graphqls
index d596000..c327770 100644
--- a/api/graph/schema.graphqls
+++ b/api/graph/schema.graphqls
@@ -103,6 +103,12 @@ enum JobStatus {
  CANCELLED
}

enum Visibility {
  PUBLIC
  UNLISTED
  PRIVATE
}

type Job {
  id: Int!
  created: Time!
@@ -111,6 +117,7 @@ type Job {
  manifest: String!
  note: String
  tags: [String!]!
  visibility: Visibility!

  "Name of the build image"
  image: String!
@@ -437,7 +444,7 @@ type Mutation {
  executed immediately if unspecified.
  """
  submit(manifest: String!, tags: [String!] note: String, secrets: Boolean,
    execute: Boolean): Job! @access(scope: JOBS, kind: RW)
    execute: Boolean, visibility: Visibility! = UNLISTED): Job! @access(scope: JOBS, kind: RW)

  "Queues a pending job."
  start(jobID: Int!): Job @access(scope: JOBS, kind: RW)
diff --git a/api/graph/schema.resolvers.go b/api/graph/schema.resolvers.go
index 4f65af3..91c5910 100644
--- a/api/graph/schema.resolvers.go
+++ b/api/graph/schema.resolvers.go
@@ -189,6 +189,7 @@ func (r *jobGroupResolver) Owner(ctx context.Context, obj *model.JobGroup) (mode

// Jobs is the resolver for the jobs field.
func (r *jobGroupResolver) Jobs(ctx context.Context, obj *model.JobGroup) ([]*model.Job, error) {
	user := auth.ForContext(ctx)
	var jobs []*model.Job
	if err := database.WithTx(ctx, &sql.TxOptions{
		Isolation: 0,
@@ -198,7 +199,13 @@ func (r *jobGroupResolver) Jobs(ctx context.Context, obj *model.JobGroup) ([]*mo
		rows, err := database.
			Select(ctx, job).
			From(`job j`).
			Where(`j.job_group_id = ?`, obj.ID).
			Where(sq.And{
				sq.Expr(`j.job_group_id = ?`, obj.ID),
				sq.Or{
					sq.Expr(`j.owner_id = ?`, user.UserID),
					sq.Expr(`j.visibility = 'PUBLIC'`),
				},
			}).
			RunWith(tx).
			QueryContext(ctx)
		if err != nil {
@@ -256,7 +263,7 @@ func (r *jobGroupResolver) Triggers(ctx context.Context, obj *model.JobGroup) ([
}

// Submit is the resolver for the submit field.
func (r *mutationResolver) Submit(ctx context.Context, manifest string, tags []string, note *string, secrets *bool, execute *bool) (*model.Job, error) {
func (r *mutationResolver) Submit(ctx context.Context, manifest string, tags []string, note *string, secrets *bool, execute *bool, visibility model.Visibility) (*model.Job, error) {
	man, err := LoadManifest(manifest)
	if err != nil {
		return nil, err
@@ -288,19 +295,19 @@ func (r *mutationResolver) Submit(ctx context.Context, manifest string, tags []s
		// TODO: Refactor tags into a pg array
		row := tx.QueryRowContext(ctx, `INSERT INTO job (
			created, updated,
			manifest, owner_id, secrets, note, tags, image, status
			manifest, owner_id, secrets, note, tags, image, status, visibility
		) VALUES (
			NOW() at time zone 'utc',
			NOW() at time zone 'utc',
			$1, $2, $3, $4, $5, $6, $7
			$1, $2, $3, $4, $5, $6, $7, $8
		) RETURNING
			id, created, updated, manifest, note, image, runner, owner_id,
			tags, status
		`, manifest, user.UserID, sec, note, tags, man.Image, status)
			tags, status, visibility
		`, manifest, user.UserID, sec, note, tags, man.Image, status, visibility)

		if err := row.Scan(&job.ID, &job.Created, &job.Updated, &job.Manifest,
			&job.Note, &job.Image, &job.Runner, &job.OwnerID, &job.RawTags,
			&job.RawStatus); err != nil {
			&job.RawStatus, &job.Visibility); err != nil {
			return err
		}

@@ -928,6 +935,7 @@ func (r *userResolver) Jobs(ctx context.Context, obj *model.User, cursor *coremo
		cursor = coremodel.NewCursor(nil)
	}

	user := auth.ForContext(ctx)
	var jobs []*model.Job
	if err := database.WithTx(ctx, &sql.TxOptions{
		Isolation: 0,
@@ -937,7 +945,13 @@ func (r *userResolver) Jobs(ctx context.Context, obj *model.User, cursor *coremo
		query := database.
			Select(ctx, job).
			From(`job j`).
			Where(`j.owner_id = ?`, obj.ID)
			Where(sq.And{
				sq.Expr(`j.owner_id = ?`, obj.ID),
				sq.Or{
					sq.Expr(`j.owner_id = ?`, user.UserID),
					sq.Expr(`j.visibility = 'PUBLIC'`),
				},
			})
		jobs, cursor = job.QueryWithCursor(ctx, tx, query, cursor)
		return nil
	}); err != nil {
diff --git a/api/loaders/middleware.go b/api/loaders/middleware.go
index d36a052..6a83822 100644
--- a/api/loaders/middleware.go
+++ b/api/loaders/middleware.go
@@ -11,6 +11,7 @@ import (
	"github.com/lib/pq"

	"git.sr.ht/~sircmpwn/builds.sr.ht/api/graph/model"
	"git.sr.ht/~sircmpwn/core-go/auth"
	"git.sr.ht/~sircmpwn/core-go/database"
)

@@ -118,6 +119,7 @@ func fetchUsersByName(ctx context.Context) func(names []string) ([]*model.User,
}

func fetchJobsByID(ctx context.Context) func(ids []int) ([]*model.Job, []error) {
	user := auth.ForContext(ctx)
	return func(ids []int) ([]*model.Job, []error) {
		jobs := make([]*model.Job, len(ids))
		if err := database.WithTx(ctx, &sql.TxOptions{
@@ -131,7 +133,13 @@ func fetchJobsByID(ctx context.Context) func(ids []int) ([]*model.Job, []error)
			query := database.
				Select(ctx, (&model.Job{}).As("job")).
				From(`job`).
				Where(sq.Expr(`job.id = ANY(?)`, pq.Array(ids)))
				Where(sq.And{
					sq.Expr(`job.id = ANY(?)`, pq.Array(ids)),
					sq.Or{
						sq.Expr(`job.owner_id = ?`, user.UserID),
						sq.Expr(`job.visibility != 'PRIVATE'`),
					},
				})
			if rows, err = query.RunWith(tx).QueryContext(ctx); err != nil {
				return err
			}
diff --git a/buildsrht/alembic/versions/ae3544d6450a_add_visibility_to_job.py b/buildsrht/alembic/versions/ae3544d6450a_add_visibility_to_job.py
new file mode 100644
index 0000000..e00e202
--- /dev/null
@@ -0,0 +1,41 @@
"""Add visibility to job

Revision ID: ae3544d6450a
Revises: 76bb268d91f7
Create Date: 2023-03-13 10:33:49.830104

"""

# revision identifiers, used by Alembic.
revision = 'ae3544d6450a'
down_revision = '76bb268d91f7'

from alembic import op
import sqlalchemy as sa


def upgrade():
    op.execute("""
    CREATE TYPE visibility AS ENUM (
        'PUBLIC',
        'UNLISTED',
        'PRIVATE'
    );

    ALTER TABLE job
    ADD COLUMN visibility visibility;

    UPDATE job
    SET visibility = 'UNLISTED'::visibility;

    ALTER TABLE job
    ALTER COLUMN visibility
    SET NOT NULL;
    """)


def downgrade():
    op.execute("""
    ALTER TABLE job DROP COLUMN visibility;
    DROP TYPE visibility;
    """)
diff --git a/buildsrht/app.py b/buildsrht/app.py
index e5321a2..0eeca8d 100644
--- a/buildsrht/app.py
@@ -28,10 +28,12 @@ class BuildApp(SrhtFlask):
        from buildsrht.blueprints.api import api
        from buildsrht.blueprints.jobs import jobs
        from buildsrht.blueprints.secrets import secrets
        from buildsrht.blueprints.settings import settings
        from srht.graphql import gql_blueprint

        self.register_blueprint(admin)
        self.register_blueprint(api)
        self.register_blueprint(settings)
        self.register_blueprint(jobs)
        self.register_blueprint(secrets)
        self.register_blueprint(gql_blueprint)
diff --git a/buildsrht/blueprints/jobs.py b/buildsrht/blueprints/jobs.py
index 67c820f..173001a 100644
--- a/buildsrht/blueprints/jobs.py
@@ -3,7 +3,7 @@ from buildsrht.manifest import Manifest
from buildsrht.rss import generate_feed
from buildsrht.runner import submit_build, requires_payment
from buildsrht.search import apply_search
from buildsrht.types import Job, JobStatus, Task, TaskStatus, User
from buildsrht.types import Job, JobStatus, Task, TaskStatus, User, Visibility
from datetime import datetime, timedelta
from flask import Blueprint, render_template, request, abort, redirect
from flask import Response, url_for
@@ -35,6 +35,23 @@ metrics = type("metrics", tuple(), {

requests_session = requests.Session()

def get_access(job, user=None):
    user = user or current_user

    # Anonymous
    if not user:
        if job.visibility == Visibility.PRIVATE:
            return False
        return True

    # Owner
    if user.id == job.owner_id:
        return True

    if job.visibility == Visibility.PRIVATE:
        return False
    return True

def tags(tags):
    if not tags:
        return list()
@@ -269,8 +286,8 @@ def user(username):
    if not user:
        abort(404)
    jobs = Job.query.filter(Job.owner_id == user.id)
    if not current_user or current_user.id != user.id:
        pass # TODO: access controls
    if not current_user or user.id != current_user.id:
        jobs = jobs.filter(Job.visibility == Visibility.PUBLIC)
    origin = cfg("builds.sr.ht", "origin")
    rss_feed = {
        "title": f"{user.username}'s jobs",
@@ -287,8 +304,8 @@ def user_rss(username):
    if not user:
        abort(404)
    jobs = Job.query.filter(Job.owner_id == user.id)
    if not current_user or current_user.id != user.id:
        pass  # TODO: access controls
    if not current_user or user.id != current_user.id:
        jobs = jobs.filter(Job.visibility == Visibility.PUBLIC)
    return jobs_feed(jobs, f"{user.username}'s jobs",
                     "jobs.user", username=username)

@@ -316,7 +333,7 @@ def tag(username, path):
    jobs = Job.query.filter(Job.owner_id == user.id)\
        .filter(Job.tags.ilike(path + "%"))
    if not current_user or current_user.id != user.id:
        pass # TODO: access controls
        jobs = jobs.filter(Job.visibility == Visibility.PUBLIC)
    origin = cfg("builds.sr.ht", "origin")
    rss_feed = {
        "title": "/".join([f"~{user.username}"] +
@@ -336,7 +353,7 @@ def tag_rss(username, path):
    jobs = Job.query.filter(Job.owner_id == user.id)\
        .filter(Job.tags.ilike(path + "%"))
    if not current_user or current_user.id != user.id:
        pass  # TODO: access controls
        jobs = jobs.filter(Job.visibility == Visibility.PUBLIC)
    base_title = "/".join([f"~{user.username}"] +
                          [t["name"] for t in tags(path)])
    return jobs_feed(jobs, base_title + " jobs",
@@ -406,6 +423,8 @@ def job_by_id(username, job_id):
    job = Job.query.options(sa.orm.joinedload(Job.tasks)).get(job_id)
    if not job:
        abort(404)
    if not get_access(job):
        abort(404)
    logs = list()
    build_user = cfg("git.sr.ht::dispatch", "/usr/bin/buildsrht-keys", "builds:builds").split(":")[0]
    final_status = [
diff --git a/buildsrht/blueprints/settings.py b/buildsrht/blueprints/settings.py
new file mode 100644
index 0000000..0368a5c
--- /dev/null
@@ -0,0 +1,42 @@
from flask import Blueprint, current_app, render_template, request, url_for, abort, redirect
from flask import current_app
from srht.database import db
from srht.oauth import current_user, loginrequired
from srht.validation import Validation
from buildsrht.types import Job, Visibility

settings = Blueprint("settings", __name__)

@settings.route("/~<username>/job/<int:job_id>/settings/details")
@loginrequired
def details_GET(username, job_id):
    job = Job.query.get(job_id)
    if not job:
        abort(404)
    if current_user.id != job.owner_id:
        abort(404)
    return render_template("job-details.html",
        view="details", job=job)

@settings.route("/~<username>/job/<int:job_id>/settings/details", methods=["POST"])
@loginrequired
def details_POST(username, job_id):
    job = Job.query.get(job_id)
    if not job:
        abort(404)
    if current_user.id != job.owner_id:
        abort(404)

    valid = Validation(request)
    visibility = valid.require("visibility")
    if not valid.ok:
        return render_template("job-details.html",
            job=job, **valid.kwargs), 400

    # TODO: GraphQL mutation to update job details
    job.visibility = visibility
    db.session.commit()

    return redirect(url_for("settings.details_GET",
        username=job.owner.username,
        job_id=job.id))
diff --git a/buildsrht/templates/job-details.html b/buildsrht/templates/job-details.html
new file mode 100644
index 0000000..769a5a9
--- /dev/null
@@ -0,0 +1,75 @@
{% extends "settings.html" %}
{% block title %}
<title>Configure {{url_for("jobs.user", username=job.owner.username)}}/#{{job.id}}
  &mdash; {{ cfg("sr.ht", "site-name") }}</title>
{% endblock %}
{% block content %}
<form class="row" method="POST">
  {{csrf_token()}}
  <div class="col-md-6 d-flex flex-column">
    <fieldset class="form-group">
      <legend>Job Visibility</legend>
      <div class="form-check">
        <label class="form-check-label">
          <input
            class="form-check-input"
            type="radio"
            name="visibility"
            value="PUBLIC"
            {% if job.visibility.value == "PUBLIC" %}
            checked
            {% endif %}
            > Public
          <small id="visibility-public-help" class="form-text text-muted">
            Shown on your profile page
          </small>
        </label>
      </div>
      <div class="form-check">
        <label
            class="form-check-label"
            title="Visible to anyone with the link, but not shown on your profile"
          >
          <input
            class="form-check-input"
            type="radio"
            name="visibility"
            value="UNLISTED"
            {% if job.visibility.value == "UNLISTED" %}
            checked
            {% endif %}
            > Unlisted
          <small id="visibility-unlisted-help" class="form-text text-muted">
            Visible to anyone who knows the URL, but not shown on your profile
          </small>
        </label>
      </div>
      <div class="form-check">
        <label
          class="form-check-label"
          title="Only visible to you and your collaborators"
        >
          <input
            class="form-check-input"
            type="radio"
            name="visibility"
            value="PRIVATE"
            {% if job.visibility.value == "PRIVATE" %}
            checked
            {% endif %}
            > Private
          <small id="visibility-unlisted-help" class="form-text text-muted">
            Only visible to you and your collaborators
          </small>
        </label>
      </div>
    </fieldset>
    {{ valid.summary() }}
    <span class="pull-right">
      <button type="submit" class="btn btn-primary">
        Save {{icon("caret-right")}}
      </button>
    </span>
  </div>
</form>
{% endblock %}
diff --git a/buildsrht/templates/job.html b/buildsrht/templates/job.html
index e5eed5e..1a7b9bf 100644
--- a/buildsrht/templates/job.html
@@ -13,15 +13,46 @@
{% endif %}
{% endblock %}
{% block body %} 
<div class="header-tabbed">
  <div class="container-fluid">
    <h2>
      <a href="{{ url_for("jobs.user", username=job.owner.username) }}">{{ job.owner }}</a>/<wbr
     >#{{ job.id }}
    </h2>
    <ul class="nav nav-tabs">
      {% if job.visibility.value != "PUBLIC" %}
      <li
        class="nav-item nav-text vis-{{job.visibility.value.lower()}}"
        {% if job.visibility.value == "UNLISTED" %}
        title="This job is only visible to those who know the URL."
        {% elif job.visibility.value == "PRIVATE" %}
        title="This job is only visible to those who were invited to view it."
        {% endif %}
      >
        {% if job.visibility.value == "UNLISTED" %}
        Unlisted
        {% elif job.visibility.value == "PRIVATE" %}
        Private
        {% endif %}
      </li>
      {% endif %}
      {% if current_user and current_user.id == job.owner_id %}
      <li class="nav-item">
        <a class="nav-link" href="{{url_for("settings.details_GET",
          username=job.owner.username,
          job_id=job.id)}}"
        >settings</a>
      </li>
      {% endif %}
    </ul>
  </div>
</div>
<div class="container-fluid">
  <section class="row">
    <div class="col-lg-3 col-md-12">
      <h2>
        #{{ job.id }}
        <span class="pull-right">
          {{icon(icon_map.get(job.status), cls=status_map.get(job.status, ""))}}
          {{ job.status.value }}
        </span>
        {{icon(icon_map.get(job.status), cls=status_map.get(job.status, ""))}}
        {{ job.status.value }}
      </h2>
      <dl>
        {% if job.note %}
diff --git a/buildsrht/templates/settings.html b/buildsrht/templates/settings.html
new file mode 100644
index 0000000..78041fa
--- /dev/null
@@ -0,0 +1,31 @@
{% extends "layout.html" %}
{% block body %}
<div class="header-tabbed">
  <div class="container">
    {% macro link(path, title) %}
    <a
      class="nav-link {% if view == title %}active{% endif %}"
      href="{{ path }}">{{ title }}</a>
    {% endmacro %}
    <h2>
      <a href="{{ url_for("jobs.user", username=job.owner.username) }}">{{ job.owner }}</a>/<wbr
     >#{{ job.id }}
    </h2>
    <ul class="nav nav-tabs">
      <li class="nav-item">
        <a class="nav-link"
         href="{{ url_for("jobs.job_by_id", username=job.owner.username, job_id=job.id) }}"
        >{{icon("caret-left")}}&nbsp;back</a>
      </li>
      <li class="nav-item">
        {{link(url_for("settings.details_GET",
          username=job.owner.username,
          job_id=job.id), "details")}}
      </li>
    </ul>
  </div>
</div>
<div class="container">
  {% block content %}{% endblock %}
</div>
{% endblock %}
diff --git a/buildsrht/templates/submit.html b/buildsrht/templates/submit.html
index a01d0a6..a3d4e72 100644
--- a/buildsrht/templates/submit.html
@@ -70,6 +70,46 @@
        rows="{{note_rows}}"
      >{{note if note else ""}}</textarea>
    </div>
    <fieldset class="form-group">
      <legend>Visibility</legend>
      <div class="form-check form-check-inline">
        <label
          class="form-check-label"
          title="Publically visible and listed on your profile"
        >
          <input
            class="form-check-input"
            type="radio"
            name="visibility"
            value="PUBLIC"> Public
        </label>
      </div>
      <div class="form-check form-check-inline">
        <label
            class="form-check-label"
            title="Visible to anyone with the link, but not shown on your profile"
          >
          <input
            class="form-check-input"
            type="radio"
            name="visibility"
            value="UNLISTED"
            checked> Unlisted
        </label>
      </div>
      <div class="form-check form-check-inline">
        <label
          class="form-check-label"
          title="Only visible to you and your collaborators"
        >
          <input
            class="form-check-input"
            type="radio"
            name="visibility"
            value="PRIVATE"> Private
        </label>
      </div>
    </fieldset>
    <div class="form-group">
      <a
        class="pull-right"
diff --git a/buildsrht/types/__init__.py b/buildsrht/types/__init__.py
index 84ec9aa..275eb76 100644
--- a/buildsrht/types/__init__.py
@@ -7,7 +7,7 @@ class User(Base, ExternalUserMixin):
class OAuthToken(Base, ExternalOAuthTokenMixin):
    pass

from .job import Job, JobStatus
from .job import Job, JobStatus, Visibility
from .task import Task, TaskStatus
from .job_group import JobGroup
from .trigger import Trigger, TriggerType, TriggerCondition
diff --git a/buildsrht/types/job.py b/buildsrht/types/job.py
index 2ef807e..550b771 100644
--- a/buildsrht/types/job.py
@@ -13,6 +13,11 @@ class JobStatus(Enum):
    timeout = 'timeout'
    cancelled = 'cancelled'

class Visibility(Enum):
    PUBLIC = 'PUBLIC'
    UNLISTED = 'UNLISTED'
    PRIVATE = 'PRIVATE'

class Job(Base):
    __tablename__ = 'job'
    id = sa.Column(sa.Integer, primary_key=True)
@@ -32,6 +37,7 @@ class Job(Base):
            nullable=False,
            default=JobStatus.pending)
    image = sa.Column(sa.String(256))
    visibility = sa.Column(sau.ChoiceType(Visibility), nullable=False)

    def __init__(self, owner, manifest):
        self.owner_id = owner.id
diff --git a/schema.sql b/schema.sql
index 919e816..073467c 100644
--- a/schema.sql
+++ b/schema.sql
@@ -10,6 +10,12 @@ CREATE TYPE webhook_event AS ENUM (
	'JOB_CREATED'
);

CREATE TYPE visibility AS ENUM (
	'PUBLIC',
	'UNLISTED',
	'PRIVATE'
);

CREATE TABLE "user" (
	id serial PRIMARY KEY,
	username character varying(256) UNIQUE,
@@ -64,7 +70,8 @@ CREATE TABLE job (
	runner character varying,
	status character varying NOT NULL,
	secrets boolean DEFAULT true NOT NULL,
	image character varying(128)
	image character varying(128),
	visibility visibility NOT NULL
);

CREATE INDEX ix_job_owner_id ON job USING btree (owner_id);

base-commit: 27a80ffed5c357317a75c35bf210fe89b53c0d4c
-- 
2.39.2

[builds.sr.ht/patches] build failed

builds.sr.ht <builds@sr.ht>
Details
Message ID
<CR5EFVTX04OW.7IFHPYGWPSEE@cirno2>
In-Reply-To
<20230313162930.26065-1-adnan@maolood.com> (view parent)
DKIM signature
missing
Download raw message
builds.sr.ht/patches: FAILED in 3m55s

[Implement job visibility][0] from [Adnan Maolood][1]

[0]: https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/39681
[1]: adnan@maolood.com

✓ #956548 SUCCESS builds.sr.ht/patches/debian.yml    https://builds.sr.ht/~sircmpwn/job/956548
✓ #956546 SUCCESS builds.sr.ht/patches/alpine.yml    https://builds.sr.ht/~sircmpwn/job/956546
✗ #956547 FAILED  builds.sr.ht/patches/archlinux.yml https://builds.sr.ht/~sircmpwn/job/956547
Details
Message ID
<CRTXT45D6Y7O.2M8S04CIM9J4S@taiga>
In-Reply-To
<20230313162930.26065-1-adnan@maolood.com> (view parent)
DKIM signature
missing
Download raw message
When testing this by using the UI at builds.sr.ht/submit I get this
exception:

srht.graphql.client.GraphQLError: {'errors': [{'message': 'pq: null value in column "visibility" of relation "job" violates not-null constraint', 'path': ['submit']}], 'data': None}
Details
Message ID
<CRYKFDQJKU57.3RPSUJGK3U93D@framework>
In-Reply-To
<CRTXT45D6Y7O.2M8S04CIM9J4S@taiga> (view parent)
DKIM signature
missing
Download raw message
On Tue Apr 11, 2023 at 8:49 AM EDT, Drew DeVault wrote:
> When testing this by using the UI at builds.sr.ht/submit I get this
> exception:
>
> srht.graphql.client.GraphQLError: {'errors': [{'message': 'pq: null value in column "visibility" of relation "job" violates not-null constraint', 'path': ['submit']}], 'data': None}

Sounds like the API is running an outdated version?

Anyways, I sent a v2 to fix an unrelated problem.
Reply to thread Export thread (mbox)