Skip to content

Commit 12686c7

Browse files
feat: add cluster filter in notification setting (#5828)
* query wip * feat: add cluster filtering in notification configuration * fix: sync fixes form ent * fix: add if exists/not exists in migration * refactoring: refactor beans * refactoring: refactor beans * fix: sql script fix * chore: update sql script numbers * chore: update sql script numbers
1 parent afd7845 commit 12686c7

15 files changed

+960
-782
lines changed

api/restHandler/NotificationRestHandler.go

Lines changed: 51 additions & 253 deletions
Large diffs are not rendered by default.

internal/sql/repository/NotificationSettingsRepository.go

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,12 @@
1717
package repository
1818

1919
import (
20+
"fmt"
21+
"github.com/devtron-labs/devtron/pkg/resourceQualifiers"
2022
"github.com/devtron-labs/devtron/pkg/sql"
2123
"github.com/go-pg/pg"
2224
"github.com/go-pg/pg/orm"
25+
"k8s.io/utils/pointer"
2326
"strconv"
2427
)
2528

@@ -83,6 +86,7 @@ type NotificationSettings struct {
8386
ViewId int `sql:"view_id"`
8487
NotificationRuleId int `sql:"notification_rule_id"`
8588
AdditionalConfigJson string `sql:"additional_config_json"` // user defined config json;
89+
ClusterId *int `sql:"cluster_id"`
8690
}
8791

8892
type SettingOptionDTO struct {
@@ -94,6 +98,7 @@ type SettingOptionDTO struct {
9498
PipelineType string `json:"pipelineType"`
9599
AppName string `json:"appName"`
96100
EnvironmentName string `json:"environmentName,omitempty"`
101+
ClusterName string `json:"clusterName"`
97102
}
98103

99104
func (impl *NotificationSettingsRepositoryImpl) FindNSViewCount() (int, error) {
@@ -221,40 +226,54 @@ func (impl *NotificationSettingsRepositoryImpl) DeleteNotificationSettingsViewBy
221226

222227
func (impl *NotificationSettingsRepositoryImpl) FindNotificationSettingDeploymentOptions(settingRequest *SearchRequest) ([]*SettingOptionDTO, error) {
223228
var settingOption []*SettingOptionDTO
224-
query := "SELECT p.id as pipeline_id,p.pipeline_name, env.environment_name, a.app_name FROM pipeline p" +
229+
query := "SELECT p.id as pipeline_id,p.pipeline_name, env.environment_name, a.app_name,c.cluster_name AS cluster_name " +
230+
" FROM pipeline p" +
225231
" INNER JOIN app a on a.id=p.app_id" +
226-
" INNER JOIN environment env on env.id = p.environment_id"
232+
" INNER JOIN environment env on env.id = p.environment_id " +
233+
" INNER JOIN cluster c on c.id = env.cluster_id"
227234
query = query + " WHERE p.deleted = false"
228235

236+
var envProdIdentifier *bool
237+
envIds := make([]*int, 0)
238+
for _, envId := range settingRequest.EnvId {
239+
if *envId == resourceQualifiers.AllExistingAndFutureProdEnvsInt || *envId == resourceQualifiers.AllExistingAndFutureNonProdEnvsInt {
240+
envProdIdentifier = pointer.Bool(*envId == resourceQualifiers.AllExistingAndFutureProdEnvsInt)
241+
continue
242+
}
243+
envIds = append(envIds, envId)
244+
}
245+
246+
queryParams := make([]interface{}, 0)
229247
if len(settingRequest.TeamId) > 0 {
230248
query = query + " AND a.team_id in (?)"
231-
} else if len(settingRequest.EnvId) > 0 {
232-
query = query + " AND p.environment_id in (?)"
249+
queryParams = append(queryParams, pg.In(settingRequest.TeamId))
250+
} else if len(envIds) > 0 || envProdIdentifier != nil {
251+
envQuery := ""
252+
if len(envIds) > 0 {
253+
envQuery = " p.environment_id in (?) "
254+
queryParams = append(queryParams, pg.In(envIds))
255+
}
256+
if envProdIdentifier != nil {
257+
if len(envQuery) > 0 {
258+
envQuery += " OR "
259+
}
260+
envQuery += " env.default = ? "
261+
queryParams = append(queryParams, *envProdIdentifier)
262+
263+
}
264+
query = query + fmt.Sprintf(" AND (%s)", envQuery)
233265
} else if len(settingRequest.AppId) > 0 {
234266
query = query + " AND p.app_id in (?)"
267+
queryParams = append(queryParams, pg.In(settingRequest.AppId))
235268
} else if len(settingRequest.PipelineName) > 0 {
236269
query = query + " AND p.pipeline_name like (?)"
270+
queryParams = append(queryParams, settingRequest.PipelineName)
271+
} else if len(settingRequest.ClusterId) > 0 {
272+
query = query + fmt.Sprintf(" AND env.cluster_id IN (?)")
273+
queryParams = append(queryParams, pg.In(settingRequest.ClusterId))
237274
}
238-
query = query + " GROUP BY 1,2,3,4;"
239-
240-
var err error
241-
if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) == 0 && len(settingRequest.AppId) == 0 {
242-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId))
243-
} else if len(settingRequest.TeamId) == 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) == 0 {
244-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.EnvId))
245-
} else if len(settingRequest.TeamId) == 0 && len(settingRequest.EnvId) == 0 && len(settingRequest.AppId) > 0 {
246-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.AppId))
247-
} else if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) == 0 {
248-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId), pg.In(settingRequest.EnvId))
249-
} else if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) == 0 && len(settingRequest.AppId) > 0 {
250-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId), pg.In(settingRequest.AppId))
251-
} else if len(settingRequest.TeamId) == 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) > 0 {
252-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.EnvId), pg.In(settingRequest.AppId))
253-
} else if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) > 0 {
254-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId), pg.In(settingRequest.EnvId), pg.In(settingRequest.AppId))
255-
} else if len(settingRequest.PipelineName) > 0 {
256-
_, err = impl.dbConnection.Query(&settingOption, query, settingRequest.PipelineName)
257-
}
275+
query = query + " GROUP BY 1,2,3,4,5;"
276+
_, err := impl.dbConnection.Query(&settingOption, query, queryParams...)
258277
if err != nil {
259278
return nil, err
260279
}
@@ -263,44 +282,44 @@ func (impl *NotificationSettingsRepositoryImpl) FindNotificationSettingDeploymen
263282

