mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-11 16:31:57 +00:00
chore: feed and task improvements (#12567)
* chore: feed and task improvements * fix: unit test * fix: flaky test
This commit is contained in:
parent
91b8df6261
commit
31296f066e
@ -52,6 +52,7 @@ describe('Task flow should work', () => {
|
|||||||
verifyResponseStatusCode('@suggestApi', 200);
|
verifyResponseStatusCode('@suggestApi', 200);
|
||||||
|
|
||||||
cy.get(`[data-testid="assignee-option-${secondAssignee}"]`)
|
cy.get(`[data-testid="assignee-option-${secondAssignee}"]`)
|
||||||
|
.should('be.visible')
|
||||||
.trigger('mouseover')
|
.trigger('mouseover')
|
||||||
.trigger('click');
|
.trigger('click');
|
||||||
|
|
||||||
@ -94,6 +95,7 @@ describe('Task flow should work', () => {
|
|||||||
verifyResponseStatusCode('@suggestApi', 200);
|
verifyResponseStatusCode('@suggestApi', 200);
|
||||||
|
|
||||||
cy.get(`[data-testid="assignee-option-${assignee}"]`)
|
cy.get(`[data-testid="assignee-option-${assignee}"]`)
|
||||||
|
.should('be.visible')
|
||||||
.trigger('mouseover')
|
.trigger('mouseover')
|
||||||
.trigger('click');
|
.trigger('click');
|
||||||
|
|
||||||
@ -136,6 +138,7 @@ describe('Task flow should work', () => {
|
|||||||
verifyResponseStatusCode('@suggestApi', 200);
|
verifyResponseStatusCode('@suggestApi', 200);
|
||||||
|
|
||||||
cy.get(`[data-testid="assignee-option-${assignee}"]`)
|
cy.get(`[data-testid="assignee-option-${assignee}"]`)
|
||||||
|
.should('be.visible')
|
||||||
.trigger('mouseover')
|
.trigger('mouseover')
|
||||||
.trigger('click');
|
.trigger('click');
|
||||||
|
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import { ThreadType } from 'generated/api/feed/createThread';
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Thread } from '../../../generated/entity/feed/thread';
|
import { Thread } from '../../../generated/entity/feed/thread';
|
||||||
import { getEntityField, getEntityFQN } from '../../../utils/FeedUtils';
|
|
||||||
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
||||||
import FeedPanelBodyV1 from '../ActivityFeedPanel/FeedPanelBodyV1';
|
import FeedPanelBodyV1 from '../ActivityFeedPanel/FeedPanelBodyV1';
|
||||||
import FeedPanelHeader from '../ActivityFeedPanel/FeedPanelHeader';
|
import FeedPanelHeader from '../ActivityFeedPanel/FeedPanelHeader';
|
||||||
@ -41,10 +40,6 @@ const ActivityFeedDrawer: FC<ActivityFeedDrawerProps> = ({
|
|||||||
postFeed,
|
postFeed,
|
||||||
selectedThread,
|
selectedThread,
|
||||||
} = useActivityFeedProvider();
|
} = useActivityFeedProvider();
|
||||||
const entityField = selectedThread
|
|
||||||
? getEntityField(selectedThread.about)
|
|
||||||
: '';
|
|
||||||
const entityFQN = selectedThread ? getEntityFQN(selectedThread.about) : '';
|
|
||||||
|
|
||||||
const onSave = (message: string) => {
|
const onSave = (message: string) => {
|
||||||
postFeed(message, selectedThread?.id ?? '').catch(() => {
|
postFeed(message, selectedThread?.id ?? '').catch(() => {
|
||||||
@ -64,8 +59,7 @@ const ActivityFeedDrawer: FC<ActivityFeedDrawerProps> = ({
|
|||||||
) : (
|
) : (
|
||||||
<FeedPanelHeader
|
<FeedPanelHeader
|
||||||
className="p-x-md"
|
className="p-x-md"
|
||||||
entityFQN={entityFQN}
|
entityLink={selectedThread?.about ?? ''}
|
||||||
entityField={entityField as string}
|
|
||||||
threadType={selectedThread?.type ?? ThreadType.Conversation}
|
threadType={selectedThread?.type ?? ThreadType.Conversation}
|
||||||
onCancel={hideDrawer}
|
onCancel={hideDrawer}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -33,8 +33,7 @@ export interface ActivityFeedPanelProp extends HTMLAttributes<HTMLDivElement> {
|
|||||||
export interface FeedPanelHeaderProp
|
export interface FeedPanelHeaderProp
|
||||||
extends HTMLAttributes<HTMLHeadingElement>,
|
extends HTMLAttributes<HTMLHeadingElement>,
|
||||||
Pick<ActivityFeedPanelProp, 'onCancel'> {
|
Pick<ActivityFeedPanelProp, 'onCancel'> {
|
||||||
entityField: string;
|
entityLink: string;
|
||||||
entityFQN?: string;
|
|
||||||
noun?: string;
|
noun?: string;
|
||||||
threadType?: ThreadType;
|
threadType?: ThreadType;
|
||||||
onShowNewConversation?: (v: boolean) => void;
|
onShowNewConversation?: (v: boolean) => void;
|
||||||
|
|||||||
@ -19,9 +19,10 @@ import FeedPanelHeader from './FeedPanelHeader';
|
|||||||
|
|
||||||
const mockFeedPanelHeaderProp = {
|
const mockFeedPanelHeaderProp = {
|
||||||
onCancel: jest.fn(),
|
onCancel: jest.fn(),
|
||||||
entityField: 'description',
|
|
||||||
noun: 'Conversations',
|
noun: 'Conversations',
|
||||||
onShowNewConversation: jest.fn(),
|
onShowNewConversation: jest.fn(),
|
||||||
|
entityLink:
|
||||||
|
'<#E::table::sample_data.ecommerce_db.shopify.dim_address::description>',
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('Test FeedPanelHeader Component', () => {
|
describe('Test FeedPanelHeader Component', () => {
|
||||||
@ -85,8 +86,7 @@ describe('Test FeedPanelHeader Component', () => {
|
|||||||
const { container } = render(
|
const { container } = render(
|
||||||
<FeedPanelHeader
|
<FeedPanelHeader
|
||||||
{...mockFeedPanelHeaderProp}
|
{...mockFeedPanelHeaderProp}
|
||||||
entityFQN="x.y.z"
|
entityLink="<#E::testCase::sample_data.ecommerce_db.shopify.dim_address.address_id.unique_column_test>"
|
||||||
entityField=""
|
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
@ -95,15 +95,15 @@ describe('Test FeedPanelHeader Component', () => {
|
|||||||
|
|
||||||
const entityAttribute = await findByTestId(container, 'entity-attribute');
|
const entityAttribute = await findByTestId(container, 'entity-attribute');
|
||||||
|
|
||||||
expect(entityAttribute).toHaveTextContent(/x.y.z/i);
|
expect(entityAttribute).toHaveTextContent(
|
||||||
|
'ecommerce_db.shopify.dim_address'
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Should render noun according to the threadtype', async () => {
|
it('Should render noun according to the thread type', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<FeedPanelHeader
|
<FeedPanelHeader
|
||||||
{...mockFeedPanelHeaderProp}
|
{...mockFeedPanelHeaderProp}
|
||||||
entityFQN="x.y.z"
|
|
||||||
entityField=""
|
|
||||||
noun={undefined}
|
noun={undefined}
|
||||||
threadType={ThreadType.Announcement}
|
threadType={ThreadType.Announcement}
|
||||||
/>,
|
/>,
|
||||||
|
|||||||
@ -15,23 +15,31 @@ import { PlusOutlined } from '@ant-design/icons';
|
|||||||
import { Button, Tooltip } from 'antd';
|
import { Button, Tooltip } from 'antd';
|
||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { getEntityLink } from 'utils/TableUtils';
|
||||||
import {
|
import {
|
||||||
|
entityDisplayName,
|
||||||
|
getEntityField,
|
||||||
getEntityFieldDisplay,
|
getEntityFieldDisplay,
|
||||||
|
getEntityFQN,
|
||||||
|
getEntityType,
|
||||||
getFeedPanelHeaderText,
|
getFeedPanelHeaderText,
|
||||||
} from '../../../utils/FeedUtils';
|
} from '../../../utils/FeedUtils';
|
||||||
import { FeedPanelHeaderProp } from './ActivityFeedPanel.interface';
|
import { FeedPanelHeaderProp } from './ActivityFeedPanel.interface';
|
||||||
|
|
||||||
const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
|
const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
|
||||||
onCancel,
|
|
||||||
entityField,
|
|
||||||
className,
|
className,
|
||||||
|
entityLink,
|
||||||
noun,
|
noun,
|
||||||
onShowNewConversation,
|
onShowNewConversation,
|
||||||
threadType,
|
threadType,
|
||||||
entityFQN = '',
|
onCancel,
|
||||||
hideCloseIcon = false,
|
hideCloseIcon = false,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const entityType = getEntityType(entityLink);
|
||||||
|
const entityFQN = getEntityFQN(entityLink);
|
||||||
|
const entityField = getEntityField(entityLink);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className={className}>
|
<header className={className}>
|
||||||
@ -42,7 +50,16 @@ const FeedPanelHeader: FC<FeedPanelHeaderProp> = ({
|
|||||||
{t('label.on-lowercase')}{' '}
|
{t('label.on-lowercase')}{' '}
|
||||||
</span>
|
</span>
|
||||||
<span className="tw-heading" data-testid="entity-attribute">
|
<span className="tw-heading" data-testid="entity-attribute">
|
||||||
{entityField ? getEntityFieldDisplay(entityField) : entityFQN}
|
{entityField ? (
|
||||||
|
getEntityFieldDisplay(entityField)
|
||||||
|
) : (
|
||||||
|
<Link
|
||||||
|
className="break-all"
|
||||||
|
data-testid="entitylink"
|
||||||
|
to={getEntityLink(entityType, entityFQN)}>
|
||||||
|
<span>{entityDisplayName(entityType, entityFQN)}</span>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div className="d-flex">
|
<div className="d-flex">
|
||||||
|
|||||||
@ -38,7 +38,6 @@ import { useHistory, useParams } from 'react-router-dom';
|
|||||||
import { getAllFeeds, getFeedCount } from 'rest/feedsAPI';
|
import { getAllFeeds, getFeedCount } from 'rest/feedsAPI';
|
||||||
import { getCountBadge, getEntityDetailLink } from 'utils/CommonUtils';
|
import { getCountBadge, getEntityDetailLink } from 'utils/CommonUtils';
|
||||||
import { ENTITY_LINK_SEPARATOR, getEntityFeedLink } from 'utils/EntityUtils';
|
import { ENTITY_LINK_SEPARATOR, getEntityFeedLink } from 'utils/EntityUtils';
|
||||||
import { getEntityField } from 'utils/FeedUtils';
|
|
||||||
import '../../Widgets/FeedsWidget/feeds-widget.less';
|
import '../../Widgets/FeedsWidget/feeds-widget.less';
|
||||||
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
||||||
import ActivityFeedListV1 from '../ActivityFeedList/ActivityFeedListV1.component';
|
import ActivityFeedListV1 from '../ActivityFeedList/ActivityFeedListV1.component';
|
||||||
@ -241,10 +240,6 @@ export const ActivityFeedTab = ({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const entityField = selectedThread
|
|
||||||
? getEntityField(selectedThread.about)
|
|
||||||
: '';
|
|
||||||
|
|
||||||
const threads = useMemo(() => {
|
const threads = useMemo(() => {
|
||||||
if (activeTab === ActivityFeedTabs.TASKS) {
|
if (activeTab === ActivityFeedTabs.TASKS) {
|
||||||
return entityThread.filter(
|
return entityThread.filter(
|
||||||
@ -407,8 +402,7 @@ export const ActivityFeedTab = ({
|
|||||||
<FeedPanelHeader
|
<FeedPanelHeader
|
||||||
hideCloseIcon
|
hideCloseIcon
|
||||||
className="p-x-md"
|
className="p-x-md"
|
||||||
entityFQN={fqn}
|
entityLink={selectedThread.about}
|
||||||
entityField={entityField as string}
|
|
||||||
threadType={selectedThread?.type ?? ThreadType.Conversation}
|
threadType={selectedThread?.type ?? ThreadType.Conversation}
|
||||||
onCancel={noop}
|
onCancel={noop}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -31,7 +31,6 @@ import {
|
|||||||
} from '../../../generated/entity/feed/thread';
|
} from '../../../generated/entity/feed/thread';
|
||||||
import { Paging } from '../../../generated/type/paging';
|
import { Paging } from '../../../generated/type/paging';
|
||||||
import { useElementInView } from '../../../hooks/useElementInView';
|
import { useElementInView } from '../../../hooks/useElementInView';
|
||||||
import { getEntityField } from '../../../utils/FeedUtils';
|
|
||||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||||
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
|
||||||
import Loader from '../../Loader/Loader';
|
import Loader from '../../Loader/Loader';
|
||||||
@ -139,8 +138,6 @@ const ActivityThreadPanelBody: FC<ActivityThreadPanelBodyProp> = ({
|
|||||||
setConfirmationState(data);
|
setConfirmationState(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const entityField = getEntityField(threadLink);
|
|
||||||
|
|
||||||
const onShowNewConversation = (value: boolean) => {
|
const onShowNewConversation = (value: boolean) => {
|
||||||
setShowNewConversation(value);
|
setShowNewConversation(value);
|
||||||
};
|
};
|
||||||
@ -239,7 +236,7 @@ const ActivityThreadPanelBody: FC<ActivityThreadPanelBodyProp> = ({
|
|||||||
{showHeader && isConversationType && (
|
{showHeader && isConversationType && (
|
||||||
<FeedPanelHeader
|
<FeedPanelHeader
|
||||||
className="tw-px-4 tw-shadow-sm"
|
className="tw-px-4 tw-shadow-sm"
|
||||||
entityField={entityField as string}
|
entityLink={selectedThread?.about ?? ''}
|
||||||
noun={
|
noun={
|
||||||
isConversationType
|
isConversationType
|
||||||
? t('label.conversation-plural')
|
? t('label.conversation-plural')
|
||||||
|
|||||||
@ -211,9 +211,8 @@ export const TaskTab = ({
|
|||||||
*
|
*
|
||||||
* @returns True if has access otherwise false
|
* @returns True if has access otherwise false
|
||||||
*/
|
*/
|
||||||
const hasEditAccess = () => isAdminUser || isAssignee || isOwner;
|
const hasEditAccess =
|
||||||
|
isAdminUser || isAssignee || isOwner || Boolean(isPartOfAssigneeTeam);
|
||||||
const hasTaskUpdateAccess = () => hasEditAccess() || isPartOfAssigneeTeam;
|
|
||||||
|
|
||||||
const onSave = (message: string) => {
|
const onSave = (message: string) => {
|
||||||
postFeed(message, taskThread?.id ?? '').catch(() => {
|
postFeed(message, taskThread?.id ?? '').catch(() => {
|
||||||
@ -266,10 +265,10 @@ export const TaskTab = ({
|
|||||||
className="m-t-sm items-end w-full"
|
className="m-t-sm items-end w-full"
|
||||||
data-testid="task-cta-buttons"
|
data-testid="task-cta-buttons"
|
||||||
size="small">
|
size="small">
|
||||||
{(isCreator || hasTaskUpdateAccess()) && (
|
{(isCreator || hasEditAccess) && (
|
||||||
<Button onClick={onTaskReject}>{t('label.close')}</Button>
|
<Button onClick={onTaskReject}>{t('label.close')}</Button>
|
||||||
)}
|
)}
|
||||||
{hasTaskUpdateAccess() ? (
|
{hasEditAccess ? (
|
||||||
<>
|
<>
|
||||||
{['RequestDescription', 'RequestTag'].includes(
|
{['RequestDescription', 'RequestTag'].includes(
|
||||||
taskDetails?.type ?? ''
|
taskDetails?.type ?? ''
|
||||||
@ -428,7 +427,7 @@ export const TaskTab = ({
|
|||||||
profileWidth="24"
|
profileWidth="24"
|
||||||
showUserName={false}
|
showUserName={false}
|
||||||
/>
|
/>
|
||||||
{(isCreator || hasTaskUpdateAccess()) && !isTaskClosed ? (
|
{(isCreator || hasEditAccess) && !isTaskClosed ? (
|
||||||
<Button
|
<Button
|
||||||
className="flex-center p-0"
|
className="flex-center p-0"
|
||||||
data-testid="edit-assignees"
|
data-testid="edit-assignees"
|
||||||
@ -457,7 +456,7 @@ export const TaskTab = ({
|
|||||||
<Col span={24}>
|
<Col span={24}>
|
||||||
{isTaskDescription && (
|
{isTaskDescription && (
|
||||||
<DescriptionTask
|
<DescriptionTask
|
||||||
hasEditAccess={hasEditAccess()}
|
hasEditAccess={hasEditAccess}
|
||||||
isTaskActionEdit={false}
|
isTaskActionEdit={false}
|
||||||
taskThread={taskThread}
|
taskThread={taskThread}
|
||||||
onChange={(value) => form.setFieldValue('description', value)}
|
onChange={(value) => form.setFieldValue('description', value)}
|
||||||
@ -466,7 +465,7 @@ export const TaskTab = ({
|
|||||||
|
|
||||||
{isTaskTags && (
|
{isTaskTags && (
|
||||||
<TagsTask
|
<TagsTask
|
||||||
hasEditAccess={hasEditAccess()}
|
hasEditAccess={hasEditAccess}
|
||||||
isTaskActionEdit={false}
|
isTaskActionEdit={false}
|
||||||
task={taskDetails}
|
task={taskDetails}
|
||||||
onChange={(value) => form.setFieldValue('updatedTags', value)}
|
onChange={(value) => form.setFieldValue('updatedTags', value)}
|
||||||
@ -522,7 +521,7 @@ export const TaskTab = ({
|
|||||||
trigger="onChange">
|
trigger="onChange">
|
||||||
<TagsTask
|
<TagsTask
|
||||||
isTaskActionEdit
|
isTaskActionEdit
|
||||||
hasEditAccess={hasEditAccess()}
|
hasEditAccess={hasEditAccess}
|
||||||
task={taskDetails}
|
task={taskDetails}
|
||||||
onChange={(value) => form.setFieldValue('updatedTags', value)}
|
onChange={(value) => form.setFieldValue('updatedTags', value)}
|
||||||
/>
|
/>
|
||||||
@ -543,7 +542,7 @@ export const TaskTab = ({
|
|||||||
trigger="onTextChange">
|
trigger="onTextChange">
|
||||||
<DescriptionTask
|
<DescriptionTask
|
||||||
isTaskActionEdit
|
isTaskActionEdit
|
||||||
hasEditAccess={hasEditAccess()}
|
hasEditAccess={hasEditAccess}
|
||||||
taskThread={taskThread}
|
taskThread={taskThread}
|
||||||
onChange={(value) => form.setFieldValue('description', value)}
|
onChange={(value) => form.setFieldValue('description', value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -483,7 +483,7 @@ export const prepareFeedLink = (entityType: string, entityFQN: string) => {
|
|||||||
|
|
||||||
export const entityDisplayName = (entityType: string, entityFQN: string) => {
|
export const entityDisplayName = (entityType: string, entityFQN: string) => {
|
||||||
let displayName;
|
let displayName;
|
||||||
if (entityType === EntityType.TABLE) {
|
if (entityType === EntityType.TABLE || entityType === EntityType.TEST_CASE) {
|
||||||
displayName = getPartialNameFromTableFQN(
|
displayName = getPartialNameFromTableFQN(
|
||||||
entityFQN,
|
entityFQN,
|
||||||
[FqnPart.Database, FqnPart.Schema, FqnPart.Table],
|
[FqnPart.Database, FqnPart.Schema, FqnPart.Table],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user