github.com/argoproj/argo-cd/v3@v3.2.1/ui/src/app/applications/components/application-summary/edit-notification-subscriptions.tsx (about)

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