irreverentsimplicity / zentasktic-project

0 stars 1 forks source link

Data types to enrich Decide #1

Closed irreverentsimplicity closed 4 months ago

irreverentsimplicity commented 6 months ago

This is intended as conversation starter for the data types we may use to enrich Decide, types which may be specific only to the Gno ecosystem project management processes. Comments welcome!

Proposed data structures / rich types for a DAO / team

These types are intended to be used in the Decide realm.

Actor

The basic working unit for an individual. Actors can be team members, or individual contributors.

type Actor struct {
    Id          string `json:"actorId"`
    TeamId      string `json:"teamId"`
    Name        string `json:"actorName"`
    // github handle?
}

Team

Teams are collective entities, they may or may not have actors assigned.

type Team struct {
    Id          string `json:"teamId"`
    Name        string `json:"teamName"`
}

WorkHour

WorkHours are abstract time measurements assignable to a Task / Projet. They may or may not be combined with Actors or Teams.

type WorkHour struct {
    Id                  string `json:"workHourId"`
    ObjectId            string `json:"objectId"`
    ObjectType          string `json:"objectType"` // Task, Project
    Amount              string `json:"workHourAmount"`
}

RewardsPoints

RewardsPoints are abstract denominations for how much a certain task or project should be worth on completion. They may or may not be assigned to Actors or Teams, but they are releasable once a Task / Project is completed (moving a Task / Project with assigned RewardsPoints from Do to a Collection releases the assigned rewards points from the total existing).

type RewardsPoint struct {
    Id              string `json:"rewardsPointId"`
    ObjectId        string `json:"objectId"`
    ObjectType      string `json:"objectType"`
    Amount          string `json:"rewardsPointAmount"`
    Status          string `json:"rewardsPointStatus"`            // assigned / released / unused, etc
}

Resources

Resources are abstract denominations for what needs to be spent, e.g. money, electricity, for a specific Task / Project.

type Resource struct {
    Id          string `json:"resourceId"`
    Name        string `json:"resourceName"`   // money / fixed expenses etc
}
thehowl commented 6 months ago
type Actor struct {
    Id          string `json:"actorId"`
    TeamId      string `json:"teamId"`
    Name        string `json:"actorName"`
    // github handle?
}

Consider adding the user's Gno address as std.Address. I think the GitHub handle should eventually be derivable from the GitHub oracle realm (from the address). That said, it probably makes sense to keep the addresses optional so not everyone has to be signed up from the get-go.

TeamId implies the user can only be in one team; I would advise against this. Consider instead either having Members []*Actor in Team; or using a tree to map Actor ID -> []*Team / Team ID -> []*Actor.

type Team struct {
    Id          string `json:"teamId"`
    Name        string `json:"teamName"`
}

Maybe it should have an owner, not sure. Definitely there should be ways to get the team's members, for instance.

type WorkHour struct {
    Id                  string `json:"workHourId"`
    ObjectId            string `json:"objectId"`
    ObjectType          string `json:"objectType"` // Task, Project
    Amount              string `json:"workHourAmount"`
}

Seems weird to track WorkHour; I'd suggest WorkDuration; possibly with Duration time.Duration.

A better way to link to the "Object" may be through an interface: I suggest a Workable interface implemented by *Task and *Project.

type RewardsPoint struct {
    Id              string `json:"rewardsPointId"`
    ObjectId        string `json:"objectId"`
    ObjectType      string `json:"objectType"`
    Amount          string `json:"rewardsPointAmount"`
    Status          string `json:"rewardsPointStatus"`            // assigned / released / unused, etc
}

Aside from Object to Workable, Amount should probably be either std.Coins or some type where it is easily possible to get the reward as an integer and denomination.

type Resource struct {
    Id          string `json:"resourceId"`
    Name        string `json:"resourceName"`   // money / fixed expenses etc
}

This one confuses me.

irreverentsimplicity commented 6 months ago

@thehowl Thank you! Will add an Address:std.Address to the Actor struct. Good points about using an interface instead of an Object mapping. Do you have any pointers to some code of using an interface, just for reference?

I also think adding []Actors to the Team object, this may be a frequently used data, so it is less costly to retrieve if we keep it inside the main struct - that's how Project struct uses []Tasks in zentasktic_core.

As for the Resource type, in my view this is just a generic placeholder for anything that we cannot think about right now. In theory, we can track any project just with time and money, but there might be metrics we cannot think of. Happy to let this out for now.

