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

Add features to workspaces #1629

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 38 additions & 28 deletions cypress/e2e/03_features.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ describe('Create Features for Workspace', () => {
url: `${HostName}/features`,
headers: { 'x-jwt': `${value}` },
body: Features[i]
}).its('body').should('have.property', 'name', Features[i].name.trim())
.its('body').should('have.property', 'brief', Features[i].brief.trim())
.its('body').should('have.property', 'requirements', Features[i].requirements.trim())
.its('body').should('have.property', 'architecture', Features[i].architecture.trim())
}).its('body').then(body => {
expect(body).to.have.property('name').and.equal(Features[i].name.trim());
expect(body).to.have.property('brief').and.equal(Features[i].brief.trim());
expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim());
expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim());
});
}
})
})
Expand All @@ -32,10 +34,12 @@ describe('Modify name for Feature', () => {
uuid: Features[i].uuid,
name: Features[i].name + "_addtext"
}
}).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext")
.its('body').should('have.property', 'brief', Features[i].brief.trim())
.its('body').should('have.property', 'requirements', Features[i].requirements.trim())
.its('body').should('have.property', 'architecture', Features[i].architecture.trim())
}).its('body').then(body => {
expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext");
expect(body).to.have.property('brief').and.equal(Features[i].brief.trim());
expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim());
expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim());
});
}
})
})
Expand All @@ -53,10 +57,12 @@ describe('Modify brief for Feature', () => {
uuid: Features[i].uuid,
brief: Features[i].brief + "_addtext"
}
}).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext")
.its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext")
.its('body').should('have.property', 'requirements', Features[i].requirements.trim())
.its('body').should('have.property', 'architecture', Features[i].architecture.trim())
}).its('body').then(body => {
expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext");
expect(body).to.have.property('brief').and.equal(Features[i].brief.trim() + " _addtext");
expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim());
expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim());
});
}
})
})
Expand All @@ -74,10 +80,12 @@ describe('Modify requirements for Feature', () => {
uuid: Features[i].uuid,
requirements: Features[i].requirements + "_addtext"
}
}).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext")
.its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext")
.its('body').should('have.property', 'requirements', Features[i].requirements.trim() + "_addtext")
.its('body').should('have.property', 'architecture', Features[i].architecture.trim())
}).its('body').then(body => {
expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext");
expect(body).to.have.property('brief').and.equal(Features[i].brief.trim() + " _addtext");
expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim() + " _addtext");
expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim());
});
}
})
})
Expand All @@ -95,10 +103,12 @@ describe('Modify architecture for Feature', () => {
uuid: Features[i].uuid,
architecture: Features[i].architecture + "_addtext"
}
}).its('body').should('have.property', 'name', Features[i].name.trim() + "_addtext")
.its('body').should('have.property', 'brief', Features[i].brief.trim() + "_addtext")
.its('body').should('have.property', 'requirements', Features[i].requirements.trim() + "_addtext")
.its('body').should('have.property', 'architecture', Features[i].architecture.trim() + "_addtext")
}).its('body').then(body => {
expect(body).to.have.property('name').and.equal(Features[i].name.trim() + " _addtext");
expect(body).to.have.property('brief').and.equal(Features[i].brief.trim() + " _addtext");
expect(body).to.have.property('requirements').and.equal(Features[i].requirements.trim() + " _addtext");
expect(body).to.have.property('architecture').and.equal(Features[i].architecture.trim() + " _addtext");
});
}
})
})
Expand All @@ -116,10 +126,10 @@ describe('Get Features for Workspace', () => {
}).then((resp) => {
expect(resp.status).to.eq(200)
for(let i = 0; i <= 2; i++) {
expect(resp.body[i]).to.have.property('name', Features[i].name.trim() + "_addtext")
expect(resp.body[i]).to.have.property('brief', Features[i].brief.trim() + "_addtext")
expect(resp.body[i]).to.have.property('requirements', Features[i].requirements.trim() + "_addtext")
expect(resp.body[i]).to.have.property('architecture', Features[i].architecture.trim() + "_addtext")
expect(resp.body[i]).to.have.property('name', Features[i].name.trim() + " _addtext")
expect(resp.body[i]).to.have.property('brief', Features[i].brief.trim() + " _addtext")
expect(resp.body[i]).to.have.property('requirements', Features[i].requirements.trim() + " _addtext")
expect(resp.body[i]).to.have.property('architecture', Features[i].architecture.trim() + " _addtext")
}
})
})
Expand All @@ -137,10 +147,10 @@ describe('Get Feature by uuid', () => {
body: {}
}).then((resp) => {
expect(resp.status).to.eq(200)
expect(resp.body).to.have.property('name', Features[i].name.trim() + "_addtext")
expect(resp.body).to.have.property('brief', Features[i].brief.trim() + "_addtext")
expect(resp.body).to.have.property('requirements', Features[i].requirements.trim() + "_addtext")
expect(resp.body).to.have.property('architecture', Features[i].architecture.trim() + "_addtext")
expect(resp.body).to.have.property('name', Features[i].name.trim() + " _addtext")
expect(resp.body).to.have.property('brief', Features[i].brief.trim() + " _addtext")
expect(resp.body).to.have.property('requirements', Features[i].requirements.trim() + " _addtext")
expect(resp.body).to.have.property('architecture', Features[i].architecture.trim() + " _addtext")
})
}
})
Expand Down
8 changes: 4 additions & 4 deletions cypress/support/objects/objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ export const Features = [
' the base from which every technical decition relays on.<br/>' +
'We are going to leverage AI to help the PM write better definitions.<br/>' +
'The fields that would benefit form AI assistance are: mission, tactics, ' +
'feature brief and feature user stories',
'feature brief and feature user stories ',
requirements: ' Create a new page for a conversation format between the PM and the LLM<br/>' +
'Rely as much as possible on stakwork workflows<br/>' +
'Have history of previous definitions ',
Expand All @@ -110,17 +110,17 @@ export const Features = [
'Front<br/><br/> ',
},
{
uuid: 'com1l5on1e49tucv350g',
uuid: 'com1l5on1e49tucv350h',
workspace_uuid: 'cohob00n1e4808utqel0',
name: ' AI Assited relation between text fields ',
priority: 2,
brief: ' A product and feature\'s various definition fields: mission, tactics, ' +
'feature brief, user stories, requirements and architecture should have some ' +
'relation between each other.<br/>' + 'One way to do that is to leverage an LLM ' +
'to discern the parts of the defintion that have a connection to other definitions.<br/>' +
'The UI will need to show the user how each definition is related to other defintions.',
'The UI will need to show the user how each definition is related to other defintions. ',
requirements: 'Create a new process after a Feature text has changed. It should use the LLM to ' +
'determine de relationship between parts of the text.',
'determine de relationship between parts of the text. ',
architecture: 'Describe the architecture of the feature with the following sections:' +
'<br/><br/>Wireframes<br/><br/>Visual Schematics<br/><br/>Object Definition<br/><br/>' +
'DB Schema Changes<br/><br/>UX<br/><br/>CI/CD<br/><br/>Changes<br/><br/>Endpoints<br/><br/>' +
Expand Down
1 change: 1 addition & 0 deletions db/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ func InitDB() {
db.AutoMigrate(&ConnectionCodes{})
db.AutoMigrate(&BountyRoles{})
db.AutoMigrate(&UserInvoiceData{})
db.AutoMigrate(&WorkspaceFeatures{})

DB.MigrateTablesWithOrgUuid()
DB.MigrateOrganizationToWorkspace()
Expand Down
41 changes: 41 additions & 0 deletions db/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package db

import (
"strings"
"time"
)

func (db database) GetFeaturesByWorkspaceUuid(uuid string) []WorkspaceFeatures {
ms := []WorkspaceFeatures{}

db.db.Model(&WorkspaceFeatures{}).Where("workspace_uuid = ?", uuid).Order("Created").Find(&ms)

return ms
}

func (db database) GetFeatureByUuid(uuid string) WorkspaceFeatures {
ms := WorkspaceFeatures{}

db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", uuid).Find(&ms)

return ms
}

func (db database) CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error) {
m.Name = strings.TrimSpace(m.Name)
m.Brief = strings.TrimSpace(m.Brief)
m.Requirements = strings.TrimSpace(m.Requirements)
m.Architecture = strings.TrimSpace(m.Architecture)

now := time.Now()
m.Updated = &now

if db.db.Model(&m).Where("uuid = ?", m.Uuid).Updates(&m).RowsAffected == 0 {
m.Created = &now
db.db.Create(&m)
}

db.db.Model(&WorkspaceFeatures{}).Where("uuid = ?", m.Uuid).Find(&m)

return m, nil
}
3 changes: 3 additions & 0 deletions db/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,7 @@ type Database interface {
PersonUniqueNameFromName(name string) (string, error)
ProcessAlerts(p Person)
UserHasAccess(pubKeyFromAuth string, uuid string, role string) bool
CreateOrEditFeature(m WorkspaceFeatures) (WorkspaceFeatures, error)
GetFeaturesByWorkspaceUuid(uuid string) []WorkspaceFeatures
GetFeatureByUuid(uuid string) WorkspaceFeatures
}
14 changes: 14 additions & 0 deletions db/structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,20 @@ type WorkspaceUsersData struct {
Person
}

type WorkspaceFeatures struct {
ID uint `json:"id"`
Uuid string `json:"uuid"`
WorkspaceUuid string `json:"workspace_uuid"`
Name string `json:"name"`
Brief string `json:"brief"`
Requirements string `json:"requirements"`
Architecture string `json:"architecture"`
Created *time.Time `json:"created"`
Updated *time.Time `json:"updated"`
CreatedBy string `json:"created_by"`
UpdatedBy string `json:"updated_by"`
}

type BountyRoles struct {
Name string `json:"name"`
}
Expand Down
93 changes: 93 additions & 0 deletions handlers/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package handlers

import (
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/go-chi/chi"
"github.com/stakwork/sphinx-tribes/auth"
"github.com/stakwork/sphinx-tribes/db"
)

type featureHandler struct {
db db.Database
}

func NewFeatureHandler(database db.Database) *featureHandler {
return &featureHandler{
db: database,
}
}

func (oh *featureHandler) CreateOrEditFeatures(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
if pubKeyFromAuth == "" {
fmt.Println("no pubkey from auth")
w.WriteHeader(http.StatusUnauthorized)
return
}

features := db.WorkspaceFeatures{}
body, _ := io.ReadAll(r.Body)
r.Body.Close()
err := json.Unmarshal(body, &features)

if err != nil {
fmt.Println(err)
w.WriteHeader(http.StatusNotAcceptable)
return
}

// Validate struct data
err = db.Validate.Struct(features)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
msg := fmt.Sprintf("Error: did not pass validation test : %s", err)
json.NewEncoder(w).Encode(msg)
return
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AbdulWahab3181 please populate the created_by field with the user public key

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@elraphty Addressed

}

p, err := oh.db.CreateOrEditFeature(features)
if err != nil {
w.WriteHeader(http.StatusBadRequest)
return
}

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(p)
}

func (oh *featureHandler) GetFeaturesByWorkspaceUuid(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
if pubKeyFromAuth == "" {
fmt.Println("no pubkey from auth")
w.WriteHeader(http.StatusUnauthorized)
return
}

uuid := chi.URLParam(r, "uuid")
workspaceFeatures := oh.db.GetFeaturesByWorkspaceUuid(uuid)

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(workspaceFeatures)
}

func (oh *featureHandler) GetFeatureByUuid(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
pubKeyFromAuth, _ := ctx.Value(auth.ContextKey).(string)
if pubKeyFromAuth == "" {
fmt.Println("no pubkey from auth")
w.WriteHeader(http.StatusUnauthorized)
return
}

uuid := chi.URLParam(r, "uuid")
workspaceFeature := oh.db.GetFeatureByUuid(uuid)

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(workspaceFeature)
}
Loading
Loading