Fossil Forum

peijen 1 month ago

Post: Ticket + forum

I am aware there are some forum updates going on and it was mentioned the team wasn’t happy with how ticket is working? But I am wondering, is adding forum to the ticket as a field or the same association title like wiki acceptable?

I love that I can customize ticket fields and views, but don’t really like how comment and comment works. And it seems to me that forum is a natural expansion of ticket functionality. And just like associated wiki is rendered inline if associate forum can be rendered inline too that would be great.

I also wonder if a reverse link can be added so you can create a ticket from a forum post/thread.

Thoughts? Z 3

stephan 1 month ago

I am aware there are some forum updates going on...

AFAIK we're done with major renovations for the time being. Suggestions for changes are welcomed.

But I am wondering, is adding forum to the ticket as a field or the same association title like wiki acceptable?

We've discussed the idea of associating tickets with threads, similarly to how each checkin or tag can have a wiki page associated with it, but nobody's been intrigued enough by it to write the code. The idea is that we could just embed the forum thread directly in the ticket and/or vice versa.

I love that I can customize ticket fields and views, but don’t really like how ...

That's the general consensus, yes ;).

And it seems to me that forum is a natural expansion of ticket functionality...

As is that.

And just like associated wiki is rendered inline if associate forum can be rendered inline too that would be great.

Patches would be thoughtfully considered :).

I also wonder if a reverse link can be added so you can create a ticket from a forum post/thread.

Three summers ago i prototyped a mechanism for recording links using tags so that we could do precisely that without having to make any changes to artifact formats, but it's never been fleshed out or implemented further than a scratchpad SQL file (which is lying around here somewhere). Z

peijen 1 month ago

Putting my ideas here.  It's been 20 years since I last wrote C code, so I will need to rely on AI to help me with the implementation.  Calling this out because I think you probably don't want a patch written by AI, but I refined the plan to be minimal instead of whatever AI suggested the first 10 go around.

Workaround

Here is the currently workaround I use and potentially points to a solution? - I added forum_thread column to ticket table and it defaults to null.  On ticket view render, - if forum_thread is null a submenu, "Start Discussion" is created for posting new thread with title='ticket/', and content pre-filled.  The idea is the first post will contain ticket information plus metadata.  Then forum_thread column on ticket is updated manually. - if forum_thread is not null, "Discussion" submenu is rendered with link to forum thread.

Patch proposal

Forum Thread Creation:

  • New query parameter assocticket= on /forumnew
  • Render back as hidden inputs like title and content
  • On forum new submit, check if tag forum-ticket/ exists   - If exists, check if ticket_uuid is valid, then create the forum post and add tag forum-ticket/.   - If not, redirect to the existing forum thread.

Render Associated Forum Thread:

  • Follow associated wiki flow.  Check if tag forum-ticket/<ticket_uuid> exists. Output as TH1 variable $forum_thread with rid of the forum thread or 0 if not exists.
  • Add new TH1 command forum_render RID to render the forum post content as HTML.  This is similar to wiki_assoc.
  • Template render submenu link "Discussion" if $forum_thread is not 0, otherwise render submenu link "Start Discussion" with prefilled title=$title and content=$comment.  Title and content are customizable by the user.
  • Add forum_render $forum_thread to the template to render the forum post content right after wiki_assoc. Z 7317088
peijen 1 month ago

Separate post you can disallow

--- AI generarted content ---

Associating forum threads with tickets, in the spirit of wiki_assoc

TLDR

  • Fossil has wiki_assoc: a ticket/check-in/branch/tag can have an associated wiki page (ticket/<uuid>) rendered inline, because wiki titles are forced globally unique via a wiki-<title> tag (tag.tagname UNIQUE).
  • Forum threads have no equivalent. Thread titles must stay free-form and non-unique (two threads can share a subject), so we can't reuse wiki's "title = identity" trick.
  • Proposed fix: a synthetic forum-ticket/<uuid> tag, created out-of-band (not via a manifest card) at thread-creation time, keyed by the ticket's uuid rather than the thread's title. Ordinary threads (no ticket) are completely unaffected.
  • "Start Discussion" reuses the existing /forume1 compose form; a new assocticket CGI param rides along like the existing title/content prefill, and is validated + tagged only on submit (ticket must exist; reject if a thread is already tagged for it).
  • Ticket page exposes a computed $forum_thread TH1 variable (rid or "0"), set the same way tkt_mage/tkt_cage already are - no tag/tagxref SQL in the template.
  • New minimal TH1 command forum_render RID just renders a forum post's content as HTML (no ticket-awareness) - the only thing a template can't already do itself.
  • Template ends up branching on $forum_thread for the submenu label (Discussion vs Start Discussion, gated on capexpr {3} i.e. WrForum), then calls forum_render $forum_thread unconditionally in the body, mirroring wiki_assoc's blind-call style.
  • Bonus/separate idea: surface incidental mentions (not "the" thread) via existing backlink table + render_backlink_graph() on /tktview, which needs a new backlink_target index since reverse lookups are currently unindexed.
  • Open questions: is the explicit tag-insert worth it vs. just a convention; naming bikeshed (forum-ticket/<uuid>, assocticket, forum_render); forum_post() needs to return the new rid; should creating an associated thread require more than plain WrForum; is the backlink-index part in scope for the same patch.