thehowl commented 6 months ago

I also think adding []Actors to the Team object, this may be a frequently used data, so it is less costly to retrieve if we keep it inside the main struct - that's how Project struct uses []Tasks in zentasktic_core.

Just a note, use *Actor (ie. the pointer is important).

This way you can keep a tree that keeps all the actor (ID -> *Actor); and then keep the references in a []*Actor as well. Because we're talking about pointers, loading a []*Actor is cheaper than loading an []Actor; and furthermore, any changes done from an *Actor retrieved from the tree will reflect also on the []*Actor value in the Team.

Do you have any pointers to some code of using an interface, just for reference?

I wouldn't know where to get it, so here's a reference:

package main

type Workable interface {
    // nobody outside your realm will be able to implement this
    assertWorkable()
}

type Task struct {
    // ...
}
func (*Task) assertWorkable() {}

var _ Workable = &Task{}

type Project struct {
    // ...
}
func (*Project) assertWorkable() {}

var _ Workable = &Project{}

With this code, you guarantee that if you have a Workable type, you'll only be able to assign nil, *Project and *Task to it. (It kind of works like a Union, I guess.)

As for the Resource type, in my view this is just a generic placeholder for anything that we cannot think about right now. In theory, we can track any project just with time and money, but there might be metrics we cannot think of. Happy to let this out for now.

I struggle to understand where it would be used in practice. I'd suggest you drop it for now and reconsider it if there is a clear use-case; which might help to suggest also what the data structure should really look like.

irreverentsimplicity commented 6 months ago

Thanks, good point about using pointers, it's a part that I often overlook in Go, as it's not on my daily JS / React toolset. Also got your point about Workable, will implement it, thanks for the code, The Resource data type I already took out, all good.

irreverentsimplicity commented 6 months ago

Here's a basic implementation of WorkDuration:

package zentasktic_project

import (
    "strconv"
    "errors"
    "time"

    "gno.land/p/demo/avl"
    "gno.land/p/demo/zentasktic"
)

type Workable interface {
    // restrict implementation of Workable to this realm
    assertWorkable()
}

type isWorkable struct {}

type WorkableTask struct {
    zentasktic.Task
}

func (wt *WorkableTask) assertWorkable() {}

type WorkableProject struct {
    zentasktic.Project
}

func (wp *WorkableProject) assertWorkable() {}

var _ Workable = &WorkableTask{}
var _ Workable = &WorkableProject{}

type WorkDuration struct {
    Id              string `json:"workHourId"`
    ObjectId        string `json:"objectId"`
    ObjectType      string `json:"objectType"` // Task, Project
    Duration        time.Duration `json:"workDuration"`
    Workable        Workable      `json:"-"`
}

var {
    WorkDurations avl.Tree // wd.Id -> wd
}

func (wd WorkDuration) AddWorkDuration() (err errors) { 
    WorkDurations.Set(wd.Id, wd)
    return nil
}

func (wd WorkDuration) RemoveWorkDuration() (err errors) { 
    // implementation
    existingWorkDuration := WorkDuration{}
    if WorkDurations.Size() != 0 {
        existingWorkDuration, exist := WorkDurations.Get(wd.Id)
        if !exist {
            return ErrWorkDurationNotFound
        }
    }

    _, removed := WorkDurations.Remove(existingWorkDuration.Id)
    if !removed {
        return ErrTaskNotRemoved
    }
    return nil
}

// getters

func GetWorkDurationByOjectId(objectId string, objectType string) (wd WorkDuration, err errors) {
    // implementation
    workDuration := WorkDuration{}

    // Iterate over the WorkDuration AVL tree to see if we have a matching ObjectId.

    WorkDurations.Iterate("", "", func(key string, value interface{}) bool {
        if workDurationItem, ok := value.(WorkDuration); ok {
            if workDurationItem.ObjectId == objectId && workDurationItem.ObjectType == objectType {
                workDuration = workDurationItem
            }
        }
        return false // Continue iteration until all nodes have been visited.
    })

    return workDuration, nil
}

The main point is that I still kept the ObjectType for some fine grain filtering. We may need to assess the WorkDurations for specific types (Tasks / Projects). I don't see how we can do that with just the interface. Am I missing something, is this doable without keeping the ObjectType in the struct?