mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-27 00:31:42 +00:00
fix(ui): edit alert and misc feedback for alerts page (#9388)
* fix(ui): edit alert and misc feedback for alerts page * disable destination for system generated alerts * minor ui comments resolved * fix edit mode in alerts workflow * address comments
This commit is contained in:
parent
2398161653
commit
bac86cba69
@ -1,3 +1,3 @@
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.00002 8.65912C5.8954 8.65912 5.79067 8.61915 5.71079 8.53927L1.61989 4.44834C1.46004 4.28849 1.46004 4.02964 1.61989 3.86989C1.77974 3.71014 2.03859 3.71004 2.19834 3.86989L6.00002 7.6716L9.80167 3.86989C9.96157 3.71004 10.2204 3.71004 10.3802 3.86989C10.5399 4.02974 10.54 4.28859 10.3802 4.44834L6.28925 8.53927C6.20937 8.61915 6.10464 8.65912 6.00002 8.65912Z" fill="#37352F" stroke="#37352F" stroke-width="0.6"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 510 B |
@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M11.5455 7.99998C11.5455 8.13947 11.4922 8.27911 11.3857 8.38561L5.93112 13.8401C5.71799 14.0533 5.37285 14.0533 5.15985 13.8401C4.94685 13.627 4.94672 13.2819 5.15985 13.0689L10.2288 7.99998L5.15985 2.93107C4.94672 2.71794 4.94672 2.3728 5.15985 2.1598C5.37299 1.9468 5.71812 1.94667 5.93112 2.1598L11.3857 7.61434C11.4922 7.72084 11.5455 7.86048 11.5455 7.99998Z" fill="#37352F" stroke="#37352F" stroke-width="0.6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 531 B |
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { PagingResponse } from 'Models';
|
||||
import axiosClient from '.';
|
||||
import { AlertAction } from '../generated/alerts/alertAction';
|
||||
@ -36,6 +37,21 @@ export const createAlertAction = async (alertAction: CreateAlertAction) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const patchAlertAction = async (id: string, operation: Operation) => {
|
||||
const response = await axiosClient.patch<
|
||||
Operation,
|
||||
AxiosResponse<AlertAction>
|
||||
>(`${BASE_URL}/${id}`, operation);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateAlertAction = async (alertAction: AlertAction) => {
|
||||
const response = await axiosClient.put<AlertAction>(BASE_URL, alertAction);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteAlertAction = async (id: string) => {
|
||||
const response = await axiosClient.delete(`${BASE_URL}/${id}`);
|
||||
|
||||
|
||||
@ -13,7 +13,11 @@
|
||||
|
||||
import { PagingResponse } from 'Models';
|
||||
import axiosClient from '.';
|
||||
import { AlertActionType, Status } from '../generated/alerts/alertAction';
|
||||
import {
|
||||
AlertAction,
|
||||
AlertActionType,
|
||||
Status,
|
||||
} from '../generated/alerts/alertAction';
|
||||
import { Alerts, TriggerConfig } from '../generated/alerts/alerts';
|
||||
import { EntitySpelFilters } from '../generated/alerts/entitySpelFilters';
|
||||
import { Function } from '../generated/type/function';
|
||||
@ -60,6 +64,12 @@ export const createAlert = async (alert: Alerts) => {
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const updateAlert = async (alert: Alerts) => {
|
||||
const response = await axiosClient.put<Alerts>(BASE_URL, alert);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
export const deleteAlert = async (id: string) => {
|
||||
const response = await axiosClient.delete(`${BASE_URL}/${id}`);
|
||||
|
||||
@ -94,7 +104,9 @@ export const getDefaultTriggerConfigs = async (after?: string) => {
|
||||
};
|
||||
|
||||
export const getAlertActionForAlerts = async (id: string) => {
|
||||
const response = await axiosClient.get(`${BASE_URL}/allAlertAction/${id}`);
|
||||
const response = await axiosClient.get<AlertAction[]>(
|
||||
`${BASE_URL}/allAlertAction/${id}`
|
||||
);
|
||||
|
||||
return response.data;
|
||||
};
|
||||
|
||||
@ -216,7 +216,7 @@ const SearchDropdown: FC<SearchDropdownProps> = ({
|
||||
</span>
|
||||
)}
|
||||
</Space>
|
||||
<DropDown className="flex self-center" />
|
||||
<DropDown className="flex self-center" height={12} width={12} />
|
||||
</Space>
|
||||
</Button>
|
||||
</Dropdown>
|
||||
|
||||
@ -17,10 +17,16 @@
|
||||
box-shadow: 0 0 10px 2px rgba(0, 0, 0, 0.3);
|
||||
height: 60px;
|
||||
position: fixed;
|
||||
right: 40px;
|
||||
right: 20px;
|
||||
bottom: 20px;
|
||||
width: 60px;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
scale: 0.75;
|
||||
transition: scale 0.5s ease-in-out;
|
||||
}
|
||||
|
||||
.slack-chat .bubble:hover {
|
||||
scale: 1;
|
||||
}
|
||||
|
||||
@ -408,7 +408,6 @@
|
||||
"edit-team-type": "Edit Team Type",
|
||||
"configure-webhook-type": "Configure {{webhookType}} Webhooks",
|
||||
"ms-teams": "MS Teams",
|
||||
"ms-team": "MS Team",
|
||||
"bot": "Bot",
|
||||
"bot-plural": "Bots",
|
||||
"policy-name": "Policy name",
|
||||
@ -584,7 +583,15 @@
|
||||
"completed-description": "Completed Description",
|
||||
"assigned-entity": "Assigned {{entity}}",
|
||||
"total-assets-view": "Total Assets View",
|
||||
"total-active-user": "Total Active User"
|
||||
"total-active-user": "Total Active User",
|
||||
"alert-actions": "Alert Actions",
|
||||
"test-results": "Test Results",
|
||||
"send-to": "Send to",
|
||||
"receiver-plural": "Receivers",
|
||||
"admin-plural": "Admins",
|
||||
"owner-plural": "Owners",
|
||||
"follower-plural": "Followers",
|
||||
"email-plural": "Emails"
|
||||
},
|
||||
"message": {
|
||||
"service-email-required": "Service account Email is required",
|
||||
@ -709,7 +716,8 @@
|
||||
"alerts-description": "Stay current with timely alerts using webhooks.",
|
||||
"alerts-destination-description": "Send notifications to Slack, MS Teams, Email, or and use Webhooks.",
|
||||
"alerts-trigger-description": "Trigger for all data assets or a specific entity.",
|
||||
"alerts-filter-description": "Specify the change events to narrow the scope of your alerts."
|
||||
"alerts-filter-description": "Specify the change events to narrow the scope of your alerts.",
|
||||
"length-validator-error": "At least {{length}} {{field}} required"
|
||||
},
|
||||
"server": {
|
||||
"you-have-not-action-anything-yet": "You have not {{action}} anything yet.",
|
||||
@ -729,7 +737,8 @@
|
||||
"ingestion-workflow-operation-error": "Error while {{operation}} ingestion workflow {{displayName}}",
|
||||
"team-moved-error": "Error while moving team",
|
||||
"entity-details-fetch-error": "Error while fetching details for {{entityType}} {{entityName}}",
|
||||
"create-entity-success": "{{entity}} created successfully."
|
||||
"create-entity-success": "{{entity}} created successfully.",
|
||||
"update-entity-success": "{{entity}} updated successfully."
|
||||
},
|
||||
"url": {}
|
||||
}
|
||||
|
||||
@ -404,7 +404,6 @@
|
||||
"edit-team-type": "Mettre à jour le type d'équipe",
|
||||
"configure-webhook-type": "Configurer {{webhookType}} Webhooks",
|
||||
"ms-teams": "MS Teams",
|
||||
"ms-team": "MS Team",
|
||||
"policy-name": "Nom de Police",
|
||||
"add-new-policy": "Ajouter une nouvelle police",
|
||||
"policies": "Polices",
|
||||
|
||||
@ -14,30 +14,35 @@ import { PlusOutlined } from '@ant-design/icons';
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Col,
|
||||
Collapse,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
Row,
|
||||
Select,
|
||||
Space,
|
||||
Switch,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { useForm } from 'antd/lib/form/Form';
|
||||
import { DefaultOptionType } from 'antd/lib/select';
|
||||
import { AxiosError } from 'axios';
|
||||
import { get, intersection, isEmpty, map, startCase } from 'lodash';
|
||||
import { get, intersection, isEmpty, map, pick, startCase, trim } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { createAlertAction } from '../../axiosAPIs/alertActionAPI';
|
||||
import {
|
||||
createAlertAction,
|
||||
updateAlertAction,
|
||||
} from '../../axiosAPIs/alertActionAPI';
|
||||
import {
|
||||
createAlert,
|
||||
getAlertActionForAlerts,
|
||||
getAlertsFromId,
|
||||
getDefaultTriggerConfigs,
|
||||
getEntityFilterFunctions,
|
||||
getFilterFunctions,
|
||||
updateAlert,
|
||||
} from '../../axiosAPIs/alertsAPI';
|
||||
import {
|
||||
getSearchedUsersAndTeams,
|
||||
@ -51,19 +56,26 @@ import {
|
||||
import { PROMISE_STATE } from '../../enums/common.enum';
|
||||
import { AlertAction } from '../../generated/alerts/alertAction';
|
||||
import {
|
||||
AlertFilterRule,
|
||||
Alerts,
|
||||
AlertTriggerType,
|
||||
Effect,
|
||||
EntityReference,
|
||||
ProviderType,
|
||||
TriggerConfig,
|
||||
} from '../../generated/alerts/alerts';
|
||||
import { AlertActionType } from '../../generated/alerts/api/createAlertAction';
|
||||
import {
|
||||
AlertActionType,
|
||||
CreateAlertAction,
|
||||
} from '../../generated/alerts/api/createAlertAction';
|
||||
import { EntitySpelFilters } from '../../generated/alerts/entitySpelFilters';
|
||||
import { Function } from '../../generated/type/function';
|
||||
import {
|
||||
getAlertActionTypeDisplayName,
|
||||
getAlertsActionTypeIcon,
|
||||
getDisplayNameForTriggerType,
|
||||
getFunctionDisplayName,
|
||||
listLengthValidator,
|
||||
StyledCard,
|
||||
} from '../../utils/Alerts/AlertsUtil';
|
||||
import { getSettingPath } from '../../utils/RouterUtils';
|
||||
@ -76,6 +88,8 @@ const AddAlertPage = () => {
|
||||
const [form] = useForm<Alerts>();
|
||||
const history = useHistory();
|
||||
const { fqn } = useParams<{ fqn: string }>();
|
||||
// To block certain action based on provider of the Alert e.g. System / User
|
||||
const [provider, setProvider] = useState<ProviderType>(ProviderType.User);
|
||||
|
||||
const [filterFunctions, setFilterFunctions] = useState<Function[]>();
|
||||
const [defaultTriggers, setDefaultTriggers] = useState<Array<TriggerConfig>>(
|
||||
@ -88,23 +102,34 @@ const AddAlertPage = () => {
|
||||
const fetchAlert = async () => {
|
||||
try {
|
||||
setLoadingCount((count) => count + 1);
|
||||
|
||||
const response: Alerts = await getAlertsFromId(fqn);
|
||||
const alertActions = await getAlertActionForAlerts(response.id);
|
||||
|
||||
const requestFilteringRules =
|
||||
response.filteringRules?.map((curr) => ({
|
||||
...curr,
|
||||
condition: curr.condition
|
||||
.replace(new RegExp(`${curr.name}\\('`), '')
|
||||
.replace(new RegExp(`'\\)`), ''),
|
||||
})) ?? [];
|
||||
response.filteringRules?.map(
|
||||
(curr) =>
|
||||
({
|
||||
...curr,
|
||||
condition: curr.condition
|
||||
.replace(new RegExp(`${curr.name}\\('`), '')
|
||||
.replaceAll("'", '')
|
||||
.replace(new RegExp(`\\)`), '')
|
||||
.split(',')
|
||||
.map(trim),
|
||||
} as unknown as AlertFilterRule)
|
||||
) ?? [];
|
||||
|
||||
setProvider(response.provider ?? ProviderType.User);
|
||||
|
||||
form.setFieldsValue({
|
||||
...response,
|
||||
filteringRules: requestFilteringRules,
|
||||
alertActions: alertActions as unknown as EntityReference[],
|
||||
});
|
||||
} catch {
|
||||
showErrorToast(
|
||||
t('message.entity-fetch-error', { entity: t('label.alert') }),
|
||||
t('server.entity-fetch-error', { entity: t('label.alert') }),
|
||||
fqn
|
||||
);
|
||||
} finally {
|
||||
@ -149,8 +174,60 @@ const AddAlertPage = () => {
|
||||
fetchDefaultTriggerConfig();
|
||||
}, []);
|
||||
|
||||
const isEditMode = useMemo(() => !isEmpty(fqn), [fqn]);
|
||||
|
||||
const updateCreateAlertActions = async (alertActions: AlertAction[]) => {
|
||||
const api = isEditMode ? updateAlertAction : createAlertAction;
|
||||
if (isEditMode) {
|
||||
if (!form.isFieldTouched(['alertActions'])) {
|
||||
// If destination is not changed return given alertAction as it is
|
||||
return Promise.resolve(
|
||||
alertActions.map((action) => ({
|
||||
id: action.id ?? '',
|
||||
type: 'alertAction',
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Else Create AlertActions and return new IDs
|
||||
const promises =
|
||||
alertActions?.map((action) =>
|
||||
api(
|
||||
pick(action, [
|
||||
'alertActionConfig',
|
||||
'alertActionType',
|
||||
'name',
|
||||
'displayName',
|
||||
'timeout',
|
||||
'batchSize',
|
||||
]) as CreateAlertAction
|
||||
)
|
||||
) ?? [];
|
||||
|
||||
const responses = await Promise.allSettled(promises);
|
||||
|
||||
const requestAlertActions: EntityReference[] = responses.map((res) => {
|
||||
if (res.status === PROMISE_STATE.REJECTED) {
|
||||
throw res.reason;
|
||||
}
|
||||
|
||||
return {
|
||||
id: res.status === PROMISE_STATE.FULFILLED ? res.value.id ?? '' : '',
|
||||
type: 'alertAction',
|
||||
};
|
||||
});
|
||||
|
||||
return Promise.resolve(requestAlertActions);
|
||||
};
|
||||
|
||||
const handleSave = async (data: Alerts) => {
|
||||
const { filteringRules, alertActions } = data;
|
||||
if (!alertActions?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const api = isEditMode ? updateAlert : createAlert;
|
||||
|
||||
const requestFilteringRules = filteringRules?.map((curr) => ({
|
||||
...curr,
|
||||
@ -160,40 +237,31 @@ const AddAlertPage = () => {
|
||||
)?.join(', ')})`,
|
||||
}));
|
||||
|
||||
const modifiedAlertActions = alertActions?.map(
|
||||
(action) =>
|
||||
({
|
||||
...action,
|
||||
name: action.name ?? action.displayName,
|
||||
displayName: action.displayName,
|
||||
} as unknown as AlertAction)
|
||||
);
|
||||
|
||||
try {
|
||||
const actions: AlertAction[] =
|
||||
alertActions?.map(
|
||||
(action) =>
|
||||
({
|
||||
...action,
|
||||
enabled: true,
|
||||
} as unknown as AlertAction)
|
||||
) ?? [];
|
||||
|
||||
const promises = actions.map((action) => createAlertAction(action));
|
||||
|
||||
const responses = await Promise.allSettled(promises);
|
||||
|
||||
const requestAlertActions: EntityReference[] = responses.map((res) => {
|
||||
if (res.status === PROMISE_STATE.REJECTED) {
|
||||
throw res.reason;
|
||||
}
|
||||
|
||||
return {
|
||||
id: res.status === PROMISE_STATE.FULFILLED ? res.value.id ?? '' : '',
|
||||
type: 'alertAction',
|
||||
};
|
||||
});
|
||||
const requestAlertActions = await updateCreateAlertActions(
|
||||
modifiedAlertActions
|
||||
);
|
||||
|
||||
try {
|
||||
await createAlert({
|
||||
await api({
|
||||
...data,
|
||||
filteringRules: requestFilteringRules,
|
||||
alertActions: requestAlertActions,
|
||||
});
|
||||
|
||||
showErrorToast(
|
||||
t('server.create-entity-success', { entity: t('alert-plural') })
|
||||
t(`server.${isEditMode ? 'update' : 'create'}-entity-success`, {
|
||||
entity: t('label.alert-plural'),
|
||||
})
|
||||
);
|
||||
history.push(
|
||||
getSettingPath(
|
||||
@ -203,15 +271,24 @@ const AddAlertPage = () => {
|
||||
);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
t('server.entity-creation-error', {
|
||||
entity: t('label.alert-plural'),
|
||||
}),
|
||||
(error as AxiosError).message
|
||||
t(
|
||||
`server.${
|
||||
isEditMode ? 'entity-updating-error' : 'entity-creation-error'
|
||||
}`,
|
||||
{
|
||||
entity: t('label.alert-plural'),
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
t('server.entity-creation-error', { entity: t('label.alert-plural') })
|
||||
t(
|
||||
`server.${
|
||||
isEditMode ? 'entity-updating-error' : 'entity-creation-error'
|
||||
}`,
|
||||
{ entity: t('label.alert-plural') }
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
@ -222,7 +299,7 @@ const AddAlertPage = () => {
|
||||
|
||||
return response.hits.hits.map((d) => ({
|
||||
label: d._source.displayName ?? d._source.name,
|
||||
value: d._source.id,
|
||||
value: d._source.fullyQualifiedName,
|
||||
}));
|
||||
} catch (error) {
|
||||
return [];
|
||||
@ -313,37 +390,171 @@ const AddAlertPage = () => {
|
||||
|
||||
// Watchers
|
||||
const filters = Form.useWatch(['filteringRules'], form);
|
||||
const alertActions = Form.useWatch(['alertActions'], form);
|
||||
const entitySelected = Form.useWatch(['triggerConfig', 'entities'], form);
|
||||
const trigger = Form.useWatch(['triggerConfig', 'type'], form);
|
||||
const alertActions = Form.useWatch(['alertActions'], form);
|
||||
|
||||
// Run time values needed for conditional rendering
|
||||
const functions = useMemo(() => {
|
||||
if (entityFunctions) {
|
||||
const exitingFunctions = filters?.map((f) => f.name) ?? [];
|
||||
let supportedFunctions: string[][] = [];
|
||||
|
||||
if (!trigger || trigger === AlertTriggerType.AllDataAssets) {
|
||||
return entityFunctions['all'].supportedFunctions.sort();
|
||||
supportedFunctions = [entityFunctions['all'].supportedFunctions];
|
||||
} else {
|
||||
supportedFunctions =
|
||||
entitySelected?.map(
|
||||
(entity) =>
|
||||
entityFunctions[entity as unknown as string].supportedFunctions
|
||||
) ?? [];
|
||||
}
|
||||
|
||||
const arrFunctions = entitySelected?.map(
|
||||
(entity) =>
|
||||
entityFunctions[entity as unknown as string].supportedFunctions
|
||||
);
|
||||
const functions = intersection(...supportedFunctions)
|
||||
.sort()
|
||||
.map((func) => ({
|
||||
label: getFunctionDisplayName(func),
|
||||
value: func,
|
||||
disabled: exitingFunctions.includes(func),
|
||||
}));
|
||||
|
||||
const functions = arrFunctions
|
||||
? intersection(...arrFunctions).sort()
|
||||
: [];
|
||||
|
||||
return functions as string[];
|
||||
return functions as DefaultOptionType[];
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [entitySelected, entityFunctions]);
|
||||
}, [entitySelected, entityFunctions, filters]);
|
||||
|
||||
const selectedTrigger = useMemo(
|
||||
() => defaultTriggers.find(({ type }) => trigger === type),
|
||||
[defaultTriggers, trigger]
|
||||
);
|
||||
|
||||
const handleChange = (changedValues: Partial<Alerts>) => {
|
||||
const { triggerConfig } = changedValues;
|
||||
if (triggerConfig?.entities || triggerConfig?.type) {
|
||||
form.resetFields(['filteringRules', 'condition']);
|
||||
}
|
||||
};
|
||||
|
||||
const getDestinationConfigFields = useCallback(
|
||||
(name: number) => {
|
||||
const alertActionType = get(alertActions, [name, 'alertActionType']);
|
||||
if (alertActions && alertActions[name]) {
|
||||
switch (alertActionType) {
|
||||
case AlertActionType.Email:
|
||||
return (
|
||||
<>
|
||||
<Form.Item
|
||||
required
|
||||
label={t('label.name')}
|
||||
labelCol={{ span: 24 }}
|
||||
name={[name, 'displayName']}>
|
||||
<Input
|
||||
disabled={provider === ProviderType.System}
|
||||
placeholder={t('label.name')}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('label.receiver-plural')}
|
||||
labelCol={{ span: 24 }}
|
||||
name={[name, 'alertActionConfig', 'receivers']}>
|
||||
<Select
|
||||
showSearch
|
||||
mode="tags"
|
||||
open={false}
|
||||
placeholder={t('label.enter-entity', {
|
||||
entity: t('label.email-plural'),
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Space align="baseline">
|
||||
<label>{t('label.send-to')}:</label>
|
||||
<Form.Item
|
||||
name={[name, 'alertActionConfig', 'sendToAdmins']}
|
||||
valuePropName="checked">
|
||||
<Checkbox>{t('label.admin-plural')}</Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={[name, 'alertActionConfig', 'sendToOwners']}
|
||||
valuePropName="checked">
|
||||
<Checkbox>{t('label.owner-plural')}</Checkbox>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={[name, 'alertActionConfig', 'sendToFollowers']}
|
||||
valuePropName="checked">
|
||||
<Checkbox>{t('label.follower-plural')}</Checkbox>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
case AlertActionType.GenericWebhook:
|
||||
case AlertActionType.SlackWebhook:
|
||||
case AlertActionType.MSTeamsWebhook:
|
||||
return (
|
||||
<>
|
||||
<Form.Item required name={[name, 'displayName']}>
|
||||
<Input
|
||||
disabled={provider === ProviderType.System}
|
||||
placeholder={t('label.name')}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
required
|
||||
name={[name, 'alertActionConfig', 'endpoint']}>
|
||||
<Input
|
||||
disabled={provider === ProviderType.System}
|
||||
placeholder={
|
||||
t('label.endpoint-url') +
|
||||
': ' +
|
||||
'http(s)://www.example.com'
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Collapse ghost>
|
||||
<Collapse.Panel
|
||||
header={`${t('label.advanced-config')}:`}
|
||||
key="1">
|
||||
<Space>
|
||||
<Form.Item
|
||||
initialValue={10}
|
||||
label="Batch Size"
|
||||
labelCol={{ span: 24 }}
|
||||
name={[name, 'batchSize']}>
|
||||
<Input disabled={provider === ProviderType.System} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
colon
|
||||
initialValue={10}
|
||||
label={`${t(
|
||||
'label.connection-timeout-plural-optional'
|
||||
)}`}
|
||||
labelCol={{ span: 24 }}
|
||||
name={[name, 'timeout']}>
|
||||
<Input disabled={provider === ProviderType.System} />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<Form.Item
|
||||
label={t('label.secret-key')}
|
||||
labelCol={{ span: 24 }}
|
||||
name={[name, 'alertActionConfig', 'secretKey']}>
|
||||
<Input
|
||||
disabled={provider === ProviderType.System}
|
||||
placeholder={t('label.secret-key')}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return <></>;
|
||||
},
|
||||
[alertActions]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={[16, 16]}>
|
||||
@ -359,14 +570,15 @@ const AddAlertPage = () => {
|
||||
<Form<Alerts>
|
||||
className="alerts-notification-form"
|
||||
form={form}
|
||||
onFinish={handleSave}>
|
||||
onFinish={handleSave}
|
||||
onValuesChange={handleChange}>
|
||||
<Card loading={loadingCount > 0}>
|
||||
<Form.Item
|
||||
label={t('label.name')}
|
||||
labelCol={{ span: 24 }}
|
||||
name="name"
|
||||
rules={[{ required: true }]}>
|
||||
<Input />
|
||||
<Input disabled={isEditMode} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t('label.description')}
|
||||
@ -375,277 +587,250 @@ const AddAlertPage = () => {
|
||||
rules={[{ required: true }]}>
|
||||
<Input.TextArea />
|
||||
</Form.Item>
|
||||
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<Space className="w-full" direction="vertical" size={16}>
|
||||
<StyledCard
|
||||
heading={t('label.trigger')}
|
||||
subHeading={t('message.alerts-trigger-description')}
|
||||
/>
|
||||
<div>
|
||||
<Form.Item
|
||||
initialValue={AlertTriggerType.AllDataAssets}
|
||||
name={['triggerConfig', 'type']}>
|
||||
<Select
|
||||
options={defaultTriggers.map((trigger) => ({
|
||||
label: getDisplayNameForTriggerType(trigger.type),
|
||||
value: trigger.type,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
{selectedTrigger?.type ===
|
||||
AlertTriggerType.SpecificDataAsset && (
|
||||
<Form.Item name={['triggerConfig', 'entities']}>
|
||||
<Form.Item>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<Space className="w-full" direction="vertical" size={16}>
|
||||
<StyledCard
|
||||
heading={t('label.trigger')}
|
||||
subHeading={t('message.alerts-trigger-description')}
|
||||
/>
|
||||
<div>
|
||||
<Form.Item
|
||||
initialValue={AlertTriggerType.AllDataAssets}
|
||||
name={['triggerConfig', 'type']}>
|
||||
<Select
|
||||
showArrow
|
||||
className="w-full"
|
||||
mode="multiple"
|
||||
options={
|
||||
selectedTrigger.entities?.map((entity) => ({
|
||||
value: entity,
|
||||
label: startCase(entity),
|
||||
})) ?? []
|
||||
}
|
||||
placeholder={t('label.select-data-assets')}
|
||||
options={defaultTriggers.map((trigger) => ({
|
||||
label: getDisplayNameForTriggerType(trigger.type),
|
||||
value: trigger.type,
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Space className="w-full" direction="vertical" size={16}>
|
||||
<StyledCard
|
||||
heading={t('label.filter-plural')}
|
||||
subHeading={t('message.alerts-filter-description')}
|
||||
/>
|
||||
{selectedTrigger?.type ===
|
||||
AlertTriggerType.SpecificDataAsset && (
|
||||
<Form.Item
|
||||
required
|
||||
messageVariables={{
|
||||
fieldName: t('label.data-assets'),
|
||||
}}
|
||||
name={['triggerConfig', 'entities']}>
|
||||
<Select
|
||||
showArrow
|
||||
className="w-full"
|
||||
mode="multiple"
|
||||
options={
|
||||
selectedTrigger.entities?.map((entity) => ({
|
||||
value: entity,
|
||||
label: startCase(entity),
|
||||
})) ?? []
|
||||
}
|
||||
placeholder={t('label.select-data-assets')}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</div>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Space className="w-full" direction="vertical" size={16}>
|
||||
<StyledCard
|
||||
heading={t('label.filter-plural')}
|
||||
subHeading={t('message.alerts-filter-description')}
|
||||
/>
|
||||
|
||||
<Form.List name="filteringRules">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }) => (
|
||||
<div key={`filteringRules-${key}`}>
|
||||
<div className="d-flex gap-1">
|
||||
<div className="flex-1">
|
||||
<Form.Item key={key} name={[name, 'name']}>
|
||||
<Select
|
||||
options={functions?.map(
|
||||
(func: string) => ({
|
||||
label: getFunctionDisplayName(func),
|
||||
value: func,
|
||||
})
|
||||
<Form.List
|
||||
name="filteringRules"
|
||||
rules={[
|
||||
{
|
||||
validator: listLengthValidator(
|
||||
t('label.filter-plural')
|
||||
),
|
||||
},
|
||||
]}>
|
||||
{(fields, { add, remove }, { errors }) => (
|
||||
<>
|
||||
<Form.Item>
|
||||
<Button
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
type="default"
|
||||
onClick={() => add({}, 0)}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.filter-plural'),
|
||||
})}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
{fields.map(({ key, name }) => (
|
||||
<div key={`filteringRules-${key}`}>
|
||||
{name > 0 && (
|
||||
<Divider
|
||||
style={{ margin: 0, marginBottom: '16px' }}
|
||||
/>
|
||||
)}
|
||||
<div className="d-flex gap-1">
|
||||
<div className="flex-1">
|
||||
<Form.Item key={key} name={[name, 'name']}>
|
||||
<Select
|
||||
options={functions}
|
||||
placeholder={t('label.select-field', {
|
||||
field: t('label.condition'),
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
{filters &&
|
||||
filters[name] &&
|
||||
getConditionField(
|
||||
filters[name].name ?? '',
|
||||
name
|
||||
)}
|
||||
placeholder={t('label.select-field', {
|
||||
field: t('label.condition'),
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
{filters &&
|
||||
filters[name] &&
|
||||
getConditionField(
|
||||
filters[name].name ?? '',
|
||||
name
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
initialValue={Effect.Allow}
|
||||
key={key}
|
||||
name={[name, 'effect']}>
|
||||
<Select
|
||||
options={map(Effect, (func: string) => ({
|
||||
label: startCase(func),
|
||||
value: func,
|
||||
}))}
|
||||
placeholder={t('label.select-field', {
|
||||
field: t('label.effect'),
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
initialValue={Effect.Allow}
|
||||
key={key}
|
||||
name={[name, 'effect']}>
|
||||
<Select
|
||||
options={map(
|
||||
Effect,
|
||||
(func: string) => ({
|
||||
label: startCase(func),
|
||||
value: func,
|
||||
})
|
||||
)}
|
||||
placeholder={t('label.select-field', {
|
||||
field: t('label.effect'),
|
||||
})}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<Button
|
||||
data-testid={`remove-filter-rule-${name}`}
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt={t('label.delete')}
|
||||
className="w-4"
|
||||
icon={Icons.DELETE}
|
||||
/>
|
||||
}
|
||||
type="text"
|
||||
onClick={() => remove(name)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
data-testid={`remove-filter-rule-${name}`}
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt={t('label.delete')}
|
||||
className="w-4"
|
||||
icon={Icons.DELETE}
|
||||
/>
|
||||
}
|
||||
type="text"
|
||||
onClick={() => remove(name)}
|
||||
/>
|
||||
</div>
|
||||
{fields.length - 1 !== key && (
|
||||
<Divider style={{ margin: '8px 0' }} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
type="dashed"
|
||||
onClick={() => add()}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.filter-plural'),
|
||||
})}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Space className="w-full" direction="vertical" size={16}>
|
||||
<StyledCard
|
||||
heading={t('label.destination')}
|
||||
subHeading={t('message.alerts-destination-description')}
|
||||
/>
|
||||
))}
|
||||
<Form.ErrorList errors={errors} />
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Space className="w-full" direction="vertical" size={16}>
|
||||
<StyledCard
|
||||
heading={t('label.destination')}
|
||||
subHeading={t('message.alerts-destination-description')}
|
||||
/>
|
||||
|
||||
<Form.List name="alertActions">
|
||||
{(fields, { add, remove }) => (
|
||||
<>
|
||||
{fields.map(({ key, name }) => (
|
||||
<div key={`alertActions-${key}`}>
|
||||
<div className="d-flex" style={{ gap: '10px' }}>
|
||||
<div className="flex-1">
|
||||
<Form.Item
|
||||
required
|
||||
key={key}
|
||||
name={[name, 'alertActionType']}>
|
||||
<Select
|
||||
placeholder={t('label.select-field', {
|
||||
field: t('label.source'),
|
||||
})}
|
||||
showSearch={false}>
|
||||
{map(AlertActionType, (value) => {
|
||||
return (
|
||||
<Select.Option
|
||||
key={value}
|
||||
value={value}>
|
||||
<Space size={16}>
|
||||
{getAlertsActionTypeIcon(
|
||||
value as AlertActionType
|
||||
)}
|
||||
{value}
|
||||
</Space>
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item required name={[name, 'name']}>
|
||||
<Input placeholder={t('label.name')} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
required
|
||||
name={[
|
||||
name,
|
||||
'alertActionConfig',
|
||||
'endpoint',
|
||||
]}>
|
||||
<Input
|
||||
placeholder={
|
||||
t('label.endpoint-url') +
|
||||
': ' +
|
||||
'http(s)://www.example.com'
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('label.advanced-config')}
|
||||
name={[name, 'enabled']}
|
||||
valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{get(
|
||||
alertActions,
|
||||
`${name}.enabled`,
|
||||
false
|
||||
) && (
|
||||
<>
|
||||
<Space className="w-full" size={16}>
|
||||
<Form.Item
|
||||
initialValue={10}
|
||||
label="Batch Size"
|
||||
labelCol={{ span: 24 }}
|
||||
name={[name, 'batchSize']}>
|
||||
<Input defaultValue={10} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
colon
|
||||
initialValue={10}
|
||||
label={`${t(
|
||||
'label.connection-timeout-plural-optional'
|
||||
)}`}
|
||||
labelCol={{ span: 24 }}
|
||||
name={[name, 'timeout']}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
<Form.Item
|
||||
label={t('label.secret-key')}
|
||||
labelCol={{ span: 24 }}
|
||||
name={[
|
||||
name,
|
||||
'alertActionConfig',
|
||||
'secretKey',
|
||||
]}>
|
||||
<Input
|
||||
placeholder={t('label.secret-key')}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.List
|
||||
name="alertActions"
|
||||
rules={[
|
||||
{
|
||||
validator: listLengthValidator(
|
||||
t('label.destination')
|
||||
),
|
||||
},
|
||||
]}>
|
||||
{(fields, { add, remove }, { errors }) => (
|
||||
<>
|
||||
<Form.Item>
|
||||
<Button
|
||||
block
|
||||
disabled={provider === ProviderType.System}
|
||||
icon={<PlusOutlined />}
|
||||
type="default"
|
||||
onClick={() => add({}, 0)}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.destination'),
|
||||
})}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
{fields.map(({ key, name }) => (
|
||||
<div key={`alertActions-${key}`}>
|
||||
{name > 0 && (
|
||||
<Divider
|
||||
style={{ margin: 0, marginBottom: '16px' }}
|
||||
/>
|
||||
)}
|
||||
<div className="d-flex" style={{ gap: '10px' }}>
|
||||
<div className="flex-1">
|
||||
<Form.Item
|
||||
required
|
||||
key={key}
|
||||
name={[name, 'alertActionType']}>
|
||||
<Select
|
||||
disabled={
|
||||
provider === ProviderType.System
|
||||
}
|
||||
placeholder={t('label.select-field', {
|
||||
field: t('label.source'),
|
||||
})}
|
||||
showSearch={false}>
|
||||
{map(AlertActionType, (value) => {
|
||||
return value ===
|
||||
AlertActionType.ActivityFeed ? null : (
|
||||
<Select.Option
|
||||
key={value}
|
||||
value={value}>
|
||||
<Space size={16}>
|
||||
{getAlertsActionTypeIcon(
|
||||
value as AlertActionType
|
||||
)}
|
||||
{getAlertActionTypeDisplayName(
|
||||
value
|
||||
)}
|
||||
</Space>
|
||||
</Select.Option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{getDestinationConfigFields(name)}
|
||||
</div>
|
||||
<Button
|
||||
data-testid={`remove-filter-rule-${name}`}
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt={t('label.delete')}
|
||||
className="w-4"
|
||||
icon={Icons.DELETE}
|
||||
/>
|
||||
}
|
||||
type="text"
|
||||
onClick={() => remove(name)}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
data-testid={`remove-filter-rule-${name}`}
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt={t('label.delete')}
|
||||
className="w-4"
|
||||
icon={Icons.DELETE}
|
||||
/>
|
||||
}
|
||||
type="text"
|
||||
onClick={() => remove(name)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{fields.length - 1 !== key && <Divider />}
|
||||
</div>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
block
|
||||
icon={<PlusOutlined />}
|
||||
type="dashed"
|
||||
onClick={() => add()}>
|
||||
{t('label.add-entity', {
|
||||
entity: t('label.destination'),
|
||||
})}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col className="footer" span={24}>
|
||||
<Button onClick={() => history.goBack()}>
|
||||
{t('label.cancel')}
|
||||
</Button>
|
||||
<Button htmlType="submit" type="primary">
|
||||
{t('label.save')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
))}
|
||||
<Form.ErrorList errors={errors} />
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col className="footer" span={24}>
|
||||
<Button onClick={() => history.goBack()}>
|
||||
{t('label.cancel')}
|
||||
</Button>
|
||||
<Button htmlType="submit" type="primary">
|
||||
{t('label.save')}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col span={24} />
|
||||
<Col span={24} />
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
|
||||
.alerts-notification-form {
|
||||
.ant-form-item {
|
||||
margin-bottom: 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
|
||||
@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2021 Collate
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Table, Typography } from 'antd';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getAlertActionForAlerts } from '../../axiosAPIs/alertsAPI';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import { AlertAction } from '../../generated/alerts/alertAction';
|
||||
import { Alerts } from '../../generated/alerts/alerts';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
export const AlertsExpanded = ({ alert }: { alert: Alerts }) => {
|
||||
const [alertActions, setAlertActions] = useState<AlertAction[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const { t } = useTranslation();
|
||||
const fetchAlertActions = useCallback(async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await getAlertActionForAlerts(alert.id);
|
||||
setAlertActions(response);
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
t('server.entity-fetch-error', { entity: t('label.alert-actions') })
|
||||
);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [alert.id]);
|
||||
|
||||
useEffect(() => {
|
||||
if (alert.id) {
|
||||
fetchAlertActions();
|
||||
}
|
||||
}, [alert.id]);
|
||||
|
||||
const columns = useMemo(
|
||||
() => [
|
||||
{
|
||||
title: t('label.name'),
|
||||
dataIndex: 'displayName',
|
||||
width: '200px',
|
||||
key: 'displayName',
|
||||
},
|
||||
{
|
||||
title: t('label.destination'),
|
||||
dataIndex: 'alertActionType',
|
||||
width: '200px',
|
||||
key: 'alertActionType',
|
||||
render: startCase,
|
||||
},
|
||||
{
|
||||
title: t('label.batch-size'),
|
||||
dataIndex: 'batchSize',
|
||||
width: '200px',
|
||||
key: 'batchSize',
|
||||
},
|
||||
{
|
||||
title: t('label.timeout'),
|
||||
dataIndex: 'timeout',
|
||||
width: '200px',
|
||||
key: 'timeout',
|
||||
},
|
||||
{
|
||||
title: t('label.endpoint'),
|
||||
dataIndex: ['alertActionConfig', 'endpoint'],
|
||||
width: '200px',
|
||||
key: 'endpoint',
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="p-x-sm">
|
||||
<Typography.Title level={5}>{t('label.alert-actions')}</Typography.Title>
|
||||
<Table
|
||||
bordered={false}
|
||||
columns={columns}
|
||||
dataSource={alertActions}
|
||||
loading={{ spinning: loading, indicator: <Loader size="small" /> }}
|
||||
pagination={false}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -10,11 +10,14 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Icon from '@ant-design/icons/lib/components/Icon';
|
||||
import { Button, Col, Row, Table, Tag, Tooltip, Typography } from 'antd';
|
||||
import { isNil } from 'lodash';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { ReactComponent as DropDownIcon } from '../../assets/svg/DropDown.svg';
|
||||
import { ReactComponent as RightArrowIcon } from '../../assets/svg/ic-right-arrow.svg';
|
||||
import { getAllAlerts } from '../../axiosAPIs/alertsAPI';
|
||||
import DeleteWidgetModal from '../../components/common/DeleteWidget/DeleteWidgetModal';
|
||||
import NextPrevious from '../../components/common/next-previous/NextPrevious';
|
||||
@ -32,6 +35,7 @@ import { getDisplayNameForTriggerType } from '../../utils/Alerts/AlertsUtil';
|
||||
import { getSettingPath } from '../../utils/RouterUtils';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import { AlertsExpanded } from './AlertRowExpanded';
|
||||
|
||||
const AlertsPage = () => {
|
||||
const [loading, setLoading] = useState(true);
|
||||
@ -166,6 +170,17 @@ const AlertsPage = () => {
|
||||
bordered
|
||||
columns={columns}
|
||||
dataSource={alerts}
|
||||
expandable={{
|
||||
expandedRowRender: (record) => <AlertsExpanded alert={record} />,
|
||||
expandIcon: ({ expanded, onExpand, expandable, record }) =>
|
||||
expandable && (
|
||||
<Icon
|
||||
component={expanded ? DropDownIcon : RightArrowIcon}
|
||||
size={16}
|
||||
onClick={(e) => onExpand(record, e)}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
loading={{ spinning: loading, indicator: <Loader /> }}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
@ -187,6 +202,7 @@ const AlertsPage = () => {
|
||||
|
||||
<DeleteWidgetModal
|
||||
afterDeleteAction={handleAlertDelete}
|
||||
allowSoftDelete={false}
|
||||
entityId={selectedAlert?.id || ''}
|
||||
entityName={selectedAlert?.name || ''}
|
||||
entityType={EntityType.ALERT}
|
||||
|
||||
@ -324,3 +324,16 @@
|
||||
.quick-filter-dropdown-trigger-btn:focus {
|
||||
background-color: @trigger-btn-hover-bg;
|
||||
}
|
||||
|
||||
// Rotate
|
||||
.rotate-inverse {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.rotate-90 {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.rotate--90 {
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
@ -362,10 +362,6 @@
|
||||
animation: highlight 3000ms ease-out;
|
||||
}
|
||||
|
||||
.rotate-inverse {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.rjsf.no-header #root__title,
|
||||
.rjsf.no-header #root__description,
|
||||
.rjsf .form-additional .control-label,
|
||||
|
||||
@ -11,14 +11,15 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Card, Typography } from 'antd';
|
||||
import { Typography } from 'antd';
|
||||
import { RuleObject } from 'antd/lib/form';
|
||||
import i18next from 'i18next';
|
||||
import React from 'react';
|
||||
import { ReactComponent as AllActivityIcon } from '../../assets/svg/all-activity.svg';
|
||||
import { ReactComponent as MailIcon } from '../../assets/svg/ic-mail.svg';
|
||||
import { ReactComponent as MSTeamsIcon } from '../../assets/svg/ms-teams-grey.svg';
|
||||
import { ReactComponent as SlackIcon } from '../../assets/svg/slack-grey.svg';
|
||||
import { ReactComponent as WebhookIcon } from '../../assets/svg/webhook-grey.svg';
|
||||
import { ReactComponent as MSTeamsIcon } from '../../assets/svg/ms-teams.svg';
|
||||
import { ReactComponent as SlackIcon } from '../../assets/svg/slack.svg';
|
||||
import { ReactComponent as WebhookIcon } from '../../assets/svg/webhook.svg';
|
||||
import { AlertActionType } from '../../generated/alerts/alertAction';
|
||||
import { AlertTriggerType } from '../../generated/alerts/alerts';
|
||||
|
||||
@ -65,13 +66,13 @@ export const StyledCard = ({
|
||||
subHeading: string;
|
||||
}) => {
|
||||
return (
|
||||
<Card bordered={false} className="bg-grey">
|
||||
<div className="bg-grey p-sm rounded-4">
|
||||
<Typography.Text>{heading}</Typography.Text>
|
||||
<br />
|
||||
<Typography.Text className="text-xs text-grey-muted">
|
||||
{subHeading}
|
||||
</Typography.Text>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -83,3 +84,43 @@ export const getDisplayNameForTriggerType = (type: AlertTriggerType) => {
|
||||
return i18next.t('label.specific-data-assets');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* @param name Field name used to identify which field has error
|
||||
* @param minLengthRequired how many item should be there in the list
|
||||
* @returns If validation failed throws an error else resolve
|
||||
*/
|
||||
export const listLengthValidator =
|
||||
<T,>(name: string, minLengthRequired = 1) =>
|
||||
async (_: RuleObject, list: T[]) => {
|
||||
if (!list || list.length < minLengthRequired) {
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
i18next.t('message.length-validator-error', {
|
||||
length: minLengthRequired,
|
||||
field: name,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
export const getAlertActionTypeDisplayName = (
|
||||
alertActionType: AlertActionType
|
||||
) => {
|
||||
switch (alertActionType) {
|
||||
case AlertActionType.ActivityFeed:
|
||||
return i18next.t('label.activity-feed');
|
||||
case AlertActionType.Email:
|
||||
return i18next.t('label.email');
|
||||
case AlertActionType.GenericWebhook:
|
||||
return i18next.t('label.webhook');
|
||||
case AlertActionType.SlackWebhook:
|
||||
return i18next.t('label.slack');
|
||||
case AlertActionType.MSTeamsWebhook:
|
||||
return i18next.t('label.ms-teams');
|
||||
}
|
||||
};
|
||||
|
||||
@ -11,8 +11,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Typography } from 'antd';
|
||||
import { ExpandableConfig } from 'antd/lib/table/interface';
|
||||
import classNames from 'classnames';
|
||||
@ -21,6 +19,7 @@ import { isEmpty, upperCase } from 'lodash';
|
||||
import { EntityTags } from 'Models';
|
||||
import React from 'react';
|
||||
import { ReactComponent as DashboardIcon } from '../assets/svg/dashboard-grey.svg';
|
||||
import { ReactComponent as DropDownIcon } from '../assets/svg/DropDown.svg';
|
||||
import { ReactComponent as MlModelIcon } from '../assets/svg/mlmodal.svg';
|
||||
import { ReactComponent as PipelineIcon } from '../assets/svg/pipeline-grey.svg';
|
||||
import { ReactComponent as TableIcon } from '../assets/svg/table-grey.svg';
|
||||
@ -373,7 +372,7 @@ export function getTableExpandableConfig<
|
||||
className="m-r-xs cursor-pointer"
|
||||
data-testid="expand-icon"
|
||||
onClick={(e) => onExpand(record, e)}>
|
||||
<FontAwesomeIcon icon={expanded ? faCaretDown : faCaretRight} />
|
||||
{expanded ? <DropDownIcon /> : <DropDownIcon />}
|
||||
</Typography.Text>
|
||||
),
|
||||
};
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user