Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
36 changes: 36 additions & 0 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,42 @@ func (c *Client) RemoveFeatureOwners(feature *Feature, ownerIDs []int64) error {
return nil
}

func (c *Client) manageFeatureGroupOwners(feature *Feature, groupIDs []int64, endpoint string) (*resty.Response, error) {
if feature.ProjectID == nil || feature.ID == nil {
return nil, fmt.Errorf("flagsmithapi: feature.ProjectID and feature.ID are required")
}
url := fmt.Sprintf("%s/projects/%d/features/%d/%s/", c.baseURL, *feature.ProjectID, *feature.ID, endpoint)
Comment thread
Zaimwa9 marked this conversation as resolved.
body := struct {
GroupIDs []int64 `json:"group_ids"`
}{
GroupIDs: groupIDs,
}
resp, err := c.client.R().SetBody(body).Post(url)
return resp, err
}

func (c *Client) AddFeatureGroupOwners(feature *Feature, groupIDs []int64) error {
resp, err := c.manageFeatureGroupOwners(feature, groupIDs, "add-group-owners")
if err != nil {
return err
}
if !resp.IsSuccess() {
return fmt.Errorf("flagsmithapi: Error adding feature group owners: %s", resp)
}
return nil
}

func (c *Client) RemoveFeatureGroupOwners(feature *Feature, groupIDs []int64) error {
resp, err := c.manageFeatureGroupOwners(feature, groupIDs, "remove-group-owners")
if err != nil {
return err
}
if !resp.IsSuccess() {
return fmt.Errorf("flagsmithapi: Error removing feature group owners: %s", resp)
}
return nil
}

