CoCoDa: Issues 🗒 #codecollab

We are beginning our journey of defining our Code Collaboration Data (CoCoDa). Some initial discussions have been going on in Radicle Collaboration Tools - #21 by cloudhead.

In this discussion we want to guide the process of defining the data involved with issues that radicle-link will store and radicle-upstream will store. This initial discussion should be a back and forth as to what user needs we expect, what kind of use stories we expect, and ultimately should lead to a more canonical definition of how we expect issues to look in the radicle ecosystem; likely to be specified in a spec document.

Taking a look at how GitHub models issues an issue is made up of the following components:

  • Title
  • Body
  • Sequence of Comments
  • Labels
  • Assignees
  • Author
  • Identifier
  • Reactions

Comments are composed of:

  • Body
  • Reactions

And then inside a Body we have interesting artifacts such as URLs, links to other artifacts, links to other projects. These will be on the more difficult end of things to present.

Some of the stories that can come to mind:

  • Create an Issue
  • Edit an Issue’s Title
  • Edit an Issue’s Body
  • Add a comment to the Issue
  • User edits Comment
  • etc.

I think it would be useful to visit the above as denotations since that will show us what types need to be used and these user stories are functions. But lemme know what you think @xla @rudolfs @garbados (I would tag Sarah but it seems her discourse isn’t set up yet ;)) is there things that I may be missing here? Is there anything I can elaborate, or you would like to elaborate on?

Peace my fellow Radicles :v:

Cool! A few more stories and potentially data amendments:

  • Close an issue → state (open/close)

Then there is a whole another planning category, where I’m not sure if we need it:

  • Set due date → due_date
  • Set weight to prioritize → weight
  • Link to another issue → linked_issues
  • Attach issue to Milestone → milestone
  • Vote on an issue → vote (potentially reaction is sufficient)

Last, I’m thinking if we need some sort of visibility management for privacy or confidentiality which allows only certain user types to see an issue.

1 Like

I imagine this would be a tricky feature of any kind of data in our network. Maybe some kind of encryption where only certain people know the shared key :thinking: Out of interest, did you have a use case for this?

Maybe a security vulnerability that isn’t supposed to be public before fix, or an enterprise use-case where something is development in stealth. I’m not really a fan of it either tbh.

Ya probably something we can look into down the line but not necessary to bake into our first assumptions :grin:

Nice start! One thing I’d consider is whether we can remove the body attribute, and just use comments for all the text. This is how GitHub works, and I think it’s pretty clever, both visually and from a data-model point of view.

1 Like

So it’s a nonempty list of comments you say? :eyes: That does make sense! I’m just about to try outline the denotations and I’ll keep this in mind as I explore them.

I’ll link back a WIP PR here once I have enough pieces together :grin:

1 Like

Yes, a nonempty!

The other thing to keep in mind is that we will probably eventually move to threaded comments. It’s one of the most requested features on GitHub, and it would mean a more tree-like structure.

1 Like

Yup! I also have that in mind :grin: Are comments not threaded in GitHub already? Or they are in some cases, if it’s attached to code in a PR. I suppose they’re not in issue comments.

Good writeup Fintan!

I haven’t seen any feature specs for issues yet, but as a starting point this looks good.
I also second @cloudhead’s comment, that we should think about comment threading, as it would make issues so much more usable.

Maybe it’s too soon to think about this, but permissions (who can contribute) could be an interesting aspect of this feature. Or at least a way to lock a discussion (by a maintainer, for example).

Let’s add @merle to future discussions, she’s now part of our team.

1 Like

They aren’t, basically you always reply to the previous comment, timeline-wise, you don’t get to choose a parent comment.

Ah right, you can’t arbitrarily thread comments. Gotya :ok_hand:

@cloudhead: This is how I imagine the data-structure to look like. So there’s a root node for a comment, followed by a list of children (possibly empty if a thread is not created under that comment). Each comment acts as it’s own root node.

Does this ring any bells for existing data-structures? :thinking:

Hmmm maybe it is just:

type Thread
μ Thread = (Comment, [Thread])

@coco @application

I think this is a good initial denotation for issues and their comment threads.

type Issue
μ Issue = (Author, Title, Thread, Metadata {- Contains assignees, due date, milestones, etc. -})

type Title
μ Title = Text

