]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/blob
c800a948ad1439c4ffefeba31b5d19c6d5b09729
[ceph-ci.git] /
1 import { Component, Inject, OnInit, Optional, ChangeDetectorRef } from '@angular/core';
2 import {
3   FormArray,
4   Validators,
5   AbstractControl,
6   FormGroup,
7   ValidationErrors
8 } from '@angular/forms';
9 import { ActionLabelsI18n } from '~/app/shared/constants/app.constants';
10 import { CdFormBuilder } from '~/app/shared/forms/cd-form-builder';
11 import { CdFormGroup } from '~/app/shared/forms/cd-form-group';
12 import { CdForm } from '~/app/shared/forms/cd-form';
13 import { ComboBoxItem } from '~/app/shared/models/combo-box.model';
14 import { Topic } from '~/app/shared/models/topic.model';
15 import { Bucket } from '../models/rgw-bucket';
16 import { RgwBucketService } from '~/app/shared/api/rgw-bucket.service';
17 import { NotificationService } from '~/app/shared/services/notification.service';
18 import { Router } from '@angular/router';
19 import { NotificationType } from '~/app/shared/enum/notification-type.enum';
20 import { RgwTopicService } from '~/app/shared/api/rgw-topic.service';
21 import {
22   events,
23   FilterRules,
24   s3KeyFilter,
25   s3KeyFilterTexts,
26   s3MetadataFilterTexts,
27   s3TagsFilterTexts,
28   TopicConfiguration
29 } from '~/app/shared/models/notification-configuration.model';
30
31 @Component({
32   selector: 'cd-rgw-notification-form',
33   templateUrl: './rgw-notification-form.component.html',
34   styleUrls: ['./rgw-notification-form.component.scss']
35 })
36 export class RgwNotificationFormComponent extends CdForm implements OnInit {
37   notificationForm: CdFormGroup;
38   eventOption: ComboBoxItem[] = events;
39   s3KeyFilterValue: string[] = [];
40   topics: Partial<Topic[]> = [];
41   topicArn: string[] = [];
42   notification_id: string;
43   notificationList: TopicConfiguration[] = [];
44   filterTypes: string[] = ['s3Key', 's3Metadata', 's3Tags'];
45   typeLabels: Record<string, string> = {
46     s3Key: 'S3 Key configuration',
47     s3Metadata: 'S3 Metadata configuration',
48     s3Tags: 'S3 Tags configuration'
49   };
50
51   filterSettings: Record<
52     string,
53     {
54       options: string[] | null;
55       isDropdown: boolean;
56       namePlaceholder: string;
57       valuePlaceholder: string;
58       nameHelper: string;
59       valueHelper: string;
60     }
61   > = {
62     s3Key: {
63       options: null,
64       isDropdown: true,
65       namePlaceholder: s3KeyFilterTexts.namePlaceholder,
66       valuePlaceholder: s3KeyFilterTexts.valuePlaceholder,
67       nameHelper: s3KeyFilterTexts.nameHelper,
68       valueHelper: s3KeyFilterTexts.valueHelper
69     },
70     s3Metadata: {
71       options: null,
72       isDropdown: false,
73       namePlaceholder: s3MetadataFilterTexts.namePlaceholder,
74       valuePlaceholder: s3MetadataFilterTexts.valuePlaceholder,
75       nameHelper: s3MetadataFilterTexts.nameHelper,
76       valueHelper: s3MetadataFilterTexts.valueHelper
77     },
78     s3Tags: {
79       options: null,
80       isDropdown: false,
81       namePlaceholder: s3TagsFilterTexts.namePlaceholder,
82       valuePlaceholder: s3TagsFilterTexts.valuePlaceholder,
83       nameHelper: s3TagsFilterTexts.nameHelper,
84       valueHelper: s3TagsFilterTexts.valueHelper
85     }
86   };
87
88   get filterControls(): Record<string, FormArray> {
89     const controls: Record<string, FormArray> = {};
90     this.filterTypes.forEach((type) => {
91       controls[type] = this.getFormArray(type);
92     });
93     return controls;
94   }
95
96   constructor(
97     @Inject('bucket') public bucket: Bucket,
98     @Optional() @Inject('selectedNotification') public selectedNotification: TopicConfiguration,
99     @Optional() @Inject('editing') public editing = false,
100     public actionLabels: ActionLabelsI18n,
101     private rgwBucketService: RgwBucketService,
102     private rgwTopicService: RgwTopicService,
103     private notificationService: NotificationService,
104     private fb: CdFormBuilder,
105     private router: Router,
106     private cdRef: ChangeDetectorRef
107   ) {
108     super();
109   }
110
111   ngOnInit() {
112     this.editing = !!this.selectedNotification;
113     this.s3KeyFilterValue = Object.values(s3KeyFilter);
114     this.filterSettings.s3Key.options = this.s3KeyFilterValue;
115     this.createNotificationForm();
116     this.rgwBucketService.listNotification(this.bucket.bucket).subscribe({
117       next: (notificationList: TopicConfiguration[]) => {
118         this.notificationList = notificationList;
119
120         this.getTopicName().then(() => {
121           if (this.editing && this.selectedNotification) {
122             this.notification_id = this.selectedNotification.Id;
123             this.notificationForm.get('id').disable();
124             this.patchNotificationForm(this.selectedNotification);
125           }
126         });
127       }
128     });
129   }
130
131   getTopicName(): Promise<void> {
132     return new Promise((resolve, reject) => {
133       this.rgwTopicService.listTopic().subscribe({
134         next: (topics: Topic[]) => {
135           this.topics = topics;
136           this.topicArn = topics.map((topic: Topic) => topic.arn);
137           resolve();
138         },
139         error: (err) => reject(err)
140       });
141     });
142   }
143
144   patchNotificationForm(config: any): void {
145     this.cdRef.detectChanges();
146     this.notificationForm.patchValue({
147       id: config.Id,
148       topic: typeof config.Topic === 'object' ? config.Topic.arn : config.Topic,
149       event: config.Event
150     });
151
152     this.setFilterRules('s3Key', config.Filter?.S3Key?.FilterRule);
153     this.setFilterRules('s3Metadata', config.Filter?.S3Metadata?.FilterRule);
154     this.setFilterRules('s3Tags', config.Filter?.S3Tags?.FilterRule);
155   }
156
157   setFilterRules(type: string, rules: FilterRules[] = []): void {
158     let formArray = this.getFormArray(type);
159     if (!formArray) {
160       const filterGroup = this.notificationForm.get('filter') as FormGroup;
161       filterGroup.setControl(type, this.fb.array([]));
162       formArray = this.getFormArray(type);
163     }
164
165     formArray.clear();
166
167     if (!rules || rules.length === 0) {
168       formArray.push(this.createNameValueGroup());
169       return;
170     }
171
172     rules.forEach((rule) => {
173       formArray.push(this.fb.group({ Name: [rule.Name], Value: [rule.Value] }));
174     });
175   }
176
177   createNotificationForm() {
178     this.notificationForm = this.fb.group({
179       id: [null, [Validators.required, this.duplicateNotificationId.bind(this)]],
180       topic: [null, [Validators.required]],
181       event: [[], []],
182       filter: this.fb.group({
183         s3Key: this.fb.array([this.createNameValueGroup()]),
184         s3Metadata: this.fb.array([this.createNameValueGroup()]),
185         s3Tags: this.fb.array([this.createNameValueGroup()])
186       })
187     });
188   }
189
190   duplicateNotificationId(control: AbstractControl): ValidationErrors | null {
191     const currentId = control.value?.trim();
192     if (!currentId) return null;
193     if (Array.isArray(this.notificationList)) {
194       const duplicateFound = this.notificationList.some(
195         (notification: TopicConfiguration) => notification.Id === currentId
196       );
197
198       return duplicateFound ? { duplicate: true } : null;
199     }
200
201     return null;
202   }
203
204   private createNameValueGroup(): CdFormGroup {
205     return this.fb.group({
206       Name: [null],
207       Value: [null]
208     });
209   }
210
211   getFormArray(arrayName: string): FormArray {
212     const filterGroup = this.notificationForm.get('filter') as FormGroup;
213     return filterGroup?.get(arrayName) as FormArray;
214   }
215
216   getFiltersControls(type: string): FormArray {
217     return this.getFormArray(type);
218   }
219
220   addRow(arrayName: string, index: number): void {
221     const array = this.getFormArray(arrayName);
222     array.insert(index + 1, this.createNameValueGroup());
223   }
224
225   removeRow(arrayName: string, index: number): void {
226     const formArray = this.getFormArray(arrayName);
227     if (formArray && formArray.length > 1 && index >= 0 && index < formArray.length) {
228       formArray.removeAt(index);
229     } else if (formArray.length === 1) {
230       const group = formArray.at(0) as FormGroup;
231       group.reset();
232     }
233
234     this.cdRef.detectChanges();
235   }
236
237   showInvalid(field: string): boolean {
238     const control: AbstractControl | null = this.notificationForm.get(field);
239     return control?.invalid && (control.dirty || control.touched);
240   }
241
242   onSubmit() {
243     if (!this.notificationForm.valid) {
244       this.notificationForm.markAllAsTouched();
245       this.notificationForm.setErrors({ cdSubmitButton: true });
246       return;
247     }
248
249     const formValue = this.notificationForm.getRawValue();
250     const buildRules = (rules: FilterRules[]) => {
251       const seen = new Set<string>();
252       return (
253         rules
254           ?.filter((item) => item.Name && item.Value)
255           .filter((item) => {
256             if (seen.has(item.Name)) return false;
257             seen.add(item.Name);
258             return true;
259           }) || []
260       );
261     };
262
263     const successMessage = this.editing
264       ? $localize`Bucket notification updated successfully`
265       : $localize`Bucket notification created successfully`;
266
267     const notificationConfiguration = {
268       TopicConfiguration: {
269         Id: formValue.id,
270         Topic: formValue.topic,
271         Event: formValue.event,
272         Filter: {
273           S3Key: {
274             FilterRules: buildRules(formValue.filter?.s3Key)
275           },
276           S3Metadata: {
277             FilterRules: buildRules(formValue.filter?.s3Metadata)
278           },
279           S3Tags: {
280             FilterRules: buildRules(formValue.filter?.s3Tags)
281           }
282         }
283       }
284     };
285
286     this.rgwBucketService
287       .setNotification(
288         this.bucket.bucket,
289         JSON.stringify(notificationConfiguration),
290         this.bucket.owner
291       )
292       .subscribe({
293         next: () => {
294           this.notificationService.show(NotificationType.success, successMessage);
295         },
296         error: (error: any) => {
297           this.notificationService.show(NotificationType.error, error.message);
298           this.notificationForm.setErrors({ cdSubmitButton: true });
299         },
300         complete: () => {
301           this.closeModal();
302         }
303       });
304   }
305
306   goToCreateNotification() {
307     this.router.navigate(['rgw/notification/create']);
308     this.closeModal();
309   }
310 }