func (c *Client) GetFeatureMVOption(featureUUID, mvOptionUUID string) (*FeatureMultivariateOption, error) {
url := fmt.Sprintf("%s/multivariate/options/get-by-uuid/%s/", c.baseURL, mvOptionUUID)
featureMVOption := FeatureMultivariateOption{}
Expand Down
131 changes: 130 additions & 1 deletion client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,8 @@ const GetProjectResponseJson = `
"hide_disabled_flags": false,
"enable_dynamo_db": true,
"migration_status": "NOT_APPLICABLE",
"use_edge_identities": false
"use_edge_identities": false,
"enforce_feature_owners": true
}
`
const ProjectID int64 = 10
Expand Down Expand Up @@ -264,6 +265,16 @@ const CreateFeatureResponseJson = `
"id": 2,
"email": "some_other_user@email.com"
}
],
"group_owners": [
{
"id": 3,
"name": "Test Group"
},
{
"id": 4,
"name": "Another Group"
}
]
}

Expand Down Expand Up @@ -483,6 +494,9 @@ func TestGetFeature(t *testing.T) {
expectedOwners := []int64{1, 2}
assert.Equal(t, &expectedOwners, feature.Owners)

expectedGroupOwners := []int64{3, 4}
assert.Equal(t, &expectedGroupOwners, feature.GroupOwners)

expectedTags := []int64{1}
assert.Equal(t, expectedTags, feature.Tags)

Expand Down Expand Up @@ -637,6 +651,121 @@ func TestRemoveFeatureOwners(t *testing.T) {

}

func TestAddFeatureGroupOwners(t *testing.T) {
// Given
projectID := ProjectID
featureID := FeatureID

description := "feature description"

feature := flagsmithapi.Feature{
Name: FeatureName,
ID: &featureID,
ProjectUUID: ProjectUUID,
ProjectID: &projectID,
Description: &description,
}
groupIDs := []int64{3, 4}
expectedRequestBody := `{"group_ids":[3,4]}`

server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
assert.Equal(t, fmt.Sprintf("/api/v1/projects/%d/features/%d/add-group-owners/", ProjectID, FeatureID), req.URL.Path)
assert.Equal(t, "POST", req.Method)
assert.Equal(t, "Api-Key "+MasterAPIKey, req.Header.Get("Authorization"))

// Test that we sent the correct body
rawBody, err := io.ReadAll(req.Body)
assert.NoError(t, err)
assert.Equal(t, expectedRequestBody, string(rawBody))

rw.Header().Set("Content-Type", "application/json")
_, err = io.WriteString(rw, CreateFeatureResponseJson)
assert.NoError(t, err)

}))
defer server.Close()

client := flagsmithapi.NewClient(MasterAPIKey, server.URL+"/api/v1")

// When
err := client.AddFeatureGroupOwners(&feature, groupIDs)

// Then
assert.NoError(t, err)

}

func TestRemoveFeatureGroupOwners(t *testing.T) {
// Given
projectID := ProjectID
featureID := FeatureID

description := "feature description"

feature := flagsmithapi.Feature{
Name: FeatureName,
ID: &featureID,
ProjectUUID: ProjectUUID,
ProjectID: &projectID,
Description: &description,
}
groupIDs := []int64{3, 4}

expectedRequestBody := `{"group_ids":[3,4]}`

server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
assert.Equal(t, fmt.Sprintf("/api/v1/projects/%d/features/%d/remove-group-owners/", ProjectID, FeatureID), req.URL.Path)
assert.Equal(t, "POST", req.Method)
assert.Equal(t, "Api-Key "+MasterAPIKey, req.Header.Get("Authorization"))

// Test that we sent the correct body
rawBody, err := io.ReadAll(req.Body)
assert.NoError(t, err)
assert.Equal(t, expectedRequestBody, string(rawBody))

rw.Header().Set("Content-Type", "application/json")
_, err = io.WriteString(rw, CreateFeatureResponseJson)
assert.NoError(t, err)

}))
defer server.Close()

client := flagsmithapi.NewClient(MasterAPIKey, server.URL+"/api/v1")

// When
err := client.RemoveFeatureGroupOwners(&feature, groupIDs)

// Then
assert.NoError(t, err)

}

Comment thread
gagantrivedi marked this conversation as resolved.
func TestAddFeatureGroupOwnersMissingParams(t *testing.T) {
// Given
client := flagsmithapi.NewClient(MasterAPIKey, "http://localhost/api/v1")
fID := FeatureID
pID := ProjectID

featureWithoutID := flagsmithapi.Feature{Name: "test", ProjectID: &pID}
featureWithoutProjectID := flagsmithapi.Feature{Name: "test", ID: &fID}
featureWithoutBoth := flagsmithapi.Feature{Name: "test"}

// When / Then - missing ID
err := client.AddFeatureGroupOwners(&featureWithoutID, []int64{1})
assert.Error(t, err)
assert.Contains(t, err.Error(), "feature.ProjectID and feature.ID are required")

// missing ProjectID
err = client.RemoveFeatureGroupOwners(&featureWithoutProjectID, []int64{1})
assert.Error(t, err)
assert.Contains(t, err.Error(), "feature.ProjectID and feature.ID are required")

// missing both
err = client.AddFeatureGroupOwners(&featureWithoutBoth, []int64{1})
assert.Error(t, err)
assert.Contains(t, err.Error(), "feature.ProjectID and feature.ID are required")
}

// 200 is arbitrarily chosen to avoid collision with other ids
const MVFeatureOptionID int64 = 200
const MVFeatureOptionUUID = "8d3512d3-721a-4cae-9855-56c02cb0afe9"
Expand Down
7 changes: 7 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ type SegmentNotFoundError struct {
type FeatureMVOptionNotFoundError struct {
featureMVOptionUUID string
}
type UserNotFoundError struct {
email string
}

func (e FeatureNotFoundError) Error() string {
return fmt.Sprintf("flagsmithapi: feature '%s' not found", e.featureUUID)
Expand All @@ -32,3 +35,7 @@ func (e FeatureStateNotFoundError) Error() string {
func (e FeatureMVOptionNotFoundError) Error() string {
return fmt.Sprintf("flagsmithapi: feature mv option '%s' not found", e.featureMVOptionUUID)
}

func (e UserNotFoundError) Error() string {
return fmt.Sprintf("flagsmithapi: user with email '%s' not found", e.email)
}
46 changes: 35 additions & 11 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type Project struct {
FeatureNameRegex string `json:"feature_name_regex,omitempty"`
StaleFlagsLimitDays int64 `json:"stale_flags_limit_days,omitempty"`
EnableRealtimeUpdates bool `json:"enable_realtime_updates,omitempty"`
EnforceFeatureOwners bool `json:"enforce_feature_owners,omitempty"`
}

type FeatureMultivariateOption struct {
Expand All @@ -43,6 +44,7 @@ type Feature struct {
DefaultEnabled bool `json:"default_enabled,omitempty"`
IsArchived bool `json:"is_archived,omitempty"`
Owners *[]int64 `json:"owners,omitempty"`
GroupOwners *[]int64 `json:"group_owners,omitempty"`
Tags []int64 `json:"tags"`

ProjectUUID string `json:"-"`
Expand All @@ -53,18 +55,22 @@ func (f *Feature) UnmarshalJSON(data []byte) error {
type owner struct {
ID int64 `json:"id"`
}
type groupOwner struct {
ID int64 `json:"id"`
}
var obj struct {
Name string `json:"name"`
UUID string `json:"uuid,omitempty"`
ID *int64 `json:"id,omitempty"`
Type *string `json:"type,omitempty"`
Description *string `json:"description,omitempty"`
InitialValue string `json:"initial_value,omitempty"`
DefaultEnabled bool `json:"default_enabled,omitempty"`
IsArchived bool `json:"is_archived,omitempty"`
Owners []owner `json:"owners,omitempty"`
ProjectID *int64 `json:"project,omitempty"`
Tags []int64 `json:"tags"`
Name string `json:"name"`
UUID string `json:"uuid,omitempty"`
ID *int64 `json:"id,omitempty"`
Type *string `json:"type,omitempty"`
Description *string `json:"description,omitempty"`
InitialValue string `json:"initial_value,omitempty"`
DefaultEnabled bool `json:"default_enabled,omitempty"`
IsArchived bool `json:"is_archived,omitempty"`
Owners []owner `json:"owners,omitempty"`
GroupOwners []groupOwner `json:"group_owners,omitempty"`
ProjectID *int64 `json:"project,omitempty"`
Tags []int64 `json:"tags"`
}

err := json.Unmarshal(data, &obj)
Expand All @@ -89,6 +95,13 @@ func (f *Feature) UnmarshalJSON(data []byte) error {
*f.Owners = append(*f.Owners, o.ID)
}
}
if obj.GroupOwners != nil {
groups := make([]int64, 0, len(obj.GroupOwners))
for _, g := range obj.GroupOwners {
groups = append(groups, g.ID)
}
f.GroupOwners = &groups
}
return nil
}

Expand Down Expand Up @@ -270,3 +283,14 @@ type Organisation struct {
RestrictProjectCreateToAdmin bool `json:"restrict_project_create_to_admin"`
PersistTraitData bool `json:"persist_trait_data"`
}

type User struct {
ID int64 `json:"id"`
Email string `json:"email"`
FirstName string `json:"first_name"`
LastName string `json:"last_name"`
LastLogin string `json:"last_login"`
DateJoined string `json:"date_joined"`
UUID string `json:"uuid"`
Role string `json:"role"`
}
Comment thread
Zaimwa9 marked this conversation as resolved.
30 changes: 30 additions & 0 deletions organisation.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,33 @@ func (c *Client) GetOrganisationByUUID(orgUUID string) (*Organisation, error) {
return &organisation, nil

}

func (c *Client) GetOrganisationUsers(orgID int64) ([]User, error) {
url := fmt.Sprintf("%s/organisations/%d/users/", c.baseURL, orgID)
users := []User{}
resp, err := c.client.R().
SetResult(&users).
Get(url)

if err != nil {
return nil, err
}
if !resp.IsSuccess() {
return nil, fmt.Errorf("flagsmithapi: Error getting organisation users: %s", resp)
}
return users, nil
}

func (c *Client) GetOrganisationUserByEmail(orgID int64, email string) (*User, error) {
users, err := c.GetOrganisationUsers(orgID)
if err != nil {
return nil, err
}
for i := range users {
if users[i].Email == email {
u := users[i]
return &u, nil
}
}
return nil, UserNotFoundError{email: email}
}
Loading
Loading