264283
func (impl *NotificationSettingsRepositoryImpl) FindNotificationSettingBuildOptions(settingRequest *SearchRequest) ([]*SettingOptionDTO, error) {
265284
var settingOption []*SettingOptionDTO
285+
envIds := make([]*int, 0)
286+
for _, envId := range settingRequest.EnvId {
287+
if *envId == resourceQualifiers.AllExistingAndFutureProdEnvsInt || *envId == resourceQualifiers.AllExistingAndFutureNonProdEnvsInt {
288+
continue
289+
}
290+
envIds = append(envIds, envId)
291+
}
292+
266293
query := "SELECT cip.id as pipeline_id,cip.name as pipeline_name, a.app_name from ci_pipeline cip" +
267294
" INNER JOIN app a on a.id = cip.app_id" +
268295
" INNER JOIN team t on t.id= a.team_id"
269-
if len(settingRequest.EnvId) > 0 {
296+
if len(envIds) > 0 || len(settingRequest.ClusterId) > 0 {
270297
query = query + " INNER JOIN ci_artifact cia on cia.pipeline_id = cip.id"
271298
query = query + " INNER JOIN cd_workflow wf on wf.ci_artifact_id = cia.id"
272299
query = query + " INNER JOIN pipeline p on p.id = wf.pipeline_id"
300+
query = query + " INNER JOIN environment e on e.id = p.environment_id"
273301
}
274-
query = query + " WHERE cip.deleted = false"
275302

303+
queryParams := make([]interface{}, 0)
304+
query = query + " WHERE cip.deleted = false"
276305
if len(settingRequest.TeamId) > 0 {
277306
query = query + " AND a.team_id in (?)"
278-
} else if len(settingRequest.EnvId) > 0 {
279-
query = query + " AND p.environment_id in (?)"
307+
queryParams = append(queryParams, pg.In(settingRequest.TeamId))
308+
} else if len(envIds) > 0 {
309+
query = query + " AND e.id in (?)"
310+
queryParams = append(queryParams, pg.In(envIds))
280311
} else if len(settingRequest.AppId) > 0 {
281312
query = query + " AND cip.app_id in (?)"
313+
queryParams = append(queryParams, pg.In(settingRequest.AppId))
282314
} else if len(settingRequest.PipelineName) > 0 {
283-
query = query + " AND cip.name like (?)"
315+
query = query + " AND cip.name like ?"
316+
queryParams = append(queryParams, "%"+settingRequest.PipelineName+"%")
317+
} else if len(settingRequest.ClusterId) > 0 {
318+
query = query + fmt.Sprintf(" AND e.cluster_id IN (?)")
319+
queryParams = append(queryParams, pg.In(settingRequest.ClusterId))
284320
}
285321
query = query + " GROUP BY 1,2,3;"
286-
var err error
287-
if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) == 0 && len(settingRequest.AppId) == 0 {
288-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId))
289-
} else if len(settingRequest.TeamId) == 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) == 0 {
290-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.EnvId))
291-
} else if len(settingRequest.TeamId) == 0 && len(settingRequest.EnvId) == 0 && len(settingRequest.AppId) > 0 {
292-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.AppId))
293-
} else if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) == 0 {
294-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId), pg.In(settingRequest.EnvId))
295-
} else if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) == 0 && len(settingRequest.AppId) > 0 {
296-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId), pg.In(settingRequest.AppId))
297-
} else if len(settingRequest.TeamId) == 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) > 0 {
298-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.EnvId), pg.In(settingRequest.AppId))
299-
} else if len(settingRequest.TeamId) > 0 && len(settingRequest.EnvId) > 0 && len(settingRequest.AppId) > 0 {
300-
_, err = impl.dbConnection.Query(&settingOption, query, pg.In(settingRequest.TeamId), pg.In(settingRequest.EnvId), pg.In(settingRequest.AppId))
301-
} else if len(settingRequest.PipelineName) > 0 {
302-
_, err = impl.dbConnection.Query(&settingOption, query, settingRequest.PipelineName)
303-
}
322+
_, err := impl.dbConnection.Query(&settingOption, query, queryParams...)
304323
if err != nil {
305324
return nil, err
306325
}
@@ -311,6 +330,7 @@ type SearchRequest struct {
311330
TeamId []*int `json:"teamId" validate:"number"`
312331
EnvId []*int `json:"envId" validate:"number"`
313332
AppId []*int `json:"appId" validate:"number"`
333+
ClusterId []*int `json:"clusterId" validate:"number"`
314334
PipelineName string `json:"pipelineName"`
315335
UserId int32 `json:"-"`
316336
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package notifier
2+
3+
import (
4+
"fmt"
5+
"github.com/devtron-labs/devtron/internal/util"
6+
"github.com/devtron-labs/devtron/pkg/notifier/beans"
7+
"k8s.io/utils/pointer"
8+
"maps"
9+
"testing"
10+
)
11+
12+
var logger, _ = util.NewSugardLogger()
13+
14+
func generateCombinations(testData beans.NotificationConfigRequest) []*beans.NotificationConfigRequest {
15+
var result []*beans.NotificationConfigRequest
16+
17+
// List of all fields with empty and non-empty combinations
18+
combinations := []struct {
19+
teamIds []*int
20+
appIds []*int
21+
envIds []*int
22+
clusterIds []*int
23+
pipelineId *int
24+
}{
25+
{testData.TeamId, testData.AppId, testData.EnvId, testData.ClusterId, testData.PipelineId}, // All non-empty
26+
{nil, testData.AppId, testData.EnvId, testData.ClusterId, testData.PipelineId}, // TeamId empty
27+
{testData.TeamId, nil, testData.EnvId, testData.ClusterId, testData.PipelineId}, // AppId empty
28+
{testData.TeamId, testData.AppId, nil, testData.ClusterId, testData.PipelineId}, // EnvId empty
29+
{testData.TeamId, testData.AppId, testData.EnvId, nil, testData.PipelineId}, // ClusterId empty
30+
{nil, nil, testData.EnvId, testData.ClusterId, testData.PipelineId}, // TeamId and AppId empty
31+
{nil, testData.AppId, nil, testData.ClusterId, testData.PipelineId}, // TeamId and EnvId empty
32+
{nil, testData.AppId, testData.EnvId, nil, testData.PipelineId}, // TeamId and ClusterId empty
33+
{testData.TeamId, nil, nil, testData.ClusterId, testData.PipelineId}, // AppId and EnvId empty
34+
{testData.TeamId, nil, testData.EnvId, nil, testData.PipelineId}, // AppId and ClusterId empty
35+
{testData.TeamId, testData.AppId, nil, nil, testData.PipelineId}, // EnvId and ClusterId empty
36+
{nil, nil, nil, testData.ClusterId, testData.PipelineId}, // TeamId, AppId, and EnvId empty
37+
{nil, nil, testData.EnvId, nil, testData.PipelineId}, // TeamId, AppId, and ClusterId empty
38+
{nil, testData.AppId, nil, nil, testData.PipelineId}, // TeamId, EnvId, and ClusterId empty
39+
{testData.TeamId, nil, nil, nil, testData.PipelineId}, // AppId, EnvId, and ClusterId empty
40+
{nil, nil, nil, nil, testData.PipelineId}, // All empty
41+
}
42+
43+
// Generate Test structs for all combinations
44+
for _, combo := range combinations {
45+
result = append(result, &beans.NotificationConfigRequest{
46+
TeamId: combo.teamIds,
47+
AppId: combo.appIds,
48+
EnvId: combo.envIds,
49+
ClusterId: combo.clusterIds,
50+
PipelineId: testData.PipelineId, // Keep PipelineId same for all
51+
})
52+
}
53+
54+
return result
55+
}
56+
57+
func StringifyLocalRequest(lr *beans.LocalRequest) string {
58+
params := make([]interface{}, 0)
59+
if lr.TeamId != nil {
60+
params = append(params, *lr.TeamId)
61+
}
62+
if lr.AppId != nil {
63+
params = append(params, *lr.AppId)
64+
}
65+
if lr.EnvId != nil {
66+
params = append(params, *lr.EnvId)
67+
}
68+
if lr.ClusterId != nil {
69+
params = append(params, *lr.ClusterId)
70+
}
71+
72+
if lr.PipelineId != nil {
73+
params = append(params, *lr.PipelineId)
74+
}
75+
76+
return fmt.Sprintf("TeamId: %v, AppId: %v, EnvId: %v,ClusterId: %v, PipelineId: %v", params...)
77+
}
78+
79+
func Equal(settingsV1, settingsV2 []*beans.LocalRequest) bool {
80+
settingsV1Set := make(map[string]bool)
81+
settingsV2Set := make(map[string]bool)
82+
83+
for _, setting := range settingsV1 {
84+
settingsV1Set[StringifyLocalRequest(setting)] = true
85+
}
86+
87+
for _, setting := range settingsV2 {
88+
settingsV2Set[StringifyLocalRequest(setting)] = true
89+
}
90+
91+
return maps.Equal(settingsV1Set, settingsV2Set)
92+
}
93+
94+
// TestGenerateSettings tests the generation of settings with old and new data
95+
func TestGenerateSettings(t *testing.T) {
96+
testData := beans.NotificationConfigRequest{
97+
TeamId: []*int{pointer.Int(1), pointer.Int(2)},
98+
ClusterId: []*int{pointer.Int(-1), pointer.Int(21)},
99+
EnvId: []*int{pointer.Int(-1), pointer.Int(-2), pointer.Int(56)},
100+
AppId: []*int{pointer.Int(11), pointer.Int(12), pointer.Int(13)},
101+
PipelineId: pointer.Int(100),
102+
}
103+
104+
// Generate all possible combinations
105+
combinations := generateCombinations(testData)
106+
// Test each combination
107+
for i, combo := range combinations {
108+
t.Run(fmt.Sprintf("combination-%v", i), func(tt *testing.T) {
109+
settingsV1 := combo.GenerateSettingCombinationsV1()
110+
settingsV2 := combo.GenerateSettingCombinations()
111+
112+
if len(settingsV2) == 0 {
113+
logger.Errorw("settingsV2 cannot be empty for the request", "request", *combo)
114+
tt.Fail()
115+
}
116+
117+
if combo.ClusterId != nil {
118+
if len(settingsV2) != (len(testData.ClusterId))*len(settingsV1) {
119+
tt.Fail()
120+
}
121+
} else {
122+
if !Equal(settingsV1, settingsV2) {
123+
tt.Fail()
124+
}
125+
}
126+
})
127+
}
128+
}

0 commit comments

Comments
 (0)