mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-24 17:08:28 +00:00
fix(ui): webhook event handling with tree structure (#7164)
* fix(ui): webhook event handling with tree structure * Fix layout * minor fix * Fix edit webhook redirection. * Fix tree structure issues Co-authored-by: Sachin Chaurasiya <sachinchaurasiyachotey87@gmail.com>
This commit is contained in:
parent
c729abaa95
commit
00e6996ac0
@ -68,3 +68,11 @@ export const resetAllFilters = async () => {
|
|||||||
|
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getInitialFilters = async () => {
|
||||||
|
const url = `${BASE_URL}/bootstrappedFilters`;
|
||||||
|
|
||||||
|
const response = await axiosClient.get<EventFilter[]>(url);
|
||||||
|
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
@ -14,7 +14,6 @@
|
|||||||
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
import { faArrowLeft } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import { Store } from 'antd/lib/form/interface';
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import cryptoRandomString from 'crypto-random-string-with-promisify-polyfill';
|
import cryptoRandomString from 'crypto-random-string-with-promisify-polyfill';
|
||||||
import { cloneDeep, isEmpty, isNil } from 'lodash';
|
import { cloneDeep, isEmpty, isNil } from 'lodash';
|
||||||
@ -26,7 +25,7 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { ROUTES, TERM_ALL } from '../../constants/constants';
|
import { ROUTES } from '../../constants/constants';
|
||||||
import {
|
import {
|
||||||
GlobalSettingOptions,
|
GlobalSettingOptions,
|
||||||
GlobalSettingsMenuCategory,
|
GlobalSettingsMenuCategory,
|
||||||
@ -55,7 +54,6 @@ import {
|
|||||||
import { checkPermission } from '../../utils/PermissionsUtils';
|
import { checkPermission } from '../../utils/PermissionsUtils';
|
||||||
import { getSettingPath } from '../../utils/RouterUtils';
|
import { getSettingPath } from '../../utils/RouterUtils';
|
||||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||||
import { getEventFilters } from '../../utils/WebhookUtils';
|
|
||||||
import { Button } from '../buttons/Button/Button';
|
import { Button } from '../buttons/Button/Button';
|
||||||
import CopyToClipboardButton from '../buttons/CopyToClipboardButton/CopyToClipboardButton';
|
import CopyToClipboardButton from '../buttons/CopyToClipboardButton/CopyToClipboardButton';
|
||||||
import CardV1 from '../common/Card/CardV1';
|
import CardV1 from '../common/Card/CardV1';
|
||||||
@ -67,8 +65,7 @@ import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal';
|
|||||||
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
||||||
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
|
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
|
||||||
import { AddWebhookProps } from './AddWebhook.interface';
|
import { AddWebhookProps } from './AddWebhook.interface';
|
||||||
import EventFilterSelect from './EventFilterSelect.component';
|
import EventFilterTree from './EventFilterTree.component';
|
||||||
import { EVENT_FILTER_FORM_INITIAL_VALUE } from './WebhookConstants';
|
|
||||||
|
|
||||||
const CONFIGURE_TEXT: { [key: string]: string } = {
|
const CONFIGURE_TEXT: { [key: string]: string } = {
|
||||||
msteams: CONFIGURE_MS_TEAMS_TEXT,
|
msteams: CONFIGURE_MS_TEAMS_TEXT,
|
||||||
@ -80,26 +77,6 @@ const Field = ({ children }: { children: React.ReactNode }) => {
|
|||||||
return <div className="tw-mt-4">{children}</div>;
|
return <div className="tw-mt-4">{children}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFormData = (eventFilters: EventFilter[]): Store => {
|
|
||||||
if (eventFilters.length === 1 && eventFilters[0].entityType === TERM_ALL) {
|
|
||||||
return EVENT_FILTER_FORM_INITIAL_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
const formEventFilters = {} as Store;
|
|
||||||
|
|
||||||
eventFilters?.forEach((eventFilter) => {
|
|
||||||
if (eventFilter.entityType === TERM_ALL) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
formEventFilters[eventFilter.entityType] = true;
|
|
||||||
formEventFilters[`${eventFilter.entityType}-tree`] =
|
|
||||||
eventFilter.filters?.map((filter) => filter.eventType) || [];
|
|
||||||
});
|
|
||||||
|
|
||||||
return formEventFilters;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AddWebhook: FunctionComponent<AddWebhookProps> = ({
|
const AddWebhook: FunctionComponent<AddWebhookProps> = ({
|
||||||
data,
|
data,
|
||||||
header,
|
header,
|
||||||
@ -113,11 +90,9 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
|
|||||||
onSave,
|
onSave,
|
||||||
}: AddWebhookProps) => {
|
}: AddWebhookProps) => {
|
||||||
const markdownRef = useRef<EditorContentRef>();
|
const markdownRef = useRef<EditorContentRef>();
|
||||||
const [eventFilterFormData, setEventFilterFormData] = useState<Store>(
|
const [eventFilterFormData, setEventFilterFormData] = useState<
|
||||||
data?.eventFilters
|
EventFilter[] | undefined
|
||||||
? getFormData(data?.eventFilters)
|
>(data?.eventFilters);
|
||||||
: EVENT_FILTER_FORM_INITIAL_VALUE
|
|
||||||
);
|
|
||||||
const [name, setName] = useState<string>(data?.name || '');
|
const [name, setName] = useState<string>(data?.name || '');
|
||||||
const [endpointUrl, setEndpointUrl] = useState<string>(data?.endpoint || '');
|
const [endpointUrl, setEndpointUrl] = useState<string>(data?.endpoint || '');
|
||||||
const [description] = useState<string>(data?.description || '');
|
const [description] = useState<string>(data?.description || '');
|
||||||
@ -250,7 +225,7 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
|
|||||||
name,
|
name,
|
||||||
description: markdownRef.current?.getEditorContent() || undefined,
|
description: markdownRef.current?.getEditorContent() || undefined,
|
||||||
endpoint: endpointUrl,
|
endpoint: endpointUrl,
|
||||||
eventFilters: getEventFilters(eventFilterFormData),
|
eventFilters: eventFilterFormData ?? ([] as EventFilter[]),
|
||||||
batchSize,
|
batchSize,
|
||||||
timeout: connectionTimeout,
|
timeout: connectionTimeout,
|
||||||
enabled: active,
|
enabled: active,
|
||||||
@ -468,9 +443,9 @@ const AddWebhook: FunctionComponent<AddWebhookProps> = ({
|
|||||||
</span>,
|
</span>,
|
||||||
'tw-mt-3'
|
'tw-mt-3'
|
||||||
)}
|
)}
|
||||||
<EventFilterSelect
|
<EventFilterTree
|
||||||
eventFilterFormData={eventFilterFormData}
|
value={eventFilterFormData || []}
|
||||||
setEventFilterFormData={(data) => setEventFilterFormData(data)}
|
onChange={setEventFilterFormData}
|
||||||
/>
|
/>
|
||||||
<Field>
|
<Field>
|
||||||
<div className="tw-flex tw-justify-end tw-pt-1">
|
<div className="tw-flex tw-justify-end tw-pt-1">
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
import { Col, Form, Row, TreeSelect } from 'antd';
|
|
||||||
import Checkbox from 'antd/lib/checkbox/Checkbox';
|
|
||||||
import { Store } from 'antd/lib/form/interface';
|
|
||||||
import { startCase } from 'lodash';
|
|
||||||
import React, { useMemo } from 'react';
|
|
||||||
import {
|
|
||||||
EventFilter,
|
|
||||||
EventType,
|
|
||||||
} from '../../generated/api/events/createWebhook';
|
|
||||||
import { Entities } from './WebhookConstants';
|
|
||||||
|
|
||||||
export enum EventUpdateTypes {
|
|
||||||
UpdatedFollowers = 'updatedFollowers',
|
|
||||||
UpdatedTags = 'updatedTags',
|
|
||||||
UpdatedOwner = 'updatedOwner',
|
|
||||||
UpdateDescription = 'updateDescription',
|
|
||||||
}
|
|
||||||
|
|
||||||
interface EventFilterSelectProps {
|
|
||||||
eventFilterFormData: Store;
|
|
||||||
setEventFilterFormData: (formData: EventFilter[]) => void;
|
|
||||||
}
|
|
||||||
const EventFilterSelect = ({
|
|
||||||
eventFilterFormData,
|
|
||||||
setEventFilterFormData,
|
|
||||||
}: EventFilterSelectProps) => {
|
|
||||||
const metricsOptions = useMemo(
|
|
||||||
() => [
|
|
||||||
{
|
|
||||||
title: 'All',
|
|
||||||
value: 'all',
|
|
||||||
key: 'all',
|
|
||||||
children: Object.values(EventType).map((metric) => ({
|
|
||||||
title: startCase(metric),
|
|
||||||
value: metric,
|
|
||||||
key: metric,
|
|
||||||
children:
|
|
||||||
metric === EventType.EntityUpdated
|
|
||||||
? Object.values(EventUpdateTypes).map((updateType) => ({
|
|
||||||
title: startCase(updateType),
|
|
||||||
value: `${EventType.EntityUpdated}-${updateType}`,
|
|
||||||
key: `${EventType.EntityUpdated}-${updateType}`,
|
|
||||||
}))
|
|
||||||
: undefined,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Form
|
|
||||||
autoComplete="off"
|
|
||||||
initialValues={eventFilterFormData}
|
|
||||||
layout="vertical"
|
|
||||||
onValuesChange={(_, data) => {
|
|
||||||
setEventFilterFormData(data);
|
|
||||||
}}>
|
|
||||||
<Row gutter={16}>
|
|
||||||
{Object.keys(Entities).map((key) => {
|
|
||||||
const value = Entities[key];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Col key={key} span={12}>
|
|
||||||
<Form.Item
|
|
||||||
name={key}
|
|
||||||
style={{ marginBottom: 4 }}
|
|
||||||
valuePropName="checked">
|
|
||||||
<Checkbox>{value}</Checkbox>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name={`${key}-tree`} style={{ marginBottom: 8 }}>
|
|
||||||
<TreeSelect
|
|
||||||
treeCheckable
|
|
||||||
disabled={!eventFilterFormData[key]}
|
|
||||||
maxTagCount={2}
|
|
||||||
placeholder="Please select"
|
|
||||||
showCheckedStrategy="SHOW_PARENT"
|
|
||||||
treeData={metricsOptions}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
</Col>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Row>
|
|
||||||
</Form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default EventFilterSelect;
|
|
@ -0,0 +1,138 @@
|
|||||||
|
import { Divider, Tree } from 'antd';
|
||||||
|
import { cloneDeep, isEmpty, map, startCase } from 'lodash';
|
||||||
|
import React, { Key, useEffect, useMemo, useState } from 'react';
|
||||||
|
import { getInitialFilters } from '../../axiosAPIs/eventFiltersAPI';
|
||||||
|
import { TERM_ALL } from '../../constants/constants';
|
||||||
|
import { EventFilter } from '../../generated/api/events/createWebhook';
|
||||||
|
import { Filters } from '../../generated/settings/settings';
|
||||||
|
import { getEventFilterFromTree } from '../../pages/ActivityFeedSettingsPage/ActivityFeedSettingsPage.utils';
|
||||||
|
import './../../pages/ActivityFeedSettingsPage/ActivityFeedSettingsPage.style.less';
|
||||||
|
|
||||||
|
interface EventFilterTreeProps {
|
||||||
|
value: EventFilter[];
|
||||||
|
onChange: (data: EventFilter[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const EventFilterTree = ({ value, onChange }: EventFilterTreeProps) => {
|
||||||
|
const [updatedTree, setUpdatedTree] = useState<Record<string, string[]>>();
|
||||||
|
const [initialFilters, setInitialFilters] = useState<EventFilter[]>([]);
|
||||||
|
|
||||||
|
const eventFilters = isEmpty(value) ? initialFilters : value;
|
||||||
|
const fetchInitialFilters = async () => {
|
||||||
|
try {
|
||||||
|
const data = await getInitialFilters();
|
||||||
|
setInitialFilters(data);
|
||||||
|
isEmpty(value) && onChange(getEventFilterFromTree({}, eventFilters));
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error(`filed to fetch event filters `);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchInitialFilters();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const generateTreeData = (entityType: string, data?: Filters[]) => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
key: entityType,
|
||||||
|
title: <strong>{startCase(entityType)}</strong>,
|
||||||
|
data: true,
|
||||||
|
children:
|
||||||
|
data?.map(({ eventType, include, exclude }) => {
|
||||||
|
const key = `${entityType}-${eventType}` as string;
|
||||||
|
|
||||||
|
return {
|
||||||
|
key: key,
|
||||||
|
title: startCase(eventType),
|
||||||
|
data: isEmpty(value) ? true : include,
|
||||||
|
children:
|
||||||
|
(include?.length === 1 && include[0] === TERM_ALL) ||
|
||||||
|
(exclude?.length === 1 && exclude[0] === TERM_ALL)
|
||||||
|
? undefined
|
||||||
|
: [
|
||||||
|
...(include?.map((inc) => ({
|
||||||
|
key: `${key}-${inc}`,
|
||||||
|
title: startCase(inc),
|
||||||
|
data: true,
|
||||||
|
})) || []),
|
||||||
|
...(exclude?.map((ex) => ({
|
||||||
|
key: `${key}-${ex}`,
|
||||||
|
title: startCase(ex),
|
||||||
|
data: false,
|
||||||
|
})) || []),
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}) || [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTreeCheckChange = (keys: Key[], entityType: string) => {
|
||||||
|
const updateData = cloneDeep(updatedTree || {});
|
||||||
|
|
||||||
|
updateData[entityType] = keys as string[];
|
||||||
|
|
||||||
|
onChange(getEventFilterFromTree(cloneDeep(updateData), eventFilters));
|
||||||
|
setUpdatedTree(updateData);
|
||||||
|
};
|
||||||
|
const getCheckedKeys = (eventFilters: EventFilter[]) => {
|
||||||
|
const checkedArray = [] as string[];
|
||||||
|
const clonedFilters = cloneDeep(eventFilters);
|
||||||
|
|
||||||
|
clonedFilters?.map(({ entityType, filters }) => {
|
||||||
|
filters &&
|
||||||
|
filters.map((obj) => {
|
||||||
|
if (
|
||||||
|
obj.include &&
|
||||||
|
obj.include.length === 1 &&
|
||||||
|
obj.include[0] === 'all'
|
||||||
|
) {
|
||||||
|
checkedArray.push(`${entityType}-${obj.eventType}`);
|
||||||
|
} else {
|
||||||
|
obj?.include?.forEach((entityUpdated) => {
|
||||||
|
const name = `${entityType}-${obj.eventType}-${entityUpdated}`;
|
||||||
|
checkedArray.push(name);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return checkedArray;
|
||||||
|
};
|
||||||
|
|
||||||
|
const checkedKeys = useMemo(() => {
|
||||||
|
const checkKeys = getCheckedKeys(eventFilters as EventFilter[]);
|
||||||
|
|
||||||
|
return checkKeys;
|
||||||
|
}, [eventFilters, updatedTree]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{initialFilters &&
|
||||||
|
map(initialFilters, ({ entityType, filters }, index) => (
|
||||||
|
<>
|
||||||
|
{entityType !== TERM_ALL ? (
|
||||||
|
<div className="tw-rounded-border" key={entityType}>
|
||||||
|
<Tree
|
||||||
|
checkable
|
||||||
|
defaultExpandAll
|
||||||
|
className="activity-feed-settings-tree"
|
||||||
|
defaultCheckedKeys={checkedKeys}
|
||||||
|
key={entityType}
|
||||||
|
treeData={generateTreeData(entityType, filters)}
|
||||||
|
onCheck={(keys) =>
|
||||||
|
handleTreeCheckChange(keys as Key[], entityType)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{index !== initialFilters.length - 1 && <Divider />}
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EventFilterTree;
|
@ -129,64 +129,53 @@ const WebhooksV1: FC<WebhooksV1Props> = ({
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col>
|
<Col xs={18}>
|
||||||
<Row gutter={[16, 16]}>
|
<Select
|
||||||
<Col xs={18}>
|
showArrow
|
||||||
<Select
|
bordered={false}
|
||||||
showArrow
|
className="tw-text-body webhook-filter-select cursor-pointer"
|
||||||
bordered={false}
|
mode="multiple"
|
||||||
className="tw-text-body webhook-filter-select cursor-pointer"
|
options={statuses}
|
||||||
mode="multiple"
|
placeholder="Filter by status"
|
||||||
options={statuses}
|
style={{ minWidth: '148px' }}
|
||||||
placeholder="Filter by status"
|
onChange={onStatusFilter}
|
||||||
style={{ minWidth: '148px' }}
|
/>
|
||||||
onChange={onStatusFilter}
|
</Col>
|
||||||
/>
|
<Col xs={6}>
|
||||||
</Col>
|
<Space align="center" className="tw-w-full tw-justify-end" size={16}>
|
||||||
<Col xs={6}>
|
<Tooltip
|
||||||
<Space
|
placement="left"
|
||||||
align="center"
|
title={
|
||||||
className="tw-w-full tw-justify-end"
|
addWebhookPermission ? 'Add Webhook' : NO_PERMISSION_FOR_ACTION
|
||||||
size={16}>
|
}>
|
||||||
{filteredData.length > 0 && (
|
<Button
|
||||||
<Tooltip
|
className={classNames('tw-h-8 tw-rounded ')}
|
||||||
placement="left"
|
data-testid="add-webhook-button"
|
||||||
title={
|
disabled={!addWebhookPermission}
|
||||||
addWebhookPermission
|
size="small"
|
||||||
? 'Add Webhook'
|
theme="primary"
|
||||||
: NO_PERMISSION_FOR_ACTION
|
variant="contained"
|
||||||
}>
|
onClick={onAddWebhook}>
|
||||||
<Button
|
Add {WEBHOOKS_INTEGRATION[webhookType]}
|
||||||
className={classNames('tw-h-8 tw-rounded ')}
|
</Button>
|
||||||
data-testid="add-webhook-button"
|
</Tooltip>
|
||||||
disabled={!addWebhookPermission}
|
</Space>
|
||||||
size="small"
|
</Col>
|
||||||
theme="primary"
|
<Col xs={24}>
|
||||||
variant="contained"
|
<WebhookTable
|
||||||
onClick={onAddWebhook}>
|
webhookList={filteredData || []}
|
||||||
Add {WEBHOOKS_INTEGRATION[webhookType]}
|
onDelete={(data) => setWebhook(data)}
|
||||||
</Button>
|
onEdit={onClickWebhook}
|
||||||
</Tooltip>
|
/>
|
||||||
)}
|
{Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
|
||||||
</Space>
|
<NextPrevious
|
||||||
</Col>
|
currentPage={currentPage}
|
||||||
<Col xs={24}>
|
pageSize={PAGE_SIZE}
|
||||||
<WebhookTable
|
paging={paging}
|
||||||
webhookList={filteredData || []}
|
pagingHandler={onPageChange}
|
||||||
onDelete={(data) => setWebhook(data)}
|
totalCount={paging.total}
|
||||||
onEdit={onClickWebhook}
|
/>
|
||||||
/>
|
)}
|
||||||
{Boolean(!isNil(paging.after) || !isNil(paging.before)) && (
|
|
||||||
<NextPrevious
|
|
||||||
currentPage={currentPage}
|
|
||||||
pageSize={PAGE_SIZE}
|
|
||||||
paging={paging}
|
|
||||||
pagingHandler={onPageChange}
|
|
||||||
totalCount={paging.total}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{selectedWebhook && (
|
{selectedWebhook && (
|
||||||
|
@ -169,6 +169,7 @@ const jsonData = {
|
|||||||
|
|
||||||
'update-profile-congif-success': 'Profile config updated successfully!',
|
'update-profile-congif-success': 'Profile config updated successfully!',
|
||||||
'update-test-case-success': 'Test case updated successfully!',
|
'update-test-case-success': 'Test case updated successfully!',
|
||||||
|
'update-webhook-success': 'Webhook updated successfully!',
|
||||||
},
|
},
|
||||||
'form-error-messages': {
|
'form-error-messages': {
|
||||||
'empty-email': 'Email is required.',
|
'empty-email': 'Email is required.',
|
||||||
|
@ -6,6 +6,8 @@ describe('Test ActivityFeedSettingsPage', () => {
|
|||||||
it('should render properly', async () => {
|
it('should render properly', async () => {
|
||||||
const { findByText } = render(<ActivityFeedSettingsPage />);
|
const { findByText } = render(<ActivityFeedSettingsPage />);
|
||||||
|
|
||||||
expect(await findByText(/Activity Feed/)).toBeInTheDocument();
|
expect(
|
||||||
|
await findByText(/No activity feed settings available/)
|
||||||
|
).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
resetAllFilters,
|
resetAllFilters,
|
||||||
updateFilters,
|
updateFilters,
|
||||||
} from '../../axiosAPIs/eventFiltersAPI';
|
} from '../../axiosAPIs/eventFiltersAPI';
|
||||||
|
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||||
import Loader from '../../components/Loader/Loader';
|
import Loader from '../../components/Loader/Loader';
|
||||||
import { TERM_ALL } from '../../constants/constants';
|
import { TERM_ALL } from '../../constants/constants';
|
||||||
import {
|
import {
|
||||||
@ -187,51 +188,61 @@ const ActivityFeedSettingsPage: React.FC = () => {
|
|||||||
<Loader />
|
<Loader />
|
||||||
</Col>
|
</Col>
|
||||||
) : (
|
) : (
|
||||||
<Row gutter={[16, 16]}>
|
<>
|
||||||
<Col span={24}>
|
{eventFilters ? (
|
||||||
<Typography.Title level={5} type="secondary">
|
<>
|
||||||
Activity Feed
|
<Row gutter={[16, 16]}>
|
||||||
</Typography.Title>
|
<Col span={24}>
|
||||||
</Col>
|
<Typography.Title level={5} type="secondary">
|
||||||
<Col span={24}>
|
Activity Feed
|
||||||
<Card size="small">
|
</Typography.Title>
|
||||||
{eventFilters &&
|
</Col>
|
||||||
map(eventFilters, ({ entityType, filters }, index) => (
|
<Col span={24}>
|
||||||
<>
|
<Card size="small">
|
||||||
{entityType !== TERM_ALL ? (
|
{eventFilters &&
|
||||||
<div className="tw-rounded-border" key={entityType}>
|
map(eventFilters, ({ entityType, filters }, index) => (
|
||||||
<Tree
|
<>
|
||||||
checkable
|
{entityType !== TERM_ALL ? (
|
||||||
defaultExpandAll
|
<div className="tw-rounded-border" key={entityType}>
|
||||||
className="activity-feed-settings-tree"
|
<Tree
|
||||||
defaultCheckedKeys={checkedKeys}
|
checkable
|
||||||
icon={null}
|
defaultExpandAll
|
||||||
key={entityType}
|
className="activity-feed-settings-tree"
|
||||||
treeData={generateTreeData(entityType, filters)}
|
defaultCheckedKeys={checkedKeys}
|
||||||
onCheck={(keys) =>
|
icon={null}
|
||||||
handleTreeCheckChange(keys as Key[], entityType)
|
key={entityType}
|
||||||
}
|
treeData={generateTreeData(entityType, filters)}
|
||||||
/>
|
onCheck={(keys) =>
|
||||||
{index !== eventFilters?.length - 1 && <Divider />}
|
handleTreeCheckChange(keys as Key[], entityType)
|
||||||
</div>
|
}
|
||||||
) : null}
|
/>
|
||||||
</>
|
{index !== eventFilters?.length - 1 && <Divider />}
|
||||||
))}
|
</div>
|
||||||
</Card>
|
) : null}
|
||||||
</Col>
|
</>
|
||||||
<Col>
|
))}
|
||||||
<Space direction="horizontal" size={16}>
|
</Card>
|
||||||
<Button type="primary" onClick={onSave}>
|
</Col>
|
||||||
Save
|
<Col>
|
||||||
</Button>
|
<Space direction="horizontal" size={16}>
|
||||||
<Button type="text" onClick={handleResetClick}>
|
<Button type="primary" onClick={onSave}>
|
||||||
Reset all
|
Save
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
<Button type="text" onClick={handleResetClick}>
|
||||||
</Col>
|
Reset all
|
||||||
<Col span={24} />
|
</Button>
|
||||||
<Col span={24} />
|
</Space>
|
||||||
</Row>
|
</Col>
|
||||||
|
<Col span={24} />
|
||||||
|
<Col span={24} />
|
||||||
|
</Row>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<ErrorPlaceHolder>
|
||||||
|
<Typography.Text>No activity feed settings available</Typography.Text>
|
||||||
|
</ErrorPlaceHolder>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { intersection, isEmpty, isUndefined, xor } from 'lodash';
|
import { isEmpty, isUndefined, xor } from 'lodash';
|
||||||
import {
|
import {
|
||||||
EventFilter,
|
EventFilter,
|
||||||
EventType,
|
EventType,
|
||||||
@ -77,19 +77,30 @@ export const getEventFilterFromTree = (
|
|||||||
filters: eventFilter.filters?.map((filter) => {
|
filters: eventFilter.filters?.map((filter) => {
|
||||||
let includeList = filter.include;
|
let includeList = filter.include;
|
||||||
let excludeList = filter.exclude;
|
let excludeList = filter.exclude;
|
||||||
|
|
||||||
|
// derive the merge list
|
||||||
|
const mergedList = [
|
||||||
|
...(includeList as string[]),
|
||||||
|
...(excludeList as string[]),
|
||||||
|
];
|
||||||
|
|
||||||
|
// manipulate tree if event type is present
|
||||||
if (updatedTree[eventFilter.entityType]) {
|
if (updatedTree[eventFilter.entityType]) {
|
||||||
|
// Split the value to get list of [eventType, filter, event]
|
||||||
const temp = updatedTree[eventFilter.entityType].map((key) =>
|
const temp = updatedTree[eventFilter.entityType].map((key) =>
|
||||||
key.split('-')
|
key.split('-')
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// grab the list of current eventType
|
||||||
const eventList = temp.filter((f) => f[1] === filter.eventType);
|
const eventList = temp.filter((f) => f[1] === filter.eventType);
|
||||||
|
|
||||||
if (eventList.length > 0) {
|
if (eventList.length > 0) {
|
||||||
if (filter.eventType === EventType.EntityUpdated) {
|
if (filter.eventType === EventType.EntityUpdated) {
|
||||||
includeList = intersection(
|
// derive include list based on selected events
|
||||||
filter.include ?? [],
|
includeList = eventList.map((f) => f[2]).filter(Boolean);
|
||||||
eventList.map((f) => f[2])
|
|
||||||
);
|
// derive the exclude list by symmetric difference
|
||||||
excludeList = xor(filter.include, includeList);
|
excludeList = xor(mergedList, includeList);
|
||||||
} else {
|
} else {
|
||||||
includeList = ['all'];
|
includeList = ['all'];
|
||||||
excludeList = [];
|
excludeList = [];
|
||||||
|
@ -30,11 +30,11 @@ import {
|
|||||||
} from '../../constants/globalSettings.constants';
|
} from '../../constants/globalSettings.constants';
|
||||||
import { FormSubmitType } from '../../enums/form.enum';
|
import { FormSubmitType } from '../../enums/form.enum';
|
||||||
import { CreateWebhook } from '../../generated/api/events/createWebhook';
|
import { CreateWebhook } from '../../generated/api/events/createWebhook';
|
||||||
import { Webhook } from '../../generated/entity/events/webhook';
|
import { Webhook, WebhookType } from '../../generated/entity/events/webhook';
|
||||||
import { useAuth } from '../../hooks/authHooks';
|
import { useAuth } from '../../hooks/authHooks';
|
||||||
import jsonData from '../../jsons/en';
|
import jsonData from '../../jsons/en';
|
||||||
import { getSettingPath } from '../../utils/RouterUtils';
|
import { getSettingPath } from '../../utils/RouterUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||||
|
|
||||||
const EDIT_HEADER_WEBHOOKS_TITLE: { [key: string]: string } = {
|
const EDIT_HEADER_WEBHOOKS_TITLE: { [key: string]: string } = {
|
||||||
msteams: 'MS Teams',
|
msteams: 'MS Teams',
|
||||||
@ -69,11 +69,23 @@ const EditWebhookPage: FunctionComponent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const goToWebhooks = () => {
|
const goToWebhooks = () => {
|
||||||
|
let type = GlobalSettingOptions.WEBHOOK;
|
||||||
|
switch (webhookData?.webhookType) {
|
||||||
|
case WebhookType.Msteams:
|
||||||
|
type = GlobalSettingOptions.MSTEAMS;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case WebhookType.Slack:
|
||||||
|
type = GlobalSettingOptions.SLACK;
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
history.push(
|
history.push(
|
||||||
getSettingPath(
|
`${getSettingPath(GlobalSettingsMenuCategory.INTEGRATIONS, type)}`
|
||||||
GlobalSettingsMenuCategory.INTEGRATIONS,
|
|
||||||
GlobalSettingOptions.WEBHOOK
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -92,6 +104,9 @@ const EditWebhookPage: FunctionComponent = () => {
|
|||||||
setStatus('initial');
|
setStatus('initial');
|
||||||
goToWebhooks();
|
goToWebhooks();
|
||||||
}, 500);
|
}, 500);
|
||||||
|
showSuccessToast(
|
||||||
|
jsonData['api-success-messages']['update-webhook-success']
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
throw jsonData['api-error-messages']['unexpected-error'];
|
throw jsonData['api-error-messages']['unexpected-error'];
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user