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

Feat: Added support for multiple project owners #4597

Open
wants to merge 30 commits into
base: multiple-owner-support
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3a4ee69
Added new route .
aryan-bhokare Apr 23, 2024
d2d8f17
Added `CreateProject` modal.
aryan-bhokare Apr 23, 2024
e9d81ad
Some changes in `CreateProjectView`.
aryan-bhokare Apr 24, 2024
e0b4518
Added `ProjectDashboardCardMenu`.
aryan-bhokare Apr 24, 2024
43bdca2
Added `ProjectDashboardCard`.
aryan-bhokare Apr 24, 2024
d368644
Added `DeleteProject` API mutations.
aryan-bhokare Apr 24, 2024
409beb9
Added `ProjectDashboard`.
aryan-bhokare Apr 24, 2024
28b890c
Added image and strings.
aryan-bhokare Apr 24, 2024
3255daa
Modified `project entities`.
aryan-bhokare May 2, 2024
f528e10
[Backend] Modification in Backend for the UI.
aryan-bhokare May 4, 2024
b0cc1ce
Added `project_util` for validation of input request.
aryan-bhokare May 4, 2024
415a34f
Indent Fixes
aryan-bhokare May 4, 2024
1fa5917
Modification for Frontend Hook of `CreateProject` API.
aryan-bhokare May 4, 2024
8d5113e
Modified `ListProject` Query frontend hook.
aryan-bhokare May 4, 2024
1eeae8b
Removed string constants and some minor changes.
aryan-bhokare May 4, 2024
be4086a
Added Project Filters.
aryan-bhokare May 4, 2024
c991380
Added pagination and filter subheader in Dashboard.
aryan-bhokare May 4, 2024
d8c14df
modified auth-api swagger file.
aryan-bhokare May 4, 2024
7b9f489
Added tags section in create-project modal.
aryan-bhokare May 4, 2024
8b5bf97
Changes due to modification of API and addition of new strings
aryan-bhokare May 4, 2024
724c606
minor changes and resolved some errors.
aryan-bhokare May 4, 2024
d60cf2e
Added routing when clicked on the card.
aryan-bhokare May 8, 2024
2eb6a17
Modifications in backend tests as per API updates.
aryan-bhokare May 8, 2024
a3d5b60
Fix: NoProjects Element and NoFilteredProject Results element.
aryan-bhokare May 8, 2024
923d281
Added scroll for the project list.
aryan-bhokare May 8, 2024
a24ba5e
Some changes in UI w.r.t Multiple Project Owner Feature.
aryan-bhokare May 8, 2024
6747199
Made search text type insensitive.
aryan-bhokare May 10, 2024
554e008
Update chaoscenter/web/src/controllers/ProjectDashboard/ProjectFilter…
aryan-bhokare May 25, 2024
42da32e
requested changes.
aryan-bhokare May 25, 2024
4dbd103
removed unnecessary handle function
aryan-bhokare May 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
52 changes: 35 additions & 17 deletions chaoscenter/authentication/api/handlers/rest/project_handler.go
Expand Up @@ -55,14 +55,17 @@ func GetUserWithProject(service services.ApplicationService) gin.HandlerFunc {

outputUser := user.GetUserWithProject()
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the function return any error which needs to be handled?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

umm it actually does not return any error


projects, err := service.GetProjectsByUserID(outputUser.ID, false)
request := utils.GetProjectFilters(c)
request.UserID = outputUser.ID

response, err := service.GetProjectsByUserID(request)
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we remove the user.GetUserWithProject() func call completely and use just the above function? Either we can get userID from getUser() or tweak the above function to search by username as well

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sure we remove user.GetUserWithProject and user GetUser and GetProjectsByUserID

if err != nil {
log.Error(err)
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}

outputUser.Projects = projects
outputUser.Projects = response.Projects

c.JSON(http.StatusOK, gin.H{"data": outputUser})
}
Expand Down Expand Up @@ -119,9 +122,10 @@ func GetProject(service services.ApplicationService) gin.HandlerFunc {
// GetProjectsByUserID queries the project with a given userID from the database and returns it in the appropriate format
func GetProjectsByUserID(service services.ApplicationService) gin.HandlerFunc {
return func(c *gin.Context) {
uID := c.MustGet("uid").(string)
projects, err := service.GetProjectsByUserID(uID, false)
if projects == nil {
request := utils.GetProjectFilters(c)

response, err := service.GetProjectsByUserID(request)
if response == nil || (response.TotalNumberOfProjects != nil && *response.TotalNumberOfProjects == 0) {
c.JSON(http.StatusOK, gin.H{
"message": "No projects found",
})
Expand All @@ -132,7 +136,7 @@ func GetProjectsByUserID(service services.ApplicationService) gin.HandlerFunc {
return
}

c.JSON(http.StatusOK, gin.H{"data": projects})
c.JSON(http.StatusOK, gin.H{"data": response})
}
}

Expand Down Expand Up @@ -300,6 +304,18 @@ func CreateProject(service services.ApplicationService) gin.HandlerFunc {
return
}

if userRequest.Description == nil {
// If description is not provided, set it to an empty string
emptyDescription := ""
userRequest.Description = &emptyDescription
}

if userRequest.Tags == nil {
// If tags are not provided, set it to an empty slice
emptyTags := make([]*string, 0)
userRequest.Tags = emptyTags
}

userRequest.UserID = c.MustGet("uid").(string)

user, err := service.GetUser(userRequest.UserID)
Expand Down Expand Up @@ -333,10 +349,12 @@ func CreateProject(service services.ApplicationService) gin.HandlerFunc {
members = append(members, newMember)
state := "active"
Copy link
Contributor

Choose a reason for hiding this comment

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

create an enum for the states

newProject := &entities.Project{
ID: pID,
Name: userRequest.ProjectName,
Members: members,
State: &state,
ID: pID,
Name: userRequest.ProjectName,
Members: members,
State: &state,
Description: userRequest.Description,
Tags: userRequest.Tags,
Audit: entities.Audit{
IsRemoved: false,
CreatedAt: time.Now().UnixMilli(),
Expand Down Expand Up @@ -591,7 +609,7 @@ func LeaveProject(service services.ApplicationService) gin.HandlerFunc {
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidRequest], presenter.CreateErrorResponse(utils.ErrInvalidRequest))
return
}

if member.Role != nil && *member.Role == entities.RoleOwner {
owners, err := service.GetProjectOwners(member.ProjectID)
if err != nil {
Expand All @@ -605,7 +623,7 @@ func LeaveProject(service services.ApplicationService) gin.HandlerFunc {
return
}
}

err = validations.RbacValidator(c.MustGet("uid").(string), member.ProjectID,
validations.MutationRbacRules["leaveProject"],
string(entities.AcceptedInvitation),
Expand Down Expand Up @@ -787,7 +805,7 @@ func UpdateMemberRole(service services.ApplicationService) gin.HandlerFunc {
return
}


// Validating member role
if member.Role == nil || (*member.Role != entities.RoleEditor && *member.Role != entities.RoleViewer && *member.Role != entities.RoleOwner) {
c.JSON(utils.ErrorStatusCodes[utils.ErrInvalidRole], presenter.CreateErrorResponse(utils.ErrInvalidRole))
Expand All @@ -805,13 +823,13 @@ func UpdateMemberRole(service services.ApplicationService) gin.HandlerFunc {
presenter.CreateErrorResponse(utils.ErrUnauthorized))
return
}

uid := c.MustGet("uid").(string)
if uid == member.UserID {
c.JSON(http.StatusBadRequest, gin.H{"message": "User cannot change their own role."})
return
}

err = service.UpdateMemberRole(member.ProjectID, member.UserID, member.Role)
if err != nil {
log.Error(err)
Expand Down Expand Up @@ -895,7 +913,7 @@ func GetProjectRole(service services.ApplicationService) gin.HandlerFunc {

}
}

// DeleteProject godoc
//
// @Description Delete a project.
Expand Down Expand Up @@ -930,7 +948,7 @@ func DeleteProject (service services.ApplicationService) gin.HandlerFunc {
c.JSON(utils.ErrorStatusCodes[utils.ErrServerError], presenter.CreateErrorResponse(utils.ErrServerError))
return
}

c.JSON(http.StatusOK, gin.H{
"message": "Successfully deleted project.",
})
Expand Down
109 changes: 97 additions & 12 deletions chaoscenter/authentication/api/handlers/rest/project_handler_test.go
Expand Up @@ -34,10 +34,15 @@ func TestGetUserWithProject(t *testing.T) {
Username: "testUser",
Email: "test@example.com",
}
project := &entities.Project{}
response := &entities.ListProjectResponse{}

request := &entities.ListProjectRequest{
UserID: "testUID",
}


service.On("FindUserByUsername", "testUser").Return(user, errors.New("failed"))
service.On("GetProjectsByUserID", "testUID", false).Return([]*entities.Project{project}, errors.New("failed"))
service.On("GetProjectsByUserID", request).Return(response, errors.New("failed"))

rest.GetUserWithProject(service)(c)

Expand All @@ -60,10 +65,31 @@ func TestGetUserWithProject(t *testing.T) {
Username: "testUser1",
Email: "test@example.com",
}
project := &entities.Project{}

response := &entities.ListProjectResponse{}

fieldName := entities.ProjectSortingFieldTime

request := &entities.ListProjectRequest{
UserID: "testUID",
Pagination: &entities.Pagination{
Page: 0,
Limit: 15,
},
Sort: &entities.SortInput{
Field: &fieldName,
Ascending: nil,
},
Filter: &entities.ListProjectInputFilter{
CreatedByMe: nil,
InvitedByOthers: nil,
ProjectName: nil,
},
}


service.On("FindUserByUsername", "testUser1").Return(user, nil)
service.On("GetProjectsByUserID", "testUID", false).Return([]*entities.Project{project}, nil)
service.On("GetProjectsByUserID", request).Return(response, nil)

rest.GetUserWithProject(service)(c)

Expand All @@ -87,10 +113,29 @@ func TestGetUserWithProject(t *testing.T) {
Email: "test@example.com",
Role: entities.RoleAdmin,
}
project := &entities.Project{}
response := &entities.ListProjectResponse{}

fieldName := entities.ProjectSortingFieldTime

request := &entities.ListProjectRequest{
UserID: "testUID",
Pagination: &entities.Pagination{
Page: 0,
Limit: 15,
},
Sort: &entities.SortInput{
Field: &fieldName,
Ascending: nil,
},
Filter: &entities.ListProjectInputFilter{
CreatedByMe: nil,
InvitedByOthers: nil,
ProjectName: nil,
},
}

service.On("FindUserByUsername", "testUser").Return(user, nil)
service.On("GetProjectsByUserID", "testUID", false).Return([]*entities.Project{project}, nil)
service.On("GetProjectsByUserID", request).Return(response, nil)

rest.GetUserWithProject(service)(c)

Expand All @@ -106,14 +151,30 @@ func TestGetProjectsByUserID(t *testing.T) {
w := httptest.NewRecorder()
ctx := GetTestGinContext(w)
ctx.Set("uid", "testUserID")
projects := []*entities.Project{
{
ID: "testProjectID",
Name: "Test Project",

response := &entities.ListProjectResponse{}

fieldName := entities.ProjectSortingFieldTime

request := &entities.ListProjectRequest{
UserID: "testUserID",
Pagination: &entities.Pagination{
Page: 0,
Limit: 15,
},
Sort: &entities.SortInput{
Field: &fieldName,
Ascending: nil,
},
Filter: &entities.ListProjectInputFilter{
CreatedByMe: nil,
InvitedByOthers: nil,
ProjectName: nil,
},
}

service := new(mocks.MockedApplicationService)
service.On("GetProjectsByUserID", "testUserID", false).Return(projects, errors.New("Failed"))
service.On("GetProjectsByUserID", request).Return(response, errors.New("Failed"))
rest.GetProjectsByUserID(service)(ctx)
assert.Equal(t, utils.ErrorStatusCodes[utils.ErrServerError], w.Code)
})
Expand All @@ -129,8 +190,32 @@ func TestGetProjectsByUserID(t *testing.T) {
Name: "Test Project",
},
}

response := &entities.ListProjectResponse{
Projects: projects,
}

fieldName := entities.ProjectSortingFieldTime

request := &entities.ListProjectRequest{
UserID: "testUserID",
Pagination: &entities.Pagination{
Page: 0,
Limit: 15,
},
Sort: &entities.SortInput{
Field: &fieldName,
Ascending: nil,
},
Filter: &entities.ListProjectInputFilter{
CreatedByMe: nil,
InvitedByOthers: nil,
ProjectName: nil,
},
}

service := new(mocks.MockedApplicationService)
service.On("GetProjectsByUserID", "testUserID", false).Return(projects, nil)
service.On("GetProjectsByUserID", request).Return(response, nil)
rest.GetProjectsByUserID(service)(ctx)
assert.Equal(t, http.StatusOK, w.Code)
})
Expand Down
8 changes: 4 additions & 4 deletions chaoscenter/authentication/api/mocks/rest_mocks.go
Expand Up @@ -90,9 +90,9 @@ func (m *MockedApplicationService) GetProjects(query bson.D) ([]*entities.Projec
return args.Get(0).([]*entities.Project), args.Error(1)
}

func (m *MockedApplicationService) GetProjectsByUserID(uid string, isOwner bool) ([]*entities.Project, error) {
args := m.Called(uid, isOwner)
return args.Get(0).([]*entities.Project), args.Error(1)
func (m *MockedApplicationService) GetProjectsByUserID(request *entities.ListProjectRequest) (*entities.ListProjectResponse, error) {
args := m.Called(request)
return args.Get(0).(*entities.ListProjectResponse), args.Error(1)
}

func (m *MockedApplicationService) GetProjectStats() ([]*entities.ProjectStats, error) {
Expand Down Expand Up @@ -213,4 +213,4 @@ func (m *MockedApplicationService) RbacValidator(userID, resourceID string, rule
func (m *MockedApplicationService) DeleteProject(projectID string) error {
args := m.Called(projectID)
return args.Error(0)
}
}