Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Data types to enrich Decide #1

Closed
irreverentsimplicity opened this issue Apr 14, 2024 · 5 comments
Closed

Data types to enrich Decide #1

irreverentsimplicity opened this issue Apr 14, 2024 · 5 comments
Assignees

Comments

@irreverentsimplicity
Copy link
Owner

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
}
@irreverentsimplicity irreverentsimplicity self-assigned this Apr 15, 2024
@thehowl
Copy link

thehowl commented Apr 15, 2024

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
Copy link
Owner Author

@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
Copy link

thehowl commented Apr 16, 2024

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
Copy link
Owner Author

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
Copy link
Owner Author

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants