lodash#fill TypeScript Examples

The following examples show how to use lodash#fill. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: strategy-form.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
StrategyForm = ({ scopeType, scopeId, commonPayload }: IProps) => {
  const memberStore = memberStoreMap[scopeType];
  const params = routeInfoStore.useStore((s) => s.params);
  const { id: strategyId, projectId = '', terminusKey = '', orgName = '' } = params;
  const [strategyForm] = Form.useForm();
  const { getRoleMap } = memberStore.effects;
  const alarmStrategyStore = alarmStrategyStoreMap[scopeType];
  const [alertTypes, alertTriggerConditions, alertTriggerConditionsContent] = alarmStrategyStore.useStore((s) => [
    s.alertTypes,
    s.alertTriggerConditions,
    s.alertTriggerConditionsContent,
  ]);
  const tableRef = React.useRef<HTMLDivElement>(null);
  const channelMethods = getNotifyChannelMethods.useData() as Obj<string>;
  const {
    getAlerts,
    createAlert,
    editAlert,
    getAlertDetail,
    getAlarmScopes,
    getAlertTypes,
    getAlertTriggerConditions,
    getAlertTriggerConditionsContent,
  } = alarmStrategyStore.effects;
  const { clearAlerts } = alarmStrategyStore.reducers;
  const { getNotifyGroups } = notifyGroupStore.effects;
  const notifyGroups = notifyGroupStore.useStore((s) => s.notifyGroups);
  const orgAddNotificationGroupAuth = usePerm((s) => s.org.cmp.alarms.addNotificationGroup.pass);

  // backend support the filterMap to match data
  const triggerConditionFilters = {
    org_name: orgName,
    project_id: projectId,
    terminus_key: terminusKey,
  };

  const addNotificationGroupAuth = scopeType === ScopeType.ORG ? orgAddNotificationGroupAuth : true; // 企业中心的添加通知组,需要验证权限,项目的暂无埋点

  const [state, updater, update] = useUpdate({
    editingRules: [] as any,
    editingFormRule: {},
    activeGroupId: undefined,
    triggerConditionValueOptions: [],
    triggerCondition: [] as COMMON_STRATEGY_NOTIFY.OperationTriggerCondition[],
    notifies: [],
    notifyLevel: null,
    allChannelMethods: notifyChannelOptionsMap,
  });

  useMount(() => {
    let payload = { scopeType, scopeId } as COMMON_NOTIFY.IGetNotifyGroupQuery;
    if (scopeType === ScopeType.MSP) {
      payload = {
        scopeType: commonPayload?.scopeType,
        scopeId: commonPayload?.scopeId,
      };
    }
    getAlerts();
    getAlarmScopes();
    getAlertTypes();
    getNotifyGroups({ ...payload, pageSize: 100 });
    getRoleMap({ scopeType, scopeId: scopeType === ScopeType.MSP ? commonPayload?.scopeId : scopeId });
    getAlertTriggerConditions(scopeType);
    getNotifyChannelMethods.fetch();
  });

  React.useEffect(() => {
    if (strategyId) {
      getAlertDetail(Number(strategyId)).then(
        ({ name, clusterNames, appIds, rules, notifies, triggerCondition }: COMMON_STRATEGY_NOTIFY.IAlertBody) => {
          updater.editingFormRule({
            id: strategyId,
            name,
            clusterName: clusterNames || [],
            appId: appIds || [],
            notifies,
          });
          strategyForm.setFieldsValue({
            name,
            silence: notifies ? `${notifies?.[0].silence.value}-${notifies?.[0].silence.unit}` : undefined,
            silencePolicy: notifies ? `${notifies?.[0].silence.policy}` : SilencePeriodType.FIXED,
          });
          updater.editingRules(
            map(rules, (rule) => ({
              key: uniqueId(),
              ...rule,
              level: rule?.level === 'WARNING' ? undefined : rule?.level,
            })),
          );
          updater.activeGroupId(notifies[0].groupId);

          updater.triggerCondition(
            (triggerCondition || []).map((x) => ({
              id: uniqueId(),
              condition: x.condition,
              operator: x.operator,
              values: x.values,
              valueOptions:
                alertTriggerConditionsContent
                  ?.find((item) => item.key === x.condition)
                  ?.options.map((y) => ({
                    key: y,
                    display: y,
                  })) ?? [],
            })),
          );

          updater.notifies(
            (notifies || []).map((x) => ({
              id: uniqueId(),
              groupId: x.groupId,
              level: x.level ? x.level?.split(',') : undefined,
              groupType: x.groupType?.split(','),
              groupTypeOptions:
                (state.allChannelMethods[x.notifyGroup.targets?.[0].type] || []).map(
                  (y: { value: string; name: string }) => ({
                    key: y.value,
                    display: y.name,
                  }),
                ) || [],
            })),
          );
        },
      );
    } else {
      updater.notifies([
        {
          id: uniqueId(),
          groupId: undefined,
          groupType: undefined,
          level: undefined,
          groupTypeOptions: [],
        },
      ]);
      updater.editingRules([
        {
          key: uniqueId(),
          name: undefined,
          window: undefined,
          functions: [],
          isRecover: true,
          level: 'Fatal',
        },
      ]);
    }
  }, [alertTriggerConditionsContent]);

  React.useEffect(() => {
    if (alertTriggerConditions?.length) {
      const query = [] as COMMON_STRATEGY_NOTIFY.IAlertTriggerConditionQueryItem[];
      forEach(alertTriggerConditions, (item) => {
        const { index, key, filters } = item;
        const filterMap = {};
        forEach(filters, (x) => {
          if (x in triggerConditionFilters) {
            filterMap[x] = triggerConditionFilters[x];
          }
        });
        query.push({ index, condition: key, filters: filterMap });
      });
      getAlertTriggerConditionsContent(query);
    }
  }, [alertTriggerConditions]);

  React.useEffect(() => {
    updater.allChannelMethods(getFinalNotifyChannelOptions(channelMethods, true));
  }, [channelMethods, updater]);

  useUnmount(() => {
    clearAlerts();
  });

  // 获取规则枚举
  const windows = React.useMemo(() => alertTypes.windows, [alertTypes.windows]);
  const silenceMap = React.useMemo(() => {
    const result = {};
    forEach(alertTypes.silence, (item) => {
      result[`${item.value}-${item.unit.key}`] = item.unit;
    });
    return result;
  }, [alertTypes.silence]);
  const operatorMap = React.useMemo(() => {
    const result = {};
    forEach(alertTypes.operators, (item) => {
      result[item.key] = item.display;
    });
    return result;
  }, [alertTypes.operators]);
  const aggregatorMap = React.useMemo(() => {
    const result = {};
    forEach(alertTypes.aggregator, (item) => {
      result[item.key] = item.display;
    });
    return result;
  }, [alertTypes.aggregator]);
  const [alertTypeRuleMap, allRuleFieldMap, allRuleMap, allRules] = React.useMemo(() => {
    const _alertTypeRuleMap = {};
    const _allRuleMap = {};
    const _allRuleFieldMap = {};
    let _allRules: any[] = [];
    forEach(alertTypes.alertTypeRules, ({ alertType, rules }) => {
      _alertTypeRuleMap[alertType.key] = rules;
      forEach(rules, (item) => {
        _allRuleMap[item.alertIndex.key] = item.alertIndex.display;
        forEach(item.functions, (subItem) => {
          _allRuleFieldMap[subItem.field.key] = subItem.field.display;
        });
      });
      _allRules = _allRules.concat(
        map(rules, ({ alertIndex, functions, ...rest }) => ({
          level: alertLevelOptions?.[0]?.key,
          alertIndex: alertIndex.key,
          functions: map(functions, ({ field, ...subRest }) => ({ field: field.key, ...subRest })),
          ...rest,
        })),
      );
    });
    return [_alertTypeRuleMap, _allRuleFieldMap, _allRuleMap, _allRules];
  }, [alertTypes.alertTypeRules]);

  const getFunctionsValueElement = (item: any, functionIndex: number, key: string) => {
    let functionsValueElement = null;
    switch (typeof item.value) {
      case 'boolean':
        functionsValueElement = (
          <Switch
            checkedChildren="true"
            unCheckedChildren="false"
            defaultChecked={item.value}
            onClick={(v: boolean) => {
              handleEditEditingRuleField(key, functionIndex, { key: 'value', value: v });
            }}
          />
        );
        break;
      case 'string':
        functionsValueElement = (
          <Input
            className="value"
            defaultValue={item.value}
            onChange={(e: any) => {
              handleEditEditingRuleField(key, functionIndex, { key: 'value', value: e.target.value });
            }}
          />
        );
        break;
      case 'number':
        functionsValueElement = (
          <InputNumber
            className="value"
            min={0}
            defaultValue={item.value}
            onChange={(v: string | number | undefined) => {
              handleEditEditingRuleField(key, functionIndex, { key: 'value', value: Number(v) });
            }}
          />
        );
        break;
      default:
        break;
    }
    return functionsValueElement;
  };

  const columns: Array<ColumnProps<COMMON_STRATEGY_NOTIFY.IFormRule>> = [
    {
      title: i18n.t('cmp:rule-name'),
      dataIndex: 'alertIndex',
      render: (value: string, { key }) => (
        <Select
          value={value}
          getPopupContainer={() => tableRef.current as HTMLElement}
          showSearch
          optionFilterProp="children"
          placeholder={i18n.t('Please Select')}
          onSelect={(alertIndex: any) => {
            const rules = cloneDeep(state.editingRules);
            const rule = find(allRules, { alertIndex });
            const index = findIndex(rules, { key });
            fill(rules, { key, ...rule }, index, index + 1);
            updater.editingRules(rules);
          }}
        >
          {map(allRules, ({ alertIndex }) => (
            <Select.Option key={alertIndex} value={alertIndex}>
              {allRuleMap[alertIndex]}
            </Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: `${i18n.t('cmp:Duration')}(min)`,
      dataIndex: 'window',
      render: (value: number, { key }: COMMON_STRATEGY_NOTIFY.IFormRule) => (
        <Select
          value={value}
          placeholder={i18n.t('Please Select')}
          getPopupContainer={() => tableRef.current as HTMLElement}
          onSelect={(window: any) => handleEditEditingRule(key, { key: 'window', value: Number(window) })}
        >
          {map(windows, (item) => (
            <Select.Option key={item} value={item}>
              {item}
            </Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Aggregation rule'),
      dataIndex: 'functions',
      render: (functions: any[], { key }: COMMON_STRATEGY_NOTIFY.IFormRule) => (
        <div className="function-list">
          {functions?.length === 0 && <Input placeholder={i18n.t('cmp:Please enter here')} />}
          {map(functions, (item, index) => (
            <div className="function-item flex-div flex items-center" key={item.field}>
              <Tooltip title={allRuleFieldMap[item.field]}>
                <span className="field-name mr-2 nowrap">{allRuleFieldMap[item.field]}</span>
              </Tooltip>
              <span className="aggregator mr-2">{aggregatorMap[item.aggregator]}</span>
              {/* <Select
                  className="aggregator mr-2"
                  defaultValue={item.aggregator}
                  disabled
                >
                  {map(aggregatorMap, (name, _key) => (<Select.Option key={_key} value={_key}>{name}</Select.Option>))}
                </Select> */}
              <Select
                className="operator mr-2"
                defaultValue={item.operator}
                getPopupContainer={() => tableRef.current as HTMLElement}
                onSelect={(value: any) => {
                  handleEditEditingRuleField(key, index, { key: 'operator', value: String(value) });
                }}
              >
                {map(operatorMap, (name, _key) => (
                  <Select.Option key={_key} value={_key}>
                    {name}
                  </Select.Option>
                ))}
              </Select>
              {getFunctionsValueElement(item, index, key)}
            </div>
          ))}
        </div>
      ),
    },
    {
      title: i18n.t('cmp:Level'),
      dataIndex: 'level',
      render: (value: string, { key }) => (
        <Select
          className="operator mr-2"
          value={value}
          getPopupContainer={() => tableRef.current as HTMLElement}
          onSelect={(level: string) => {
            handleEditEditingRule(key, { key: 'level', value: level });
          }}
        >
          {map(alertLevelOptions, (item) => (
            <Option key={item.key} value={item.key}>
              {item.display}
            </Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Trigger recovery'),
      dataIndex: 'isRecover',
      render: (isRecover: boolean, { key }: COMMON_STRATEGY_NOTIFY.IFormRule) => (
        <>
          <Switch
            checked={isRecover}
            onChange={(checked) => handleEditEditingRule(key, { key: 'isRecover', value: checked })}
          />
        </>
      ),
    },
  ];

  const actions: IActions<COMMON_STRATEGY_NOTIFY.IFormRule> = {
    render: (record) => [
      {
        title: i18n.t('Delete'),
        onClick: () => {
          handleRemoveEditingRule(record.key);
        },
      },
    ],
  };

  const fieldsList = [
    {
      label: i18n.t('cmp:alert name'),
      name: 'name',
      itemProps: {
        placeholder: i18n.t('cmp:Please enter here'),
        disabled: !isEmpty(state.editingFormRule),
        maxLength: 50,
        style: { width: 480 },
      },
    },
    {
      label: i18n.t('cmp:Filter rule'),
      name: 'triggerCondition',
      required: false,
      getComp: () => (
        <>
          <Button className="flex items-center mb-2" type="primary" ghost onClick={handleAddTriggerConditions}>
            <ErdaIcon type="plus" size="16" />
            <span>{i18n.t('cmp:Add-rule')}</span>
          </Button>
          {state.triggerCondition?.length > 0 && (
            <div className="p-2 bg-cultured w-min">
              {state.triggerCondition?.map((item) => (
                <TriggerConditionSelect
                  keyOptions={alertTriggerConditions}
                  key={item.id}
                  id={item.id}
                  current={find(state.triggerCondition, (x) => x.id === item.id)}
                  handleEditTriggerConditions={handleEditTriggerConditions}
                  handleRemoveTriggerConditions={handleRemoveTriggerConditions}
                  operatorOptions={conditionOperatorOptions}
                  valueOptionsList={alertTriggerConditionsContent}
                />
              ))}
            </div>
          )}
        </>
      ),
    },
    {
      label: i18n.t('cmp:Alert rule'),
      name: 'expressions',
      required: false,
      getComp: () => (
        <div ref={tableRef}>
          <div className="opportunity-header flex mb-2">
            <Popover
              placement="bottomLeft"
              trigger="click"
              content={
                <div className="alarm-rule-collection">
                  {map(alertTypes.alertTypeRules, (item) => (
                    <div
                      className="collection-item hover-active-bg"
                      key={item.alertType.key}
                      onClick={() => {
                        handleClickAlertType(item.alertType.key);
                      }}
                    >
                      {item.alertType.display}
                    </div>
                  ))}
                </div>
              }
            >
              <Button className="mr-2 flex items-center" ghost type="primary">
                <ErdaIcon type="page-template" className="mr-1" size="14" />
                <span>{i18n.t('cmp:Type Template')}</span>
              </Button>
            </Popover>
            <Button type="primary" className="flex items-center" ghost onClick={handleAddEditingRule}>
              <ErdaIcon type="plus" size="16" />
              <span>{i18n.t('cmp:Add-rule')}</span>
            </Button>
          </div>
          <ErdaTable
            hideHeader
            rowKey="key"
            actions={actions}
            className="opportunity-table"
            dataSource={state.editingRules}
            columns={columns}
          />
        </div>
      ),
    },
    {
      label: i18n.t('cmp:Silence period'),
      name: 'silence',
      itemProps: {
        style: { width: 480 },
      },
      type: 'select',
      options: map(silenceMap, ({ display }, value) => ({ name: `${value.split('-')[0]} ${display}`, value })),
    },
    {
      label: i18n.t('Silence period policy'),
      name: 'silencePolicy',
      initialValue: state.editingFormRule.notifies
        ? `${state.editingFormRule.notifies[0].silence.policy}`
        : SilencePeriodType.FIXED,
      type: 'radioGroup',
      options: map(SILENCE_PERIOD_POLICY_MAP, (name, value) => ({ name, value })),
    },
    {
      label: i18n.t('dop:Notify'),
      required: false,
      name: 'notifies',
      getComp: () => (
        <>
          <Button type="primary" ghost className="flex items-center mb-2" onClick={handleAddNotifyStrategy}>
            <ErdaIcon type="plus" size="16" />
            <span>{i18n.t('cmp:Add Notification Target')}</span>
          </Button>
          {state.notifies?.length > 0 && (
            <div className="p-2 bg-cultured w-min">
              {state.notifies?.map((item) => (
                <NotifyStrategySelect
                  alertLevelOptions={alertLevelOptions}
                  goToNotifyGroup={() => {
                    goTo(notifyGroupPage[scopeType], { projectId: scopeId, ...params });
                  }}
                  notifyGroups={notifyGroups}
                  notifyChannelMap={state.allChannelMethods}
                  addNotificationGroupAuth={addNotificationGroupAuth}
                  key={item.id}
                  id={item.id}
                  updater={updater.activeGroupId}
                  current={state.notifies?.find((x) => x.id === item.id)}
                  handleEditNotifyStrategy={handleEditNotifyStrategy}
                  handleRemoveNotifyStrategy={handleRemoveNotifyStrategy}
                  valueOptions={item.groupTypeOptions}
                />
              ))}
            </div>
          )}
        </>
      ),
    },
    {
      label: '',
      getComp: ({ form }: { form: FormInstance }) => {
        return (
          <div className="text-right bg-white">
            <Button className="btn-save" type="primary" onClick={() => handleSave(form)}>
              {i18n.t('Save')}
            </Button>
            <Button className="ml-3" onClick={() => window.history.back()}>
              {i18n.t('Cancel')}
            </Button>
          </div>
        );
      },
    },
  ];

  // 添加集合的规则
  const handleClickAlertType = (val: string) => {
    const formRules: COMMON_STRATEGY_NOTIFY.IFormRule[] = map(
      alertTypeRuleMap[val],
      (rule: COMMON_STRATEGY_NOTIFY.IDataExpression) => ({
        key: uniqueId(),
        alertIndex: rule.alertIndex.key,
        window: rule.window,
        functions: map(rule.functions, ({ field, ...rest }) => ({
          field: field.key,
          ...rest,
        })),
        isRecover: rule.isRecover,
        level: alertLevelOptions?.[0]?.key,
      }),
    );
    updater.editingRules([...formRules, ...state.editingRules]);
  };

  // 添加单条规则
  const handleAddEditingRule = () => {
    updater.editingRules([
      {
        key: uniqueId(),
        name: undefined,
        window: undefined,
        functions: [],
        isRecover: true,
        level: 'Fatal',
      },
      ...state.editingRules,
    ]);
  };

  // 移除表格编辑中的规则
  const handleRemoveEditingRule = (key: string) => {
    updater.editingRules(filter(state.editingRules, (item) => item.key !== key));
  };

  // 编辑单条规则
  const handleEditEditingRule = (key: string, item: { key: string; value: any }) => {
    const rules = cloneDeep(state.editingRules);
    const rule = find(rules, { key });
    const index = findIndex(rules, { key });

    fill(rules, { key, ...rule, [item.key]: item.value }, index, index + 1);
    updater.editingRules(rules);
  };

  // 编辑单条规则下的指标
  const handleEditEditingRuleField = (key: string, index: number, item: { key: string; value: any }) => {
    const rules = cloneDeep(state.editingRules);
    const { functions } = find(rules, { key }) || {};
    const functionItem = functions[index];

    fill(functions, { ...functionItem, [item.key]: item.value }, index, index + 1);
    handleEditEditingRule(key, { key: 'functions', value: functions });
  };

  const handleSave = (form: FormInstance) => {
    form
      .validateFields()
      .then((values) => {
        const { name, silence = '', silencePolicy } = values;
        const [value, unit] = silence.split('-');
        const payload: COMMON_STRATEGY_NOTIFY.IAlertBody = {
          name,
          domain: location.origin,
          rules: map(state.editingRules, ({ key, ...rest }) => rest),
          notifies: state.notifies.map((item) => ({
            silence: {
              value: Number(value),
              unit,
              policy: silencePolicy,
            },
            groupId: item?.groupId,
            groupType: item?.groupType?.join(','),
            level: item?.level?.join(','),
          })),
          triggerCondition: state.triggerCondition.map((x) => ({
            condition: x.condition,
            operator: x.operator,
            values: x.values,
          })),
        };
        if (beforeSubmit(values)) {
          if (!isEmpty(state.editingFormRule)) {
            editAlert({ body: payload, id: state.editingFormRule.id });
          } else {
            createAlert(payload);
          }
          window.history.back();
        }
      })
      .catch(({ errorFields }) => {
        form.scrollToField(errorFields[0].name);
      });
  };

  // 添加单条触发条件
  const handleAddTriggerConditions = () => {
    // const currentTriggerValues =
    //   alertTriggerConditionsContent
    //     ?.find((item) => item.key === alertTriggerConditions?.[0]?.key)
    //     ?.options.map((item) => ({ key: item, display: item })) ?? [];

    updater.triggerCondition([
      {
        id: uniqueId(),
        condition: undefined,
        operator: conditionOperatorOptions?.[0].key,
        values: undefined,
        valueOptions: [],
      },
      ...(state.triggerCondition || []),
    ]);
  };

  // 添加单条通知策略
  const handleAddNotifyStrategy = () => {
    // const activeGroup = notifyGroups[0];
    // const groupTypeOptions =
    //   ((activeGroup && notifyChannelOptionsMap[activeGroup.targets[0].type]) || []).map((x) => ({
    //     key: x.value,
    //     display: x.name,
    //   })) || [];
    // updater.groupTypeOptions(groupTypeOptions);
    updater.notifies([
      {
        id: uniqueId(),
        groupId: undefined,
        level: undefined,
        groupType: undefined,
        groupTypeOptions: [],
      },
      ...(state.notifies || []),
    ]);
  };

  // 移除表格编辑中的规则
  const handleRemoveTriggerConditions = (id: string) => {
    updater.triggerCondition(filter(state.triggerCondition, (item) => item.id !== id));
  };

  // 移除策略
  const handleRemoveNotifyStrategy = (id: string) => {
    updater.notifies(filter(state.notifies, (item) => item.id !== id));
  };

  // 编辑单条触发条件
  const handleEditNotifyStrategy = (id: string, item: { key: string; value: string }) => {
    const rules = cloneDeep(state.notifies);
    const rule = find(rules, { id });
    const index = findIndex(rules, { id });

    fill(rules, { id, ...rule, [item.key]: item.value }, index, index + 1);
    updater.notifies(rules);
  };

  // 编辑单条触发条件
  const handleEditTriggerConditions = (id: string, item: { key: string; value: any }) => {
    const rules = cloneDeep(state.triggerCondition);

    const rule = find(rules, { id });
    const index = findIndex(rules, { id });
    if (item.key === 'operator' && item.value === 'all') {
      fill(
        rules,
        { id, ...rule, values: state.triggerCondition[index].valueOptions?.map((x) => x?.key)?.join(',') },
        index,
        index + 1,
      );
    }
    fill(rules, { id, ...rule, [item.key]: item.value }, index, index + 1);
    updater.triggerCondition(rules);
  };
  const beforeSubmit = (param: any) => {
    if (state.triggerCondition?.length > 0) {
      let isIncomplete = false;
      state.triggerCondition.forEach((item) => {
        for (const key in item) {
          // the third box is input when type is 'match' or 'notMatch', valueOptions is empty array
          if (
            (!item[key] && item.operator !== 'all') ||
            (!['match', 'notMatch'].includes(item.operator) && isArray(item[key]) && item[key].length === 0)
          ) {
            isIncomplete = true;
          }
        }
      });
      if (isIncomplete) {
        warning({
          title: i18n.t('cmp:content of filter rule is missing, please complete!'),
        });
        return null;
      }
    }

    if (isEmpty(state.editingRules)) {
      warning({
        title: i18n.t('cmp:create at least one rule'),
      });
      return null;
    } else {
      let isIncomplete = false;

      state.editingRules.forEach((item: { [x: string]: string | any[] }) => {
        for (const key in item) {
          if (['functions', 'level', 'name', 'window'].includes(key)) {
            if (!item[key] || (isArray(item[key]) && item[key].length === 0)) {
              isIncomplete = true;
            }
          }
        }
      });
      if (isIncomplete) {
        warning({
          title: i18n.t('cmp:content of alarm rule is missing, please complete!'),
        });
        return null;
      }
    }

    if (isEmpty(state.notifies)) {
      warning({
        title: i18n.t('cmp:create at least one notification object'),
      });
      return null;
    } else {
      let isIncomplete = false;
      state.notifies.forEach((item) => {
        for (const key in item) {
          if (!item[key] || (isArray(item[key]) && item[key].length === 0)) {
            isIncomplete = true;
          }
        }
      });
      if (isIncomplete) {
        warning({
          title: i18n.t('content of notification object is missing, please complete!'),
        });
        return null;
      }
    }

    const isLegalFunctions = every(state.editingRules, ({ functions }) => {
      return every(functions, ({ value }) => {
        return !(isNull(value) || value === '');
      });
    });

    if (!isLegalFunctions) {
      warning({
        title: i18n.t('cmp:rule value cannot be empty'),
      });
      return null;
    }
    return param;
  };

  return (
    <div>
      <RenderForm layout="vertical" form={strategyForm} list={fieldsList} className="w-full" />
    </div>
  );
}
Example #2
Source File: index.tsx    From erda-ui with GNU Affero General Public License v3.0 4 votes vote down vote up
CustomAlarm = ({ scopeType }: { scopeType: string }) => {
  const customAlarmStore = customAlarmStoreMap[scopeType];
  const monitorMetaDataStore = monitorMetaDataStoreMap[scopeType];
  const [switchCustomAlarmLoading, getPreviewMetaDataLoading, getCustomAlarmsLoading, getCustomAlarmDetailLoading] =
    useLoading(customAlarmStore, [
      'switchCustomAlarm',
      'getPreviewMetaData',
      'getCustomAlarms',
      'getCustomAlarmDetail',
    ]);
  const [extraLoading] = useLoading(monitorMetaDataStore, ['getMetaData']);
  const [metaGroups, metaConstantMap, metaMetrics] = monitorMetaDataStore.useStore((s: any) => [
    s.metaGroups,
    s.metaConstantMap,
    s.metaMetrics,
  ]);
  const { getMetaGroups, getMetaData } = monitorMetaDataStore.effects;
  const {
    fields,
    tags,
    metric,
    filters: defaultFilters,
  } = React.useMemo(() => (metaMetrics || [])[0] || {}, [metaMetrics]);
  const { types, filters } = React.useMemo(() => metaConstantMap, [metaConstantMap]);
  const fieldsMap = React.useMemo(() => keyBy(fields, 'key'), [fields]);

  const [customAlarms, customAlarmPaging, customMetricMap, customAlarmDetail, customAlarmTargets] =
    customAlarmStore.useStore((s: any) => [
      s.customAlarms,
      s.customAlarmPaging,
      s.customMetricMap,
      s.customAlarmDetail,
      s.customAlarmTargets,
    ]);
  const {
    getCustomAlarms,
    switchCustomAlarm,
    deleteCustomAlarm,
    getCustomMetrics,
    getCustomAlarmDetail,
    getCustomAlarmTargets,
    createCustomAlarm,
    editCustomAlarm,
  } = customAlarmStore.effects;
  const { clearCustomAlarmDetail } = customAlarmStore.reducers;
  const { total, pageSize, pageNo } = customAlarmPaging;

  useMount(() => {
    getMetaGroups();
    getCustomMetrics();
    getCustomAlarmTargets();
  });

  const [
    { modalVisible, editingFilters, editingFields, selectedMetric, activedFormData, previewerKey, layout, searchValue },
    updater,
    update,
  ] = useUpdate({
    layout: [],
    modalVisible: false,
    editingFilters: [],
    editingFields: [],
    selectedMetric: undefined as any,
    activedFormData: {},
    previewerKey: undefined,
    searchValue: '',
  });

  React.useEffect(() => {
    updater.selectedMetric(metric);
  }, [metric, updater]);

  React.useEffect(() => {
    if (isEmpty(customAlarmDetail)) return;
    const { rules } = customAlarmDetail;
    const { activedMetricGroups } = rules[0];
    getMetaData({ groupId: activedMetricGroups[activedMetricGroups.length - 1] });
  }, [customAlarmDetail, getMetaData]);

  React.useEffect(() => {
    const { rules, notifies } = customAlarmDetail;
    if (isEmpty(rules) || isEmpty(notifies)) return;

    const { functions } = rules[0];
    update({
      editingFields: map(functions, (item) => {
        const aggregations = get(types[get(fieldsMap[item.field], 'type')], 'aggregations');
        return {
          ...item,
          uniKey: uniqueId(),
          aggregations,
          aggregatorType: get(find(aggregations, { aggregation: item.aggregator }), 'result_type'),
        };
      }),
    });
  }, [customAlarmDetail, fieldsMap, types, update]);

  React.useEffect(() => {
    const { name, rules, notifies, id } = customAlarmDetail;
    if (isEmpty(rules) || isEmpty(notifies)) return;

    const { window, metric: _metric, filters: _filters, group, activedMetricGroups } = rules[0];
    const { title, content, targets } = notifies[0];
    update({
      editingFilters: map(_filters, (item) => ({ ...item, uniKey: uniqueId() })),
      activedFormData: {
        id,
        name,
        rule: {
          activedMetricGroups,
          window,
          metric: _metric,
          group,
        },
        notify: {
          title,
          content,
          targets: filter(targets, (target) => target !== 'ticket'),
        },
      },
      selectedMetric: _metric,
    });
  }, [customAlarmDetail, update]);

  React.useEffect(() => {
    getCustomAlarms({ name: searchValue, pageNo: 1 });
  }, [searchValue]);

  const handlePageChange: TableProps<COMMON_CUSTOM_ALARM.CustomAlarms>['onChange'] = (paging) => {
    const { current, pageSize: size } = paging;
    getCustomAlarms({ pageNo: current, pageSize: size, name: searchValue });
  };
  const handleDeleteAlarm = (id: number) => {
    confirm({
      title: i18n.t('are you sure you want to delete this item?'),
      content: i18n.t('the item will be permanently deleted!'),
      onOk() {
        deleteCustomAlarm(id);
      },
    });
  };

  const handleEnableRule = (enable: string, record: COMMON_CUSTOM_ALARM.CustomAlarms) => {
    switchCustomAlarm({
      enable: enable === 'enable',
      id: record.id,
    }).then(() => {
      getCustomAlarms({ pageNo, pageSize, name: searchValue });
    });
  };

  const columns: Array<ColumnProps<COMMON_CUSTOM_ALARM.CustomAlarms>> = [
    {
      title: i18n.t('Name'),
      dataIndex: 'name',
      key: 'name',
    },
    {
      title: i18n.t('Status'),
      dataIndex: 'enable',
      onCell: () => ({ style: { minWidth: 100, maxWidth: 300 } }),
      render: (enable: boolean, record) => (
        <Dropdown
          trigger={['click']}
          overlay={
            <Menu
              onClick={(e) => {
                handleEnableRule(e.key, record);
              }}
            >
              <Menu.Item key="enable">
                <Badge text={i18n.t('Enable')} status="success" />
              </Menu.Item>
              <Menu.Item key="unable">
                <Badge text={i18n.t('unable')} status="default" />
              </Menu.Item>
            </Menu>
          }
        >
          <div
            onClick={(e) => e.stopPropagation()}
            className="group flex items-center justify-between px-2 cursor-pointer absolute top-0 left-0 bottom-0 right-0 hover:bg-default-04"
          >
            <Badge text={enable ? i18n.t('Enable') : i18n.t('unable')} status={enable ? 'success' : 'default'} />
            <ErdaIcon type="caret-down" size={20} fill="black-3" className="opacity-0 group-hover:opacity-100" />
          </div>
        </Dropdown>
      ),
    },
    {
      title: i18n.t('Indicator'),
      dataIndex: 'metric',
      key: 'metric',
    },
    {
      title: i18n.t('Period'),
      dataIndex: 'window',
      key: 'window',
      render: (value: number) => `${value} ${i18n.t('min')}`,
    },
    {
      title: i18n.t('Notification method'),
      dataIndex: 'notifyTargets',
      key: 'notifyTargets',
      render: (value: string[]) => `${value.join('、')}`,
    },
    {
      title: i18n.t('Creator'),
      dataIndex: 'creator',
      render: (text: string) => <UserInfo id={text} />,
    },
  ];

  const filterColumns = [
    {
      title: i18n.t('label'),
      dataIndex: 'tag',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Filter) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(tag) => {
            handleEditEditingFilters(uniKey, [
              { key: 'tag', value: tag },
              { key: 'value', value: undefined },
            ]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(tags, ({ key, name }) => (
            <Select.Option key={key} value={key}>
              {name}
            </Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('operation'),
      dataIndex: 'operator',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Filter) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(operator) => {
            handleEditEditingFilters(uniKey, [{ key: 'operator', value: operator }]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(filters, ({ operation, name }) => (
            <Select.Option key={operation}>{name}</Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Expected value'),
      dataIndex: 'value',
      render: (value: any, { uniKey }: COMMON_CUSTOM_ALARM.Filter) => {
        let expectedValEle = (
          <Input
            defaultValue={value}
            onBlur={(e: any) => {
              handleEditEditingFilters(uniKey, [{ key: 'value', value: e.target.value }]);
            }}
          />
        );
        const selectedFilter = find(editingFilters, { uniKey }) || ({} as any);
        const { values: _values } = find(tags, { key: selectedFilter.tag }) || ({} as any);
        if (!isEmpty(_values)) {
          expectedValEle = (
            <Select
              dropdownMatchSelectWidth={false}
              showSearch
              className="w-full"
              value={value}
              onSelect={(v: any) => {
                handleEditEditingFilters(uniKey, [{ key: 'value', value: v }]);
              }}
              getPopupContainer={() => document.body}
            >
              {map(_values, ({ value: v, name }) => (
                <Select.Option key={v} value={v}>
                  {name}
                </Select.Option>
              ))}
            </Select>
          );
        }
        return expectedValEle;
      },
    },
  ];

  const filteredTableActions: IActions<COMMON_CUSTOM_ALARM.Filter> = {
    render: (record) => [
      {
        title: i18n.t('Delete'),
        onClick: () => {
          handleRemoveEditingFilter(record.uniKey);
        },
      },
    ],
  };

  const getFieldColumns = (form: FormInstance) => [
    {
      title: i18n.t('Field'),
      dataIndex: 'field',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Field) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(field: any) => {
            handleEditEditingFields(uniKey, [
              { key: 'field', value: field },
              { key: 'aggregations', value: get(types[get(fieldsMap[field], 'type')], 'aggregations') },
            ]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(fields, ({ key, name }) => (
            <Select.Option key={key} value={key}>
              <Tooltip title={name}>{name}</Tooltip>
            </Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Alias'),
      dataIndex: 'alias',
      render: (value: string, { uniKey }: COMMON_CUSTOM_ALARM.Field) => (
        <Input
          defaultValue={value}
          onBlur={(e: any) => {
            handleEditEditingFields(uniKey, [{ key: 'alias', value: e.target.value }]);
          }}
        />
      ),
    },
    {
      title: i18n.t('cmp:Aggregation'),
      dataIndex: 'aggregator',
      render: (value: string, { uniKey, aggregations }: COMMON_CUSTOM_ALARM.Field) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(aggregator: any) => {
            handleEditEditingFields(uniKey, [
              { key: 'aggregator', value: aggregator },
              { key: 'aggregatorType', value: get(find(aggregations, { aggregation: aggregator }), 'result_type') },
            ]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(aggregations, ({ aggregation, name }) => (
            <Select.Option key={aggregation}>{name}</Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('Operations'),
      dataIndex: 'operator',
      render: (value: string, { uniKey, aggregatorType }: COMMON_CUSTOM_ALARM.Field) => (
        <Select
          dropdownMatchSelectWidth={false}
          defaultValue={value}
          className="w-full"
          onSelect={(operator) => {
            handleEditEditingFields(uniKey, [{ key: 'operator', value: operator }]);
          }}
          getPopupContainer={() => document.body}
        >
          {map(get(types[aggregatorType], 'operations'), ({ operation, name }) => (
            <Select.Option key={operation}>{name}</Select.Option>
          ))}
        </Select>
      ),
    },
    {
      title: i18n.t('cmp:Default threshold'),
      dataIndex: 'value',
      fixed: 'right',
      render: (value: any, { uniKey, aggregatorType }: COMMON_CUSTOM_ALARM.Field) => {
        let valueEle = null;
        switch (aggregatorType) {
          case DataType.STRING:
          case DataType.STRING_ARRAY:
            valueEle = (
              <Input
                defaultValue={value}
                onBlur={(e: any) => {
                  handleEditEditingFields(uniKey, [{ key: 'value', value: e.target.value }]);
                }}
              />
            );
            break;
          case DataType.NUMBER:
          case DataType.NUMBER_ARRAY:
            valueEle = (
              <InputNumber
                min={0}
                defaultValue={value}
                onChange={(v: any) => {
                  debounceEditEditingFields(uniKey, [{ key: 'value', value: v }]);
                }}
              />
            );
            break;
          case DataType.BOOL:
          case DataType.BOOL_ARRAY:
            valueEle = (
              <Switch
                checkedChildren="true"
                unCheckedChildren="false"
                defaultChecked={value}
                onClick={(v: boolean) => {
                  handleEditEditingFields(uniKey, [{ key: 'value', value: v }]);
                }}
              />
            );
            break;
          default:
            break;
        }
        return valueEle;
      },
    },
  ];

  const fieldsTableActions: IActions<COMMON_CUSTOM_ALARM.Field> = {
    render: (record) => [
      {
        title: i18n.t('Delete'),
        onClick: () => {
          handleRemoveEditingField(record.uniKey);
        },
      },
    ],
  };

  const handleAddEditingFilters = () => {
    updater.editingFilters([
      {
        uniKey: uniqueId(),
        // tag: customMetricMap.metricMap[selectedMetric].tags[0].tag.key,
        tag: undefined,
        // operator: keys(customMetricMap.filterOperatorMap)[0],
        operator: undefined,
      },
      ...editingFilters,
    ]);
  };

  const handleAddEditingFields = () => {
    updater.editingFields([
      {
        uniKey: uniqueId(),
        field: undefined,
        alias: undefined,
        aggregator: undefined,
        operator: undefined,
      },
      ...editingFields,
    ]);
  };

  const editRule = (rules: any, uniKey: any, items: Array<{ key: string; value: any }>) => {
    if (!uniKey) return;
    const _rules = cloneDeep(rules);
    const rule = find(_rules, { uniKey });
    const index = findIndex(_rules, { uniKey });
    const rest = reduce(items, (acc, { key, value }) => ({ ...acc, [key]: value }), {});
    const newRule = {
      uniKey,
      ...rule,
      ...rest,
    } as any;

    // // 标签、字段对应不同的 value 类型,改变标签或字段就重置 value
    // if (['tag', 'field'].includes(item.key)) {
    //   newRule = { ...newRule, value: undefined };
    // }

    fill(_rules, newRule, index, index + 1);

    return _rules;
  };

  const handleShowNotifySample = () => {
    Modal.info({
      title: i18n.t('cmp:Template Sample'),
      content: <span className="prewrap">{customMetricMap.notifySample}</span>,
    });
  };

  const handleEditEditingFilters = (uniKey: any, items: Array<{ key: string; value: any }>) => {
    updater.editingFilters(editRule(editingFilters, uniKey, items));
  };

  const handleEditEditingFields = (uniKey: any, items: Array<{ key: string; value: any }>) => {
    updater.editingFields(editRule(editingFields, uniKey, items));
  };

  const debounceEditEditingFields = debounce(handleEditEditingFields, 500);

  const handleRemoveEditingFilter = (uniKey: string | undefined) => {
    updater.editingFilters(filter(editingFilters, (item) => item.uniKey !== uniKey));
  };

  const handleRemoveEditingField = (uniKey: string | undefined) => {
    updater.editingFields(filter(editingFields, (item) => item.uniKey !== uniKey));
  };

  const extraKeys = ['uniKey', 'aggregations', 'aggregatorType'];
  const openModal = (id?: number) => {
    id && getCustomAlarmDetail(id);
    updater.modalVisible(true);
  };

  const closeModal = () => {
    updater.editingFields([]);
    updater.editingFilters([]);
    updater.activedFormData({});
    updater.modalVisible(false);
    updater.previewerKey(undefined);
    clearCustomAlarmDetail();
  };

  const someValueEmpty = (data: any[], key: string) => {
    return some(data, (item) => isEmpty(toString(item[key])));
  };

  const beforeSubmit = (data: any) => {
    return new Promise((resolve, reject) => {
      if (isEmpty(editingFields)) {
        message.warning(i18n.t('cmp:field rules are required'));
        return reject();
      }
      if (someValueEmpty(editingFilters, 'value')) {
        message.warning(i18n.t('cmp:The expected value of filter rule is required.'));
        return reject();
      }
      if (someValueEmpty(editingFields, 'alias')) {
        message.warning(i18n.t('cmp:field rule alias is required'));
        return reject();
      }
      if (uniqBy(editingFields, 'alias').length !== editingFields.length) {
        message.warning(i18n.t('cmp:field rule alias cannot be repeated'));
        return reject();
      }
      if (someValueEmpty(editingFields, 'value')) {
        message.warning(i18n.t('cmp:field rule threshold is required'));
        return reject();
      }
      resolve(data);
    });
  };

  const handleUpdateCustomAlarm = (value: { name: string; rule: any; notify: any }) => {
    const _notify = merge({}, value.notify, { targets: [...(value.notify.targets || []), 'ticket'] });
    const payload = {
      name: value.name,
      rules: [
        {
          ...value.rule,
          metric: selectedMetric,
          functions: map(editingFields, (item) => omit(item, extraKeys)),
          filters: map(editingFilters, (item) => omit(item, extraKeys)),
        },
      ],
      notifies: [_notify],
    };
    if (isEmpty(activedFormData)) {
      createCustomAlarm(payload);
    } else {
      editCustomAlarm({ id: activedFormData.id, ...payload });
    }
    closeModal();
  };

  const BasicForm = ({ form }: { form: FormInstance }) => {
    const fieldsList = [
      {
        label: i18n.t('Name'),
        name: 'name',
        itemProps: {
          maxLength: 50,
        },
      },
    ];
    return <RenderPureForm list={fieldsList} form={form} formItemLayout={formItemLayout} />;
  };

  const RuleForm = ({ form }: { form: FormInstance }) => {
    let fieldsList = [
      {
        label: `${i18n.t('Period')} (${i18n.t('common:minutes')})`,
        name: ['rule', 'window'],
        type: 'inputNumber',
        itemProps: {
          min: 0,
          precision: 0,
          className: 'w-full',
        },
      },
      {
        label: i18n.t('Indicator'),
        name: ['rule', 'activedMetricGroups'],
        type: 'cascader',
        options: metaGroups,
        itemProps: {
          className: 'w-full',
          showSearch: true,
          placeholder: i18n.t('cmp:Please select the index group'),
          onChange: (v: any) => {
            getMetaData({ groupId: v[v.length - 1] }).then(() => {
              form.setFieldsValue({
                rule: {
                  group: undefined,
                },
              });
              update({
                editingFilters: [],
                editingFields: [],
                previewerKey: undefined,
              });
            });
          },
        },
      },
    ];
    if (selectedMetric) {
      fieldsList = concat(
        fieldsList,
        {
          label: i18n.t('cmp:Filter rule'),
          name: ['rule', 'filters'],
          required: false,
          getComp: () => (
            <>
              <Button
                ghost
                className="mb-2"
                type="primary"
                disabled={someValueEmpty(editingFilters, 'value')}
                onClick={handleAddEditingFilters}
              >
                {i18n.t('cmp:Add-filter-rules')}
              </Button>
              <ErdaTable
                hideHeader
                className="filter-rule-table"
                rowKey="uniKey"
                dataSource={editingFilters}
                columns={filterColumns}
                actions={filteredTableActions}
                scroll={undefined}
              />
            </>
          ),
        },
        {
          label: i18n.t('cmp:Grouping rule'),
          name: ['rule', 'group'],
          required: true,
          type: 'select',
          options: map(tags, ({ key, name }) => ({ value: key, name })),
          itemProps: {
            mode: 'multiple',
            allowClear: true,
            className: 'w-full',
          },
        },
        {
          label: i18n.t('cmp:Field rule'),
          name: ['rule', 'functions'],
          required: false,
          getComp: () => (
            <>
              <Button
                className="mb-2"
                type="primary"
                ghost
                disabled={someValueEmpty(editingFields, 'value')}
                onClick={handleAddEditingFields}
              >
                {i18n.t('cmp:Add-field-rules')}
              </Button>
              <ErdaTable
                hideHeader
                className="field-rule-table"
                rowKey="uniKey"
                dataSource={editingFields}
                actions={fieldsTableActions}
                columns={getFieldColumns(form)}
                scroll={undefined}
              />
            </>
          ),
        },
      );
    }
    return <RenderPureForm list={fieldsList} form={form} formItemLayout={formItemLayout} />;
  };

  const NotifyForm = ({ form }: { form: FormInstance }) => {
    const Comp = () => (
      <>
        <Button
          className="mb-2"
          type="primary"
          ghost
          disabled={isEmpty(customMetricMap.notifySample)}
          onClick={handleShowNotifySample}
        >
          {i18n.t('cmp:Template Sample')}
        </Button>
        <MarkdownEditor
          value={form.getFieldValue(['notify', 'content'])}
          onBlur={(value) => {
            form.setFieldsValue({
              notify: {
                ...(form.getFieldValue('notify') || {}),
                content: value,
              },
            });
          }}
          placeholder={i18n.t('cmp:Refer to the sample to input content')}
          maxLength={512}
        />
      </>
    );

    const fieldsList = [
      {
        label: i18n.t('cmp:Notification method'),
        name: ['notify', 'targets'],
        type: 'select',
        required: false,
        options: map(
          filter(customAlarmTargets, ({ key }) => key !== 'ticket'),
          ({ key, display }) => ({ value: key, name: display }),
        ),
        itemProps: {
          mode: 'multiple',
          allowClear: true,
          className: 'w-full',
        },
      },
      {
        label: i18n.t('cmp:Message title'),
        name: ['notify', 'title'],
        itemProps: {
          maxLength: 128,
          placeholder: i18n.t('cmp:message title rules template', { interpolation: { suffix: '>', prefix: '<' } }),
        },
      },
      {
        label: i18n.t('cmp:Message content'),
        name: ['notify', 'content'],
        getComp: () => <Comp />,
      },
    ];
    return <RenderPureForm list={fieldsList} form={form} formItemLayout={formItemLayout} />;
  };

  const CustomAlarmForm = ({ form }: any) => {
    if (isEmpty(customMetricMap) || isEmpty(customAlarmTargets)) return null;
    return (
      <div className="custom-alarm-form">
        <BasicForm form={form} />
        <div className="title font-bold text-base">{i18n.t('cmp:Trigger rule')}</div>
        <RuleForm form={form} />
        <div className="title font-bold text-base">{i18n.t('cmp:Message template')}</div>
        <NotifyForm form={form} />
      </div>
    );
  };

  const customRender = (content: JSX.Element) => (
    <div className="flex justify-between items-center">
      <div className="flex-1">{content}</div>
      <IF check={!!previewerKey}>
        <div className="custom-alarm-previewer px-4">
          <Spin spinning={getPreviewMetaDataLoading}>
            <BoardGrid.Pure layout={layout} />
          </Spin>
        </div>
      </IF>
    </div>
  );

  const actions: IActions<COMMON_CUSTOM_ALARM.CustomAlarms> = {
    render: (record: COMMON_CUSTOM_ALARM.CustomAlarms) => renderMenu(record),
  };

  const renderMenu = (record: COMMON_CUSTOM_ALARM.CustomAlarms) => {
    const { editAlarmRule, deleteAlarmRule } = {
      editAlarmRule: {
        title: i18n.t('Edit'),
        onClick: () => openModal(record.id),
      },
      deleteAlarmRule: {
        title: i18n.t('Delete'),
        onClick: () => handleDeleteAlarm(record.id),
      },
    };

    return [editAlarmRule, deleteAlarmRule];
  };

  const handleChange = React.useCallback(
    debounce((value) => {
      updater.searchValue(value);
    }, 1000),
    [],
  );

  return (
    <div className="custom-alarm">
      <TopButtonGroup>
        <Button type="primary" onClick={() => openModal()}>
          {i18n.t('cmp:Add Custom Rule')}
        </Button>
      </TopButtonGroup>
      <ErdaTable
        slot={
          <Input
            size="small"
            className="w-[200px] bg-black-06 border-none ml-0.5"
            allowClear
            prefix={<ErdaIcon size="16" fill={'default-3'} type="search" />}
            onChange={(e) => {
              handleChange(e.target.value);
            }}
            placeholder={i18n.t('search by {name}', { name: i18n.t('Name').toLowerCase() })}
          />
        }
        loading={getCustomAlarmsLoading || switchCustomAlarmLoading}
        dataSource={customAlarms}
        columns={columns}
        rowKey="id"
        onChange={handlePageChange}
        pagination={{ current: pageNo, pageSize, total }}
        actions={actions}
      />
      <FormModal
        name={i18n.t('cmp:custom rule')}
        loading={getCustomAlarmDetailLoading || extraLoading}
        visible={modalVisible}
        width={1200}
        modalProps={{ bodyStyle: { height: '550px', overflow: 'auto' } }}
        PureForm={CustomAlarmForm}
        formData={activedFormData}
        customRender={customRender}
        onOk={handleUpdateCustomAlarm}
        beforeSubmit={beforeSubmit}
        onCancel={closeModal}
      />
    </div>
  );
}