argo-cd
220 строк · 10.1 Кб
1import {Autocomplete} from 'argo-ui';
2import * as React from 'react';
3import {DataLoader} from '../../../shared/components';
4import * as models from '../../../shared/models';
5import {services} from '../../../shared/services';
6
7import {ApplicationSummaryProps} from './application-summary';
8
9import './edit-notification-subscriptions.scss';
10
11export const NOTIFICATION_SUBSCRIPTION_ANNOTATION_PREFIX = 'notifications.argoproj.io/subscribe';
12
13export const NOTIFICATION_SUBSCRIPTION_ANNOTATION_REGEX = new RegExp(`^notifications\.argoproj\.io\/subscribe\.[a-zA-Z-]{1,100}\.[a-zA-Z-]{1,100}$`);
14
15export type TNotificationSubscription = {
16trigger: string;
17// notification service name
18service: string;
19// a semicolon separated list of recipients
20value: string;
21};
22
23export const notificationSubscriptionsParser = {
24annotationsToSubscriptions: (annotations: models.Application['metadata']['annotations']): TNotificationSubscription[] => {
25const subscriptions: TNotificationSubscription[] = [];
26
27for (const [key, value] of Object.entries(annotations || {})) {
28if (NOTIFICATION_SUBSCRIPTION_ANNOTATION_REGEX.test(key)) {
29try {
30const [trigger, service] = key.slice(NOTIFICATION_SUBSCRIPTION_ANNOTATION_PREFIX.length + 1 /* for dot "." */).split('.');
31
32subscriptions.push({trigger, service, value});
33} catch (e) {
34// console.error(`annotationsToSubscriptions parsing issue for ${key}`);
35throw new Error(e);
36}
37}
38}
39
40return subscriptions;
41},
42subscriptionsToAnnotations: (subscriptions: TNotificationSubscription[]): models.Application['metadata']['annotations'] => {
43const annotations: models.Application['metadata']['annotations'] = {};
44
45for (const subscription of subscriptions || []) {
46annotations[notificationSubscriptionsParser.subscriptionToAnnotationKey(subscription)] = subscription.value;
47}
48
49return annotations;
50},
51subscriptionToAnnotationKey: (subscription: TNotificationSubscription): string =>
52`${NOTIFICATION_SUBSCRIPTION_ANNOTATION_PREFIX}.${subscription.trigger}.${subscription.service}`
53};
54
55/**
56* split the notification subscription related annotation to have it in seperate edit field
57* this hook will emit notification subscription state, controller & merge utility to core annotations helpful when final submit
58*/
59export const useEditNotificationSubscriptions = (annotations: models.Application['metadata']['annotations']) => {
60const [subscriptions, setSubscriptions] = React.useState(notificationSubscriptionsParser.annotationsToSubscriptions(annotations));
61
62const onAddNewSubscription = () => {
63const lastSubscription = subscriptions[subscriptions.length - 1];
64
65if (subscriptions.length === 0 || lastSubscription.trigger || lastSubscription.service || lastSubscription.value) {
66setSubscriptions([
67...subscriptions,
68{
69trigger: '',
70service: '',
71value: ''
72}
73]);
74}
75};
76
77const onEditSubscription = (idx: number, subscription: TNotificationSubscription) => {
78const existingSubscription = subscriptions.findIndex((sub, toFindIdx) => toFindIdx !== idx && sub.service === subscription.service && sub.trigger === subscription.trigger);
79let newSubscriptions = [...subscriptions];
80
81if (existingSubscription !== -1) {
82// remove existing subscription
83newSubscriptions = newSubscriptions.filter((_, newSubscriptionIdx) => newSubscriptionIdx !== existingSubscription);
84// decrement index because one value is removed
85idx--;
86}
87
88if (idx === -1) {
89newSubscriptions = [subscription];
90} else {
91newSubscriptions = newSubscriptions.map((oldSubscription, oldSubscriptionIdx) => (oldSubscriptionIdx === idx ? subscription : oldSubscription));
92}
93
94setSubscriptions(newSubscriptions);
95};
96
97const onRemoveSubscription = (idx: number) => idx >= 0 && setSubscriptions(subscriptions.filter((_, i) => i !== idx));
98
99const withNotificationSubscriptions = (updateApp: ApplicationSummaryProps['updateApp']) => (...args: Parameters<ApplicationSummaryProps['updateApp']>) => {
100const app = args[0];
101
102const notificationSubscriptionsRaw = notificationSubscriptionsParser.subscriptionsToAnnotations(subscriptions);
103
104if (Object.keys(notificationSubscriptionsRaw)?.length) {
105app.metadata.annotations = {
106...notificationSubscriptionsRaw,
107...(app.metadata.annotations || {})
108};
109}
110
111return updateApp(app, args[1]);
112};
113
114const onResetNotificationSubscriptions = () => setSubscriptions(notificationSubscriptionsParser.annotationsToSubscriptions(annotations));
115
116return {
117/**
118* abstraction of notification subscription annotations in edit view
119*/
120subscriptions,
121onAddNewSubscription,
122onEditSubscription,
123onRemoveSubscription,
124/**
125* merge abstracted 'subscriptions' into core 'metadata.annotations' in form submit
126*/
127withNotificationSubscriptions,
128onResetNotificationSubscriptions
129};
130};
131
132export interface EditNotificationSubscriptionsProps extends ReturnType<typeof useEditNotificationSubscriptions> {}
133
134export const EditNotificationSubscriptions = ({subscriptions, onAddNewSubscription, onEditSubscription, onRemoveSubscription}: EditNotificationSubscriptionsProps) => {
135return (
136<div className='edit-notification-subscriptions argo-field'>
137{subscriptions.map((subscription, idx) => (
138<div className='edit-notification-subscriptions__subscription' key={idx}>
139<input className='argo-field edit-notification-subscriptions__input-prefix' disabled={true} value={NOTIFICATION_SUBSCRIPTION_ANNOTATION_PREFIX} />
140<b> . </b>
141<DataLoader load={() => services.notification.listTriggers().then(triggers => triggers.map(trigger => trigger.name))}>
142{triggersList => (
143<Autocomplete
144wrapperProps={{
145className: 'argo-field edit-notification-subscriptions__autocomplete-wrapper'
146}}
147inputProps={{
148className: 'argo-field',
149placeholder: 'on-sync-running',
150title: 'Trigger'
151}}
152value={subscription.trigger}
153onChange={e => {
154onEditSubscription(idx, {
155...subscription,
156trigger: e.target.value
157});
158}}
159items={triggersList}
160onSelect={trigger => onEditSubscription(idx, {...subscription, trigger})}
161filterSuggestions={true}
162qeid='application-edit-notification-subscription-trigger'
163/>
164)}
165</DataLoader>
166<b> . </b>
167<DataLoader load={() => services.notification.listServices().then(_services => _services.map(service => service.name))}>
168{serviceList => (
169<Autocomplete
170wrapperProps={{
171className: 'argo-field edit-notification-subscriptions__autocomplete-wrapper'
172}}
173inputProps={{
174className: 'argo-field',
175placeholder: 'slack',
176title: 'Service'
177}}
178value={subscription.service}
179onChange={e => {
180onEditSubscription(idx, {
181...subscription,
182service: e.target.value
183});
184}}
185items={serviceList}
186onSelect={service => onEditSubscription(idx, {...subscription, service})}
187filterSuggestions={true}
188qeid='application-edit-notification-subscription-service'
189/>
190)}
191</DataLoader>
192 =
193<input
194autoComplete='fake'
195className='argo-field'
196placeholder='my-channel1; my-channel2'
197title='Value'
198value={subscription.value}
199onChange={e => {
200onEditSubscription(idx, {
201...subscription,
202value: e.target.value
203});
204}}
205qe-id='application-edit-notification-subscription-value'
206/>
207<button className='button-close'>
208<i className='fa fa-times' style={{cursor: 'pointer'}} onClick={() => onRemoveSubscription(idx)} />
209</button>
210</div>
211))}
212{subscriptions.length === 0 && <label>No items</label>}
213<div>
214<button className='argo-button argo-button--base argo-button--short' onClick={() => onAddNewSubscription()}>
215<i className='fa fa-plus' style={{cursor: 'pointer'}} />
216</button>
217</div>
218</div>
219);
220};
221