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
UsereditsComment
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?
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.
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 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.
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.
So it’s a nonempty list of comments you say? 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
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.
Yup! I also have that in mind 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.
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.
@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?
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
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.