Code

1. forum.c — internal lookup helpers (used by the create-time guard in [2] and by the TH1-variable binding in [3]; not exposed to TH1 directly)

/*
** Return the tagid for the tag that associates a forum thread with
** ticket zTktUuid, or 0 if no such tag exists yet.
*/
int forum_tagid_for_ticket(const char *zTktUuid){
  return db_int(0, "SELECT tagid FROM tag WHERE tagname='forum-ticket/%q'",
                zTktUuid);
}

/*
** Resolve a forum-ticket tagid to the rid of the associated root post,
** or 0 if the tag is not (or no longer) attached to anything live.
*/
int forum_rid_for_tagid(int tagid){
  if( tagid<=0 ) return 0;
  return db_int(0,
    "SELECT rid FROM tagxref WHERE tagid=%d ORDER BY mtime DESC LIMIT 1",
    tagid);
}

2. forum.cforumnew_page() additions

/* Inside forumnew_page(), alongside the existing title/mimetype/content reads: */
const char *zAssocTicket = P("assocticket");

/* ... existing GET-render path ... when emitting the form, next to the
** existing login_insert_csrf_secret() call: */
if( zAssocTicket && zAssocTicket[0] ){
  @ <input type="hidden" name="assocticket" value="%h(zAssocTicket)">
}

/* ... existing submit path ... */
if( P("submit") && cgi_csrf_safe(2) ){
  int newRid = 0;
  if( zAssocTicket && zAssocTicket[0] ){
    if( !db_exists("SELECT 1 FROM ticket WHERE tkt_uuid=%Q", zAssocTicket) ){
      webpage_error("No such ticket: %s", zAssocTicket);
    }
    if( forum_rid_for_tagid(forum_tagid_for_ticket(zAssocTicket))!=0 ){
      webpage_error("Ticket %s already has a discussion thread", zAssocTicket);
      /* or: cgi_redirectf(...) straight to the existing thread */
    }
  }
  newRid = forum_post(zTitle, zMimetype, zContent, iInReplyTo /*0 for new thread*/);
  if( zAssocTicket && zAssocTicket[0] && newRid>0 ){
    char *zTag = mprintf("forum-ticket/%s", zAssocTicket);
    tag_insert(zTag, 1, 0, newRid, db_double(0.0,"SELECT julianday('now')"), newRid);
    fossil_free(zTag);
  }
  /* existing redirect to /forumpost/<newRid's uuid> */
}

forum_post() (forum.c:1310-1415) currently just commits and redirects — it needs a small signature change (return the rid, or an int *pNewRid out-param) to make the above possible.

3. tkt.c — expose $forum_thread as a server-computed TH1 variable

tktview_page() (tkt.c:734) calls initializeVariablesFromDb() (tkt.c:189-231), which Th_Store()s every ticket column generically, plus a couple of computed extras (tkt_mage, tkt_cage). $forum_thread fits that computed-extra category:

/* Inside initializeVariablesFromDb(), after the existing column-binding
** loop, alongside the tkt_mage/tkt_cage Th_Store() calls. Always stored,
** never omitted -- 0 means "no thread yet" -- so the template can pass
** it straight to forum_render blindly, the same way wiki_assoc is called
** blindly, with no guard needed at the call site: */
{
  int fRid = forum_rid_for_tagid(forum_tagid_for_ticket(zTktUuid));
  char zBuf[24];
  sqlite3_snprintf(sizeof(zBuf), zBuf, "%d", fRid);
  Th_Store("forum_thread", zBuf);
}

The template just tests $forum_thread for truthiness ("0" is falsy); forum_render (§4) treats rid<=0 as a no-op, so calling it unconditionally is always safe.

4. th_main.c — a minimal, generic forum_render TH1 command (no ticket-awareness at all)

The only thing a template can't do for itself is turning a forum post's stored content (only inside the manifest, manifest_get()) into HTML:

