Skip to content

Commit

Permalink
Merge pull request #1629 from AbdulWahab3181/feature/add-feature-endp…
Browse files Browse the repository at this point in the history
…oints

Add features to workspaces
  • Loading branch information
elraphty committed May 2, 2024
2 parents ddf56a8 + 3008133 commit 3c8fdc1
Show file tree
Hide file tree
Showing 10 changed files with 368 additions and 32 deletions.
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
95 changes: 95 additions & 0 deletions handlers/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
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
}

features.CreatedBy = pubKeyFromAuth

// 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
}

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

0 comments on commit 3c8fdc1

Please sign in to comment.