type Comment
μ Comment = (Author, Text)

type Thread
μ Thread = (Comment, [Thread])

-- Pretty much just free form text
type Label
μ Label = Text

-- Opaque as well (maybe just need the name for display)
type Author

type Reaction

-- User should be opaque and defined by the system
-- interacting with the issues. Could be the Global User ID,
-- could be the Device ID, could just be a nick.
type User

type Date

type Priority

type Milestone
μ Milestone = (Text {- Title -}, Date {- Due Date -})

create :: Author -> Title -> Text -> Issue
μ create author title commentBody =
  let comment = (author, commentBody)
  in (author, title, (comment, []))

editTitle :: Author -> Issue -> Title -> Maybe Issue
μ editTitle author issue title =
  let change (oldAuthor, _, thread) = (oldAuthor, title, thread)
  in editable change author issue

editComment :: Author -> Issue -> Comment -> Option Issue
μ editComment author issue comment =
  let change (oldAuthor, title, thread) = (oldAuthor, title, ??? comment thread)
  in editable author issue change

editable :: (Issue -> Issue) -> Author -> Issue -> Option Issue
editable change author issue =
  if oldAuthor == author
  then Just (change issue)
  else Nothing

threadComment :: Comment -> Thread -> Thread
μ threadComment comment (initial, comments) = (comment, comments <> [comment])

-- Comment on the main thread
comment :: Comment -> Issue
μ comment c (author, title, thread) = (author, title, threadComment c thread)

-- | Setting metadata
--   All the below are just metadata of the issue
--   and we should have some kind of lens-like way of
--   setting them.
dueDate :: Date -> Issue -> Issue
μ dueDate newDate = set date newDate

prioritize :: Priority -> Issue -> Issue
μ prioritize newPriority = set priority newPriority

milestone :: Milestone -> Issue -> Issue
μ milestone newMilestone = set milestone newMilestone

assignUser :: Issue -> User -> Issue
μ assignUser issue user =
  if not (user `elem` view assignees issue)
  then modify (user:) assignees issue
  else issue

removeUser :: Issue -> User -> Issue
μ assignUser issue user = modify (user:) assignees issue
  if (user `elem` view assignees issue)
  then issue
  else modify (filter (user /=)) assignees issue

react :: User -> Reaction -> Issue -> Issue
μ react user reaction issue =
  let userReaction = (user, reaction)
  in if (userReaction `elem` view reactions issue)
     then issue
     else modify (userReaction:) reactions issue

Something fun to notice is that Thread is actually just Cofree.

type Thread = (Comment, [Thread])

-- Make it polymorphic over Comment
type Thread a = (a, [Thread a])

-- Now make it polymorphic over the list []
type Thread f a = (a, f (Thread f a))

-- Renaming Thread to Cofree and using <: instead of (,)
type Cofree f a = a <: f (Cofree f a)

What this means is that there’s already a bunch of functionality that exists for our Threads :grin:

This Cofree thing only refines things a little so far. But maybe there’s nicer properties that I can’t see yet; my comonad game is not the best.

firstComment :: Thread -> Comment
μ firstComment thread = extract (μ thread)

startThread :: Comment -> Thread
μ startThread = pure

threadComment :: Comment -> Thread -> Thread
μ threadComment comment (initial :< comments) = initial :< comments <> startThread comment

The initial feature-set looks exhaustive. What’s not yet captured is the moderation side, where project maintainers can lock a conversation or close a noise/spamy issue.

For all things linking/referencing (i.e. other issues, milestones, revisions) we need to establish unique identities and how to refer to I think this is out of scope for this discussion and we should focus on actions/operations.

Accounting for threaded conversations sounds like the right impulse, thanks for bringing it up @cloudhead. To keep the complexity manageable in the beginning we could treat the issue as a single thread with a single root comment, which is the “issue body”. Later on we can open up the possibility to arbirtrary (or in some bounds) thread conversations.

How do you imagine this to work?

  • a maintainer can lock a conversation, preventing further comments to be created
  • a maintainer can close an issue created by someone else

For the first operation any comments after the lock should be invalid/not displayed. Maintainers could be excluded of that rule.

Closing and opening of issues might also be an operation which only maintainers can perform and/or is configurable on a per project basis.

@kim Does this help to understand the desired functionality.