/*
** TH1 command: forum_render RID
**
** Render the body of forum post RID (an artifact rid, not a uuid) as
** an HTML fragment, exactly the way Fossil already renders forum
** posts elsewhere (forum_render(), forum.c:636). Renders nothing if
** RID doesn't name a forum post, or if the caller lacks RdForum.
**
** Deliberately has no notion of tickets, tags, or any other
** association -- the calling template is expected to already know
** which rid to render (typically via a plain "query" against
** tag/tagxref) and to handle all "does this exist yet, what do we
** call it" UI decisions itself.
*/
static int forumRenderCmd(
  Th_Interp *interp,
  void *p,
  int argc,
  const char **argv,
  int *argl
){
  int rid;
  Manifest *pManifest;
  if( argc!=2 ){
    return Th_WrongNumArgs(interp, "forum_render RID");
  }
  if( !g.perm.RdForum ) return TH_OK;
  rid = atoi(argv[1]);
  if( rid<=0 ) return TH_OK;
  pManifest = manifest_get(rid, CFTYPE_FORUM, 0);
  if( pManifest==0 ) return TH_OK;
  forum_render(0, pManifest->zMimetype, pManifest->zWiki, 0, 1);
  manifest_destroy(pManifest);
  return TH_OK;
}

(forum_render()'s real signature is (zTitle, zMimetype, zContent, zClass, bScroll), forum.c:636, obtained the same way forum_display_post() does via manifest_get(rid, CFTYPE_FORUM, 0).)

Registered next to wiki_assoc in the aCommand[] table (th_main.c, ~line 2428):

{"wiki_assoc",     wikiAssocCmd,       0},
{"forum_render",   forumRenderCmd,     0},

Resulting ticket-view template

<th1>
...
if {[capexpr {nk}]} {
  submenu link "Edit Wiki" $baseurl/wikiedit?name=ticket/$tkt_uuid
}
if {$forum_thread} {
  submenu link "Discussion" $baseurl/forumpost/$forum_thread
} elseif {[capexpr {3}]} {
  submenu link "Start Discussion" \
    $baseurl/forume1?title=[htmlize $title]&content=[htmlize $comment]&assocticket=[htmlize $tkt_uuid]
}
</th1>
...
<th1>
wiki_assoc "ticket" $tkt_uuid
forum_render $forum_thread
</th1>

$title/$comment are real columns on the stock ticket schema (src/tktsetup.c:67-97), already bound and used by the default view template, so the default template gets a prefilled "Start Discussion" link for free; customized schemas just don't get free prefill until their template copy is updated.

Surfacing other mentions, without conflating them with "the" thread

The backlink table is already populated for forum posts (manifest.c:2877, BKLNK_FORUM) whenever a post links to another artifact's hash — a soft, many-to-many relationship, unlike the tag above.

Rather than making backlinks carry the primary association, keep them as a secondary "Other discussions mentioning this ticket" list via the existing render_backlink_graph() (backlink.c:35), not currently wired into /tktview. backlink currently has only CREATE INDEX backlink_src ON backlink(srcid, srctype), no index on target, so a reverse lookup is an unindexed scan today:

CREATE INDEX backlink_target ON backlink(target, srctype);

Z 9bb32f927a0dcca2755f1f9aef9f0a19

stephan 1 month ago

Separate post you can disallow...

Ooops! ;)

--- AI generarted content ---

Even so, it's topical and interesting.

stephan 1 month ago

Separate post you can disallow...

Ooops! ;)

--- AI generarted content ---

Even so, it's topical and interesting.

Edit: my apologies for the moderation delay. Your future posts won't await moderation.

peijen 1 month ago

Any thoughts on the design? Things you would do differently?

Is this something you would consider adding to fossil? Mostly trying to get a sense if I should continue to invest more time in my workaround.

Thanks.

stephan 4 weeks, 2 days ago

Any thoughts on the design? Things you would do differently?

i've never been a fan of ticketing systems and tend to avoid them. The only one i've ever used which i really liked was Jira (some 10 years ago) but, like all others, it tends to mutate into a very project-specific configuration. Which is kinda the point of their configurability (and why they're so configurable in the first place) but it gives each one a distinct usage pattern and that's annoying. (Github's isn't bad, either, but it's basically just a trimmed-down forum with tagging capabilities, and i commend them for keeping it that simple.) That is to say...

Is this something you would consider adding to fossil?

Personally i'd shy away from any efforts to re-do the ticketing system and leave those decisions, and that work, to the ones motivated to implement it. (Work on the forum to make it a better fit for tickets, on the other, is a different topic, though - that would be more likely to entice me.)

As to whether the other maintainers would be up for a ticket overhaul, i can't speculate.

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button