mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-06 04:26:57 +00:00
chore(ui): cleanup activity feed from settings page (#9362)
This commit is contained in:
parent
1430e7e9c0
commit
17344f2c10
@ -1,19 +0,0 @@
|
||||
.activity-feed-settings-tree {
|
||||
.ant-tree-list-holder-inner {
|
||||
margin-left: -16px;
|
||||
}
|
||||
.ant-tree-switcher.ant-tree-switcher_open {
|
||||
visibility: hidden;
|
||||
}
|
||||
.ant-tree-node-content-wrapper {
|
||||
padding: 0;
|
||||
}
|
||||
.ant-divider.ant-divider-horizontal {
|
||||
margin: 16px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.settings-page-card-container {
|
||||
box-shadow: 1px 1px 3px 0px rgb(0 0 0 / 12%);
|
||||
border: 1px #dde3ea solid;
|
||||
}
|
||||
@ -1,26 +0,0 @@
|
||||
import { render } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import ActivityFeedSettingsPage from './ActivityFeedSettingsPage';
|
||||
|
||||
jest.mock('../../axiosAPIs/eventFiltersAPI', () => ({
|
||||
getActivityFeedEventFilters: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve()),
|
||||
resetAllFilters: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
updateFilters: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||
}));
|
||||
|
||||
jest.mock('../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
showSuccessToast: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Test ActivityFeedSettingsPage', () => {
|
||||
it('should render properly', async () => {
|
||||
const { findByText } = render(<ActivityFeedSettingsPage />);
|
||||
|
||||
expect(
|
||||
await findByText(/No activity feed settings available/)
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -1,294 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Col,
|
||||
Divider,
|
||||
Row,
|
||||
Space,
|
||||
Tooltip,
|
||||
Tree,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import { cloneDeep, isUndefined, map, startCase } from 'lodash';
|
||||
import React, { Key, useEffect, useState } from 'react';
|
||||
import {
|
||||
ActivityFeedSettings,
|
||||
getActivityFeedEventFilters,
|
||||
resetAllFilters,
|
||||
updateFilters,
|
||||
} from '../../axiosAPIs/eventFiltersAPI';
|
||||
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface';
|
||||
import { TERM_ALL } from '../../constants/constants';
|
||||
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||
import { Operation } from '../../generated/entity/policies/policy';
|
||||
import {
|
||||
EventFilter,
|
||||
Filters,
|
||||
SettingType,
|
||||
} from '../../generated/settings/settings';
|
||||
import jsonData from '../../jsons/en';
|
||||
import { checkPermission } from '../../utils/PermissionsUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import './ActivityFeedSettingsPage.style.less';
|
||||
import { getEventFilterFromTree } from './ActivityFeedSettingsPage.utils';
|
||||
|
||||
const ActivityFeedSettingsPage: React.FC = () => {
|
||||
const [eventFilters, setEventFilters] = useState<EventFilter[]>();
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [selectedKey, setSelectedKey] = useState<string>();
|
||||
const [checkedKeys, setCheckedKeys] = useState<string[]>([]);
|
||||
const [updatedTree, setUpdatedTree] = useState<Record<string, string[]>>();
|
||||
|
||||
const fetchEventFilters = async () => {
|
||||
try {
|
||||
const data = await getActivityFeedEventFilters();
|
||||
setEventFilters(data);
|
||||
} catch (error) {
|
||||
const err = error as AxiosError;
|
||||
showErrorToast(err, jsonData['api-error-messages']['fetch-settings']);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const createActivityFeed = async (req: ActivityFeedSettings) => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await updateFilters(req);
|
||||
const filteredData = data.config_value?.filter(
|
||||
({ entityType }) => entityType !== TERM_ALL
|
||||
);
|
||||
|
||||
setEventFilters(filteredData);
|
||||
showSuccessToast(
|
||||
jsonData['api-success-messages']['add-settings-success']
|
||||
);
|
||||
} catch {
|
||||
showErrorToast(jsonData['api-error-messages']['add-settings-error']);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const generateTreeData = (entityType: string, data?: Filters[]) => {
|
||||
return [
|
||||
{
|
||||
key: entityType,
|
||||
title: (
|
||||
<Typography.Text strong>{startCase(entityType)}</Typography.Text>
|
||||
),
|
||||
data: true,
|
||||
children:
|
||||
data?.map(({ eventType, include, exclude }) => {
|
||||
const key = `${entityType}-${eventType}` as string;
|
||||
|
||||
return {
|
||||
key: key,
|
||||
title: startCase(eventType),
|
||||
data: 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 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 handleTreeCheckChange = (keys: Key[], entityType: string) => {
|
||||
const key = String(keys[0]).split('-')[0];
|
||||
setCheckedKeys(keys as string[]);
|
||||
const updateData = cloneDeep(updatedTree || {});
|
||||
|
||||
updateData[entityType] = keys as string[];
|
||||
|
||||
setUpdatedTree(updateData);
|
||||
setSelectedKey(key);
|
||||
};
|
||||
|
||||
const onSave = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (!isUndefined(updatedTree) && selectedKey && eventFilters) {
|
||||
const deepClonedTree = cloneDeep(updatedTree);
|
||||
|
||||
const data = {
|
||||
config_type: SettingType.ActivityFeedFilterSetting,
|
||||
config_value: getEventFilterFromTree(deepClonedTree, eventFilters),
|
||||
} as ActivityFeedSettings;
|
||||
|
||||
createActivityFeed(data);
|
||||
setUpdatedTree(undefined);
|
||||
setSelectedKey(undefined);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchEventFilters();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const checkKeys = getCheckedKeys(eventFilters as EventFilter[]);
|
||||
|
||||
setCheckedKeys(checkKeys);
|
||||
}, [eventFilters, updatedTree, selectedKey]);
|
||||
|
||||
const { permissions } = usePermissionProvider();
|
||||
|
||||
const editPermission = checkPermission(
|
||||
Operation.EditAll,
|
||||
ResourceEntity.FEED,
|
||||
permissions
|
||||
);
|
||||
const createPermission = checkPermission(
|
||||
Operation.Create,
|
||||
ResourceEntity.FEED,
|
||||
permissions
|
||||
);
|
||||
|
||||
const handleResetClick = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const data = await resetAllFilters();
|
||||
const filteredData = data.config_value?.filter(
|
||||
({ entityType }) => entityType !== TERM_ALL
|
||||
);
|
||||
|
||||
setEventFilters(filteredData);
|
||||
showSuccessToast(
|
||||
jsonData['api-success-messages']['add-settings-success']
|
||||
);
|
||||
} catch {
|
||||
showErrorToast(jsonData['api-error-messages']['add-settings-error']);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return loading ? (
|
||||
<Col span={24}>
|
||||
<Loader />
|
||||
</Col>
|
||||
) : (
|
||||
<>
|
||||
{eventFilters ? (
|
||||
<>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Typography.Title
|
||||
level={5}
|
||||
style={{ margin: 0 }}
|
||||
type="secondary">
|
||||
Activity Feed
|
||||
</Typography.Title>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Card className="settings-page-card-container" size="small">
|
||||
{eventFilters &&
|
||||
map(eventFilters, ({ entityType, filters }, index) => (
|
||||
<>
|
||||
{entityType !== TERM_ALL ? (
|
||||
<div
|
||||
className="activity-feed-settings-tree"
|
||||
key={entityType}>
|
||||
<Tree
|
||||
checkable
|
||||
defaultExpandAll
|
||||
defaultCheckedKeys={checkedKeys}
|
||||
icon={null}
|
||||
key={entityType}
|
||||
treeData={generateTreeData(entityType, filters)}
|
||||
onCheck={(keys) =>
|
||||
handleTreeCheckChange(keys as Key[], entityType)
|
||||
}
|
||||
/>
|
||||
{index !== eventFilters?.length - 1 && <Divider />}
|
||||
</div>
|
||||
) : null}
|
||||
</>
|
||||
))}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col>
|
||||
<Space direction="horizontal" size={16}>
|
||||
<Tooltip
|
||||
title={
|
||||
editPermission || createPermission
|
||||
? ''
|
||||
: NO_PERMISSION_FOR_ACTION
|
||||
}>
|
||||
<Button
|
||||
disabled={!(editPermission || createPermission)}
|
||||
type="primary"
|
||||
onClick={onSave}>
|
||||
Save
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={createPermission ? '' : NO_PERMISSION_FOR_ACTION}>
|
||||
<Button
|
||||
disabled={!createPermission}
|
||||
type="text"
|
||||
onClick={handleResetClick}>
|
||||
Reset to default
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Col>
|
||||
<Col span={24} />
|
||||
<Col span={24} />
|
||||
</Row>
|
||||
</>
|
||||
) : (
|
||||
<ErrorPlaceHolder>
|
||||
<Typography.Text>No activity feed settings available</Typography.Text>
|
||||
</ErrorPlaceHolder>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ActivityFeedSettingsPage;
|
||||
@ -1,54 +0,0 @@
|
||||
import { xor } from 'lodash';
|
||||
import { EventFilter, EventType } from '../../generated/settings/settings';
|
||||
|
||||
export const getEventFilterFromTree = (
|
||||
updatedTree: Record<string, string[]>,
|
||||
eventFilters: EventFilter[]
|
||||
): EventFilter[] => {
|
||||
return eventFilters.map((eventFilter) => ({
|
||||
...eventFilter,
|
||||
filters: eventFilter.filters?.map((filter) => {
|
||||
let includeList = filter.include;
|
||||
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]) {
|
||||
// Split the value to get list of [eventType, filter, event]
|
||||
const temp = updatedTree[eventFilter.entityType].map((key) =>
|
||||
key.split('-')
|
||||
);
|
||||
|
||||
// grab the list of current eventType
|
||||
const eventList = temp.filter((f) => f[1] === filter.eventType);
|
||||
|
||||
if (eventList.length > 0) {
|
||||
if (filter.eventType === EventType.EntityUpdated) {
|
||||
// derive include list based on selected events
|
||||
includeList = eventList.map((f) => f[2]).filter(Boolean);
|
||||
|
||||
// derive the exclude list by symmetric difference
|
||||
excludeList = xor(mergedList, includeList);
|
||||
} else {
|
||||
includeList = ['all'];
|
||||
excludeList = [];
|
||||
}
|
||||
} else {
|
||||
excludeList = [...(includeList ?? []), ...(excludeList ?? [])];
|
||||
includeList = [];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...filter,
|
||||
include: includeList,
|
||||
exclude: excludeList,
|
||||
};
|
||||
}),
|
||||
}));
|
||||
};
|
||||
@ -20,7 +20,6 @@ import {
|
||||
GlobalSettingsMenuCategory,
|
||||
} from '../constants/GlobalSettings.constants';
|
||||
import { TeamType } from '../generated/entity/teams/team';
|
||||
import ActivityFeedSettingsPage from '../pages/ActivityFeedSettingsPage/ActivityFeedSettingsPage';
|
||||
import TeamsPage from '../pages/teams/TeamsPage';
|
||||
import { userPermissions } from '../utils/PermissionsUtils';
|
||||
import {
|
||||
@ -170,21 +169,6 @@ const GlobalSettingRouter = () => {
|
||||
)}
|
||||
/>
|
||||
|
||||
<AdminProtectedRoute
|
||||
exact
|
||||
// Currently we don't have any permission related to ActivityFeed settings page
|
||||
// update below once we have it
|
||||
component={ActivityFeedSettingsPage}
|
||||
hasPermission={userPermissions.hasViewPermissions(
|
||||
ResourceEntity.FEED,
|
||||
permissions
|
||||
)}
|
||||
path={getSettingPath(
|
||||
GlobalSettingsMenuCategory.COLLABORATION,
|
||||
GlobalSettingOptions.ACTIVITY_FEED
|
||||
)}
|
||||
/>
|
||||
|
||||
<AdminProtectedRoute
|
||||
exact
|
||||
component={ElasticSearchIndexPage}
|
||||
|
||||
@ -15,7 +15,6 @@ import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||
import { camelCase } from 'lodash';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { ReactComponent as AdminIcon } from '../../src/assets/svg/admin.svg';
|
||||
import { ReactComponent as AllActivityIcon } from '../../src/assets/svg/all-activity.svg';
|
||||
import { ReactComponent as BotIcon } from '../../src/assets/svg/bot-profile.svg';
|
||||
import { ReactComponent as DashboardIcon } from '../../src/assets/svg/dashboard-grey.svg';
|
||||
import { ReactComponent as ElasticSearchIcon } from '../../src/assets/svg/elasticsearch.svg';
|
||||
@ -153,19 +152,6 @@ export const getGlobalSettingsMenuWithPermission = (
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'Collaboration',
|
||||
items: [
|
||||
{
|
||||
label: 'Activity Feed',
|
||||
isProtected: userPermissions.hasViewPermissions(
|
||||
ResourceEntity.FEED,
|
||||
permissions
|
||||
),
|
||||
icon: <AllActivityIcon className="side-panel-icons" />,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: 'Custom Attributes',
|
||||
items: [
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user