@@ -20,6 +20,7 @@ import (
20
20
"encoding/json"
21
21
"errors"
22
22
"github.com/devtron-labs/devtron/internal/sql/repository"
23
+ "github.com/devtron-labs/devtron/pkg/attributes/bean"
23
24
"github.com/go-pg/pg"
24
25
"go.uber.org/zap"
25
26
"reflect"
@@ -99,92 +100,181 @@ func (impl UserAttributesServiceImpl) UpdateUserAttributes(request *UserAttribut
99
100
}
100
101
101
102
func (impl UserAttributesServiceImpl ) PatchUserAttributes (request * UserAttributesDto ) (* UserAttributesDto , error ) {
102
- userAttribute , err := impl .GetUserAttribute (request )
103
+ existingAttribute , err := impl .GetUserAttribute (request )
103
104
if err != nil {
104
105
impl .logger .Errorw ("error while getting user attributes during patch request" , "req" , request , "error" , err )
105
- return nil , errors .New ("error occurred while updating user attributes" )
106
+ return nil , errors .New ("error occurred while getting user attributes" )
106
107
}
107
- if userAttribute == nil {
108
+
109
+ if existingAttribute == nil {
108
110
impl .logger .Info ("no data found for request, so going to add instead of update" , "req" , request )
109
- attributes , err := impl .AddUserAttributes (request )
111
+ newAttribute , err := impl .AddUserAttributes (request )
110
112
if err != nil {
111
113
impl .logger .Errorw ("error in adding new user attributes" , "req" , request , "error" , err )
112
- return nil , errors .New ("error occurred while updating user attributes" )
114
+ return nil , errors .New ("error occurred while adding user attributes" )
113
115
}
114
- return attributes , nil
116
+ return newAttribute , nil
115
117
}
116
118
117
- // Parse existing JSON
118
- var existingData map [string ]interface {}
119
- if userAttribute .Value != "" {
120
- err = json .Unmarshal ([]byte (userAttribute .Value ), & existingData )
121
- if err != nil {
122
- impl .logger .Errorw ("error parsing existing json value" , "value" , userAttribute .Value , "error" , err )
123
- return nil , errors .New ("error occurred while updating user attributes" )
124
- }
125
- } else {
126
- existingData = make (map [string ]interface {})
119
+ existingData , err := impl .parseJSONValue (existingAttribute .Value , "existing" )
120
+ if err != nil {
121
+ return nil , err
127
122
}
128
123
129
- // Parse new JSON
130
- var newData map [string ]interface {}
131
- if request .Value != "" {
132
- err = json .Unmarshal ([]byte (request .Value ), & newData )
133
- if err != nil {
134
- impl .logger .Errorw ("error parsing request json value" , "value" , request .Value , "error" , err )
135
- return nil , errors .New ("error occurred while updating user attributes" )
136
- }
137
- } else {
138
- newData = make (map [string ]interface {})
124
+ newData , err := impl .parseJSONValue (request .Value , "request" )
125
+ if err != nil {
126
+ return nil , err
139
127
}
140
128
141
- // Check if there are any changes
142
- anyChanges := false
129
+ // Merge the data
130
+ hasChanges := impl .mergeUserAttributesData (existingData , newData )
131
+ if ! hasChanges {
132
+ impl .logger .Infow ("no changes detected, skipping update" , "key" , request .Key )
133
+ return existingAttribute , nil
134
+ }
143
135
144
- // Merge the objects (patch style)
145
- for key , newValue := range newData {
146
- existingValue , exists := existingData [key ]
147
- if ! exists || ! reflect .DeepEqual (existingValue , newValue ) {
148
- existingData [key ] = newValue
149
- anyChanges = true
150
- }
136
+ // Update in database and return result
137
+ return impl .updateAttributeInDatabase (request , existingData )
138
+ }
139
+
140
+ // parseJSONValue parses a JSON string into a map, with proper error handling
141
+ func (impl UserAttributesServiceImpl ) parseJSONValue (jsonValue , context string ) (map [string ]interface {}, error ) {
142
+ var data map [string ]interface {}
143
+
144
+ if jsonValue == "" {
145
+ return make (map [string ]interface {}), nil
151
146
}
152
147
153
- // If no changes, return the existing data
154
- if ! anyChanges {
155
- impl .logger .Infow ( "no change detected, skipping update" , "key " , request . Key )
156
- return userAttribute , nil
148
+ err := json . Unmarshal ([] byte ( jsonValue ), & data )
149
+ if err != nil {
150
+ impl .logger .Errorw ( "error parsing JSON value" , "context" , context , "value" , jsonValue , "error " , err )
151
+ return nil , errors . New ( "error occurred while parsing user attributes data" )
157
152
}
158
153
159
- // Convert back to JSON string
160
- mergedJson , err := json .Marshal (existingData )
154
+ return data , nil
155
+ }
156
+
157
+ // updateAttributeInDatabase updates the merged data in the database
158
+ func (impl UserAttributesServiceImpl ) updateAttributeInDatabase (request * UserAttributesDto , mergedData map [string ]interface {}) (* UserAttributesDto , error ) {
159
+ // Convert merged data back to JSON
160
+ mergedJSON , err := json .Marshal (mergedData )
161
161
if err != nil {
162
- impl .logger .Errorw ("error converting merged data to json " , "data" , existingData , "error" , err )
163
- return nil , errors .New ("error occurred while updating user attributes" )
162
+ impl .logger .Errorw ("error converting merged data to JSON " , "data" , mergedData , "error" , err )
163
+ return nil , errors .New ("error occurred while processing user attributes" )
164
164
}
165
165
166
+ // Create DAO for database update
166
167
dao := & repository.UserAttributesDao {
167
168
EmailId : request .EmailId ,
168
169
Key : request .Key ,
169
- Value : string (mergedJson ),
170
+ Value : string (mergedJSON ),
170
171
UserId : request .UserId ,
171
172
}
172
173
174
+ // Update in database
173
175
err = impl .attributesRepository .UpdateDataValByKey (dao )
174
176
if err != nil {
175
- impl .logger .Errorw ("error in update attributes" , "req " , dao , "error" , err )
177
+ impl .logger .Errorw ("error updating user attributes in database " , "dao " , dao , "error" , err )
176
178
return nil , errors .New ("error occurred while updating user attributes" )
177
179
}
178
180
179
- // Return the updated data
180
- result := & UserAttributesDto {
181
+ // Build and return response
182
+ return impl .buildResponseDTO (request , string (mergedJSON )), nil
183
+ }
184
+
185
+ // buildResponseDTO creates the response DTO
186
+ func (impl UserAttributesServiceImpl ) buildResponseDTO (request * UserAttributesDto , mergedValue string ) * UserAttributesDto {
187
+ return & UserAttributesDto {
181
188
EmailId : request .EmailId ,
182
189
Key : request .Key ,
183
- Value : string ( mergedJson ) ,
190
+ Value : mergedValue ,
184
191
UserId : request .UserId ,
185
192
}
193
+ }
194
+
195
+ // mergeUserAttributesData merges newData into existingData with special handling for resources
196
+ func (impl UserAttributesServiceImpl ) mergeUserAttributesData (existingData , newData map [string ]interface {}) bool {
197
+ hasChanges := false
198
+
199
+ for key , newValue := range newData {
200
+ if key == bean .UserPreferencesResourcesKey {
201
+ // Special handling for resources - merge nested structure
202
+ if impl .mergeResourcesData (existingData , newValue ) {
203
+ hasChanges = true
204
+ }
205
+ } else {
206
+ if impl .mergeStandardAttribute (existingData , key , newValue ) {
207
+ hasChanges = true
208
+ }
209
+ }
210
+ }
211
+
212
+ return hasChanges
213
+ }
214
+
215
+ // mergeStandardAttribute merges a standard (non-resource) attribute
216
+ func (impl UserAttributesServiceImpl ) mergeStandardAttribute (existingData map [string ]interface {}, key string , newValue interface {}) bool {
217
+ existingValue , exists := existingData [key ]
218
+ if ! exists || ! reflect .DeepEqual (existingValue , newValue ) {
219
+ existingData [key ] = newValue
220
+ return true
221
+ }
222
+ return false
223
+ }
224
+
225
+ // mergeResourcesData handles the special merging logic for the resources object
226
+ func (impl UserAttributesServiceImpl ) mergeResourcesData (existingData map [string ]interface {}, newResourcesValue interface {}) bool {
227
+ impl .ensureResourcesStructureExists (existingData )
228
+
229
+ existingResources , ok := existingData [bean .UserPreferencesResourcesKey ].(map [string ]interface {})
230
+ if ! ok {
231
+ existingData [bean .UserPreferencesResourcesKey ] = newResourcesValue
232
+ return true
233
+ }
234
+
235
+ newResources , ok := newResourcesValue .(map [string ]interface {})
236
+ if ! ok {
237
+ existingData [bean .UserPreferencesResourcesKey ] = newResourcesValue
238
+ return true
239
+ }
186
240
187
- return result , nil
241
+ return impl .mergeResourceTypes (existingResources , newResources )
242
+ }
243
+
244
+ // ensureResourcesStructureExists initializes the resources structure if it doesn't exist
245
+ func (impl UserAttributesServiceImpl ) ensureResourcesStructureExists (existingData map [string ]interface {}) {
246
+ if existingData [bean .UserPreferencesResourcesKey ] == nil {
247
+ existingData [bean .UserPreferencesResourcesKey ] = make (map [string ]interface {})
248
+ }
249
+ }
250
+
251
+ // mergeResourceTypes merges individual resource types from new resources into existing resources
252
+ func (impl UserAttributesServiceImpl ) mergeResourceTypes (existingResources , newResources map [string ]interface {}) bool {
253
+ hasChanges := false
254
+
255
+ // Merge each resource type from newResources
256
+ for resourceType , newResourceData := range newResources {
257
+ existingResourceData , exists := existingResources [resourceType ]
258
+ if ! exists || ! reflect .DeepEqual (existingResourceData , newResourceData ) {
259
+ existingResources [resourceType ] = newResourceData
260
+ hasChanges = true
261
+ }
262
+ }
263
+
264
+ return hasChanges
265
+ }
266
+
267
+ // initializeSupportedResourceTypes ensures all supported resource types are initialized
268
+ func (impl UserAttributesServiceImpl ) initializeSupportedResourceTypes (existingResources map [string ]interface {}) {
269
+ supportedResourceTypes := []string {"cluster" , "job" , "app-group" , "application/devtron-application" }
270
+
271
+ for _ , resourceType := range supportedResourceTypes {
272
+ if existingResources [resourceType ] == nil {
273
+ existingResources [resourceType ] = map [string ]interface {}{
274
+ "recently-visited" : []interface {}{},
275
+ }
276
+ }
277
+ }
188
278
}
189
279
190
280
func (impl UserAttributesServiceImpl ) GetUserAttribute (request * UserAttributesDto ) (* UserAttributesDto , error ) {
0 commit comments