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 |
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 |
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>
);
}