mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-12 09:18:20 +00:00
Revamp ✨ : Request/update description should be created as a task (#5637)
* Revamp ✨ : Request/update description should be created as a task #5321 * Fix Feed header issue * Add support for profile picture for task assignees * Add support for title input on request/update task * Update component based on task type * Update editor height * Fix unit test * Minor fixes
This commit is contained in:
parent
7ad97d8fed
commit
07d882ee50
@ -14,9 +14,10 @@
|
||||
import classNames from 'classnames';
|
||||
import { isUndefined, toString } from 'lodash';
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import AppState from '../../../../AppState';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../../../constants/char.constants';
|
||||
import { getUserPath } from '../../../../constants/constants';
|
||||
import {
|
||||
EntityType,
|
||||
FqnPart,
|
||||
@ -34,6 +35,7 @@ import {
|
||||
import { getEntityLink } from '../../../../utils/TableUtils';
|
||||
import { getTaskDetailPath } from '../../../../utils/TasksUtils';
|
||||
import { getDayTimeByTimeStamp } from '../../../../utils/TimeUtils';
|
||||
import Ellipses from '../../../common/Ellipses/Ellipses';
|
||||
import EntityPopOverCard from '../../../common/PopOverCard/EntityPopOverCard';
|
||||
import UserPopOverCard from '../../../common/PopOverCard/UserPopOverCard';
|
||||
import { FeedHeaderProp } from '../ActivityFeedCard.interface';
|
||||
@ -50,6 +52,10 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
|
||||
feedType,
|
||||
taskDetails,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const onTitleClickHandler = (name: string) => {
|
||||
history.push(getUserPath(name));
|
||||
};
|
||||
const entityDisplayName = () => {
|
||||
let displayName;
|
||||
if (entityType === EntityType.TABLE) {
|
||||
@ -110,6 +116,7 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
|
||||
const getFeedLinkElement = () => {
|
||||
if (!isUndefined(entityFQN) && !isUndefined(entityType)) {
|
||||
return (
|
||||
<Ellipses rows={1}>
|
||||
<span className="tw-pl-1 tw-font-normal" data-testid="headerText">
|
||||
{getFeedAction(feedType)}{' '}
|
||||
{isEntityFeed ? (
|
||||
@ -117,8 +124,6 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
|
||||
{getEntityFieldDisplay(entityField)}
|
||||
</span>
|
||||
) : (
|
||||
<Fragment>
|
||||
{feedType === ThreadType.Conversation ? (
|
||||
<Fragment>
|
||||
<span data-testid="entityType">{entityType} </span>
|
||||
<Link data-testid="entitylink" to={prepareFeedLink()}>
|
||||
@ -133,20 +138,55 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
|
||||
</button>
|
||||
</Link>
|
||||
</Fragment>
|
||||
) : (
|
||||
)}
|
||||
</span>
|
||||
</Ellipses>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getTaskLinkElement = () => {
|
||||
if (!isUndefined(entityFQN) && !isUndefined(entityType)) {
|
||||
return (
|
||||
<div className="tw-flex tw-flex-wrap">
|
||||
<span className="tw-px-1">created a task</span>
|
||||
<Link
|
||||
data-testid="tasklink"
|
||||
to={getTaskDetailPath(toString(taskDetails?.id)).pathname}>
|
||||
<button
|
||||
className="tw-text-info"
|
||||
disabled={AppState.isTourOpen}>
|
||||
<button className="tw-text-info" disabled={AppState.isTourOpen}>
|
||||
{`#${taskDetails?.id}`} <span>{taskDetails?.type}</span>
|
||||
</button>
|
||||
</Link>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
<span className="tw-px-1">for</span>
|
||||
{isEntityFeed ? (
|
||||
<span className="tw-heading" data-testid="headerText-entityField">
|
||||
{getEntityFieldDisplay(entityField)}
|
||||
</span>
|
||||
) : (
|
||||
<span className="tw-flex">
|
||||
<span className="tw-pr-1">{entityType}</span>
|
||||
|
||||
<EntityPopOverCard entityFQN={entityFQN} entityType={entityType}>
|
||||
<Link data-testid="entitylink" to={prepareFeedLink()}>
|
||||
<button disabled={AppState.isTourOpen}>
|
||||
<Ellipses
|
||||
className="tw-w-28"
|
||||
rows={1}
|
||||
style={{
|
||||
color: 'rgb(24, 144, 255)',
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'none',
|
||||
}}>
|
||||
{entityDisplayName()}
|
||||
</Ellipses>
|
||||
</button>
|
||||
</Link>
|
||||
</EntityPopOverCard>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
@ -157,10 +197,16 @@ const FeedCardHeader: FC<FeedHeaderProp> = ({
|
||||
<div className={classNames('tw-flex', className)}>
|
||||
<div className="tw-flex tw-m-0 tw-pl-2 tw-leading-4">
|
||||
<UserPopOverCard userName={createdBy}>
|
||||
<span className="thread-author tw-cursor-pointer">{createdBy}</span>
|
||||
<span
|
||||
className="thread-author tw-cursor-pointer"
|
||||
onClick={() => onTitleClickHandler(createdBy)}>
|
||||
{createdBy}
|
||||
</span>
|
||||
</UserPopOverCard>
|
||||
|
||||
{getFeedLinkElement()}
|
||||
{feedType === ThreadType.Conversation
|
||||
? getFeedLinkElement()
|
||||
: getTaskLinkElement()}
|
||||
<span
|
||||
className="tw-text-grey-muted tw-pl-2 tw-text-xs tw--mb-0.5"
|
||||
data-testid="timestamp">
|
||||
|
@ -12,9 +12,12 @@
|
||||
*/
|
||||
|
||||
import { Card } from 'antd';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { Post, ThreadType } from '../../../generated/entity/feed/thread';
|
||||
import { getEntityName } from '../../../utils/CommonUtils';
|
||||
import UserPopOverCard from '../../common/PopOverCard/UserPopOverCard';
|
||||
import ProfilePicture from '../../common/ProfilePicture/ProfilePicture';
|
||||
import { leftPanelAntCardStyle } from '../../containers/PageLayout';
|
||||
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
|
||||
import FeedCardFooter from '../ActivityFeedCard/FeedCardFooter/FeedCardFooter';
|
||||
@ -150,12 +153,26 @@ const FeedListBody: FC<FeedListBodyProp> = ({
|
||||
) : null}
|
||||
</div>
|
||||
{feed.task && (
|
||||
<div className="tw-border-t tw-border-main tw-py-1">
|
||||
<div className="tw-border-t tw-border-main tw-py-1 tw-flex">
|
||||
<span className="tw-text-grey-muted">Assignees: </span>
|
||||
<span className="tw-ml-0.5 tw-align-middle">
|
||||
{feed.task.assignees
|
||||
.map((assignee) => getEntityName(assignee))
|
||||
.join(', ')}
|
||||
<span className="tw-ml-0.5 tw-align-middle tw-grid tw-grid-cols-4">
|
||||
{feed.task.assignees.map((assignee) => (
|
||||
<UserPopOverCard
|
||||
key={uniqueId()}
|
||||
type={assignee.type}
|
||||
userName={assignee.name || ''}>
|
||||
<span className="tw-flex tw-m-1.5 tw-mt-0">
|
||||
<ProfilePicture
|
||||
id=""
|
||||
name={getEntityName(assignee)}
|
||||
width="20"
|
||||
/>
|
||||
<span className="tw-ml-1">
|
||||
{getEntityName(assignee)}
|
||||
</span>
|
||||
</span>
|
||||
</UserPopOverCard>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
@ -11,10 +11,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Card } from 'antd';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { FC, Fragment } from 'react';
|
||||
import { Post, ThreadType } from '../../../generated/entity/feed/thread';
|
||||
import { getEntityName } from '../../../utils/CommonUtils';
|
||||
import { getFeedListWithRelativeDays } from '../../../utils/FeedUtils';
|
||||
import UserPopOverCard from '../../common/PopOverCard/UserPopOverCard';
|
||||
import ProfilePicture from '../../common/ProfilePicture/ProfilePicture';
|
||||
import { leftPanelAntCardStyle } from '../../containers/PageLayout';
|
||||
import ActivityFeedCard from '../ActivityFeedCard/ActivityFeedCard';
|
||||
import FeedCardFooter from '../ActivityFeedCard/FeedCardFooter/FeedCardFooter';
|
||||
@ -139,10 +142,24 @@ const ActivityThreadList: FC<ActivityThreadListProp> = ({
|
||||
<span className="tw-text-grey-muted">
|
||||
Assignees:{' '}
|
||||
</span>
|
||||
<span className="tw-ml-0.5 tw-align-middle">
|
||||
{thread.task.assignees
|
||||
.map((assignee) => getEntityName(assignee))
|
||||
.join(', ')}
|
||||
<span className="tw-ml-0.5 tw-align-middle tw-grid tw-grid-cols-3">
|
||||
{thread.task.assignees.map((assignee) => (
|
||||
<UserPopOverCard
|
||||
key={uniqueId()}
|
||||
type={assignee.type}
|
||||
userName={assignee.name || ''}>
|
||||
<span className="tw-flex tw-m-1.5 tw-mt-0">
|
||||
<ProfilePicture
|
||||
id=""
|
||||
name={getEntityName(assignee)}
|
||||
width="20"
|
||||
/>
|
||||
<span className="tw-ml-1">
|
||||
{getEntityName(assignee)}
|
||||
</span>
|
||||
</span>
|
||||
</UserPopOverCard>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
@ -41,6 +41,7 @@ export interface ActivityThreadPanelBodyProp
|
||||
> {
|
||||
threadType: ThreadType;
|
||||
showHeader?: boolean;
|
||||
onTabChange?: (key: string) => void;
|
||||
}
|
||||
|
||||
export interface ActivityThreadListProp
|
||||
|
@ -11,7 +11,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findByTestId, findByText, render } from '@testing-library/react';
|
||||
import {
|
||||
findAllByText,
|
||||
findByTestId,
|
||||
findByText,
|
||||
render,
|
||||
} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
@ -73,10 +78,13 @@ describe('Test ActivityThreadPanel Component', () => {
|
||||
);
|
||||
const panelOverlay = await findByText(container, /FeedPanelOverlay/i);
|
||||
|
||||
const panelThreadList = await findByText(container, /ActivityThreadList/i);
|
||||
const panelThreadList = await findAllByText(
|
||||
container,
|
||||
/ActivityThreadList/i
|
||||
);
|
||||
|
||||
expect(panelOverlay).toBeInTheDocument();
|
||||
expect(panelThreadList).toBeInTheDocument();
|
||||
expect(panelThreadList).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('Should create an observer if IntersectionObserver is available', async () => {
|
||||
|
@ -69,6 +69,7 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
|
||||
threadType={ThreadType.Task}
|
||||
updateThreadHandler={updateThreadHandler}
|
||||
onCancel={onCancel}
|
||||
onTabChange={onTabChange}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane key="2" tab="Conversations">
|
||||
@ -80,6 +81,7 @@ const ActivityThreadPanel: FC<ActivityThreadPanelProp> = ({
|
||||
threadType={ThreadType.Conversation}
|
||||
updateThreadHandler={updateThreadHandler}
|
||||
onCancel={onCancel}
|
||||
onTabChange={onTabChange}
|
||||
/>
|
||||
</TabPane>
|
||||
</Tabs>
|
||||
|
@ -14,7 +14,7 @@
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { isUndefined } from 'lodash';
|
||||
import { isEqual, isUndefined } from 'lodash';
|
||||
import React, { FC, Fragment, RefObject, useEffect, useState } from 'react';
|
||||
import AppState from '../../../AppState';
|
||||
import { getAllFeeds } from '../../../axiosAPIs/feedsAPI';
|
||||
@ -22,10 +22,12 @@ import { confirmStateInitialValue } from '../../../constants/feed.constants';
|
||||
import { observerOptions } from '../../../constants/Mydata.constants';
|
||||
import { Thread, ThreadType } from '../../../generated/entity/feed/thread';
|
||||
import { Paging } from '../../../generated/type/paging';
|
||||
import { useAfterMount } from '../../../hooks/useAfterMount';
|
||||
import { useInfiniteScroll } from '../../../hooks/useInfiniteScroll';
|
||||
import jsonData from '../../../jsons/en';
|
||||
import { getEntityField } from '../../../utils/FeedUtils';
|
||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||
import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import Loader from '../../Loader/Loader';
|
||||
import { ConfirmState } from '../ActivityFeedCard/ActivityFeedCard.interface';
|
||||
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
|
||||
@ -45,6 +47,7 @@ const ActivityThreadPanelBody: FC<ActivityThreadPanelBodyProp> = ({
|
||||
className,
|
||||
showHeader = true,
|
||||
threadType,
|
||||
onTabChange,
|
||||
}) => {
|
||||
const [threads, setThreads] = useState<Thread[]>([]);
|
||||
const [selectedThread, setSelectedThread] = useState<Thread>();
|
||||
@ -195,6 +198,12 @@ const ActivityThreadPanelBody: FC<ActivityThreadPanelBodyProp> = ({
|
||||
fetchMoreThread(isInView as boolean, paging, isThreadLoading);
|
||||
}, [paging, isThreadLoading, isInView]);
|
||||
|
||||
useAfterMount(() => {
|
||||
if (threadType === ThreadType.Task && !isThreadLoading) {
|
||||
isEqual(threads.length, 0) && onTabChange && onTabChange('2');
|
||||
}
|
||||
}, [threads, isThreadLoading]);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div id="thread-panel-body">
|
||||
@ -233,6 +242,9 @@ const ActivityThreadPanelBody: FC<ActivityThreadPanelBodyProp> = ({
|
||||
<Fragment>
|
||||
{showNewConversation || threads.length === 0 ? (
|
||||
<div className="tw-pt-2">
|
||||
<Fragment>
|
||||
{threadType === ThreadType.Conversation ? (
|
||||
<Fragment>
|
||||
<p className="tw-ml-9 tw-mr-2 tw-my-2">
|
||||
You are starting a new conversation
|
||||
</p>
|
||||
@ -242,6 +254,11 @@ const ActivityThreadPanelBody: FC<ActivityThreadPanelBodyProp> = ({
|
||||
placeHolder="Enter a message"
|
||||
onSave={onPostThread}
|
||||
/>
|
||||
</Fragment>
|
||||
) : (
|
||||
<ErrorPlaceHolder>No tasks yet</ErrorPlaceHolder>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
) : null}
|
||||
<ActivityThreadList
|
||||
|
@ -16,8 +16,13 @@ import React from 'react';
|
||||
const TaskBadge = () => {
|
||||
return (
|
||||
<span
|
||||
className="tw-text-grey-muted tw-border tw-border-main tw-rounded tw-px-2 tw-absolute tw-left-4 tw--top-3"
|
||||
style={{ background: '#DCE3EC' }}>
|
||||
className="tw-rounded tw-px-2 tw-absolute tw-left-4 tw--top-3"
|
||||
style={{
|
||||
color: '#485056',
|
||||
background: '#EBF5FF',
|
||||
boxShadow: '0px 1px 2px rgba(0, 0, 0, 0.06)',
|
||||
borderRadius: '4px',
|
||||
}}>
|
||||
Task
|
||||
</span>
|
||||
);
|
||||
|
@ -13,17 +13,21 @@
|
||||
|
||||
import { Typography } from 'antd';
|
||||
import { EllipsisConfig } from 'antd/lib/typography/Base';
|
||||
import React, { FC } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import React, { CSSProperties, FC } from 'react';
|
||||
|
||||
interface Props extends EllipsisConfig {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const Ellipses: FC<Props> = ({ children, ...props }) => {
|
||||
const Ellipses: FC<Props> = ({ children, className, style, ...props }) => {
|
||||
return (
|
||||
<Typography.Paragraph
|
||||
className="ant-typography-ellipsis-custom"
|
||||
ellipsis={props}>
|
||||
className={classNames('ant-typography-ellipsis-custom', className)}
|
||||
ellipsis={props}
|
||||
style={style}>
|
||||
{children}
|
||||
</Typography.Paragraph>
|
||||
);
|
||||
|
@ -13,6 +13,7 @@
|
||||
|
||||
import { Popover } from 'antd';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { FC, Fragment, HTMLAttributes, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { getUserByName } from '../../../axiosAPIs/userAPI';
|
||||
@ -27,14 +28,17 @@ import ProfilePicture from '../ProfilePicture/ProfilePicture';
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLDivElement> {
|
||||
userName: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
const UserPopOverCard: FC<Props> = ({ children, userName }) => {
|
||||
const UserPopOverCard: FC<Props> = ({ children, userName, type = 'user' }) => {
|
||||
const history = useHistory();
|
||||
const [userData, setUserData] = useState<User>({} as User);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const getData = () => {
|
||||
if (type === 'user') {
|
||||
setIsLoading(true);
|
||||
getUserByName(userName, 'profile,roles,teams,follows,owns')
|
||||
.then((res: AxiosResponse) => {
|
||||
setUserData(res.data);
|
||||
@ -43,6 +47,7 @@ const UserPopOverCard: FC<Props> = ({ children, userName }) => {
|
||||
showErrorToast(err);
|
||||
})
|
||||
.finally(() => setIsLoading(false));
|
||||
}
|
||||
};
|
||||
|
||||
const onTitleClickHandler = (path: string) => {
|
||||
@ -117,6 +122,7 @@ const UserPopOverCard: FC<Props> = ({ children, userName }) => {
|
||||
{displayName !== name ? (
|
||||
<span className="tw-text-grey-muted">{name}</span>
|
||||
) : null}
|
||||
{isEmpty(userData) && <span>{userName}</span>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@ -129,8 +135,14 @@ const UserPopOverCard: FC<Props> = ({ children, userName }) => {
|
||||
<Loader size="small" />
|
||||
) : (
|
||||
<div className="tw-w-80">
|
||||
{isEmpty(userData) ? (
|
||||
<span>No data available</span>
|
||||
) : (
|
||||
<Fragment>
|
||||
<UserTeams />
|
||||
<UserRoles />
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
|
@ -51,4 +51,5 @@ export interface RichTextEditorProp extends HTMLAttributes<HTMLDivElement> {
|
||||
useCommandShortcut?: boolean;
|
||||
readonly?: boolean;
|
||||
height?: string;
|
||||
onTextChange?: (value: string) => void;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import { Editor, Viewer } from '@toast-ui/react-editor';
|
||||
import classNames from 'classnames';
|
||||
import React, {
|
||||
createRef,
|
||||
forwardRef,
|
||||
@ -37,6 +38,9 @@ const RichTextEditor = forwardRef<editorRef, RichTextEditorProp>(
|
||||
initialValue = '',
|
||||
readonly,
|
||||
height,
|
||||
className,
|
||||
style,
|
||||
onTextChange,
|
||||
}: RichTextEditorProp,
|
||||
ref
|
||||
) => {
|
||||
@ -49,6 +53,7 @@ const RichTextEditor = forwardRef<editorRef, RichTextEditorProp>(
|
||||
?.getInstance()
|
||||
.getMarkdown() as string;
|
||||
setEditorValue(value);
|
||||
onTextChange && onTextChange(value);
|
||||
};
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
@ -62,7 +67,7 @@ const RichTextEditor = forwardRef<editorRef, RichTextEditorProp>(
|
||||
}, [initialValue]);
|
||||
|
||||
return (
|
||||
<div className="tw-my-4">
|
||||
<div className={classNames(className, 'tw-my-4')} style={style}>
|
||||
{readonly ? (
|
||||
<div
|
||||
className="tw-border tw-border-main tw-p-2 tw-rounded"
|
||||
|
@ -13,14 +13,18 @@
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
export const useAfterMount = (fnCallback: () => void) => {
|
||||
export const useAfterMount = (
|
||||
fnCallback: () => void,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
deps?: ReadonlyArray<any>
|
||||
) => {
|
||||
const [isMounting, setIsMounting] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isMounting) {
|
||||
fnCallback();
|
||||
}
|
||||
}, [isMounting]);
|
||||
}, [isMounting, ...(deps || [])]);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounting(false);
|
||||
|
@ -11,12 +11,13 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Card } from 'antd';
|
||||
import { Button, Card, Input } from 'antd';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { capitalize, isNil } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { EditorContentRef, EntityTags } from 'Models';
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@ -70,6 +71,8 @@ const RequestDescription = () => {
|
||||
const [entityData, setEntityData] = useState<EntityData>({} as EntityData);
|
||||
const [options, setOptions] = useState<Option[]>([]);
|
||||
const [assignees, setAssignees] = useState<Array<Option>>([]);
|
||||
const [title, setTitle] = useState<string>('');
|
||||
const [suggestion, setSuggestion] = useState<string>('');
|
||||
|
||||
const entityTier = useMemo(() => {
|
||||
const tierFQN = getTierTags(entityData.tags || [])?.tagFQN;
|
||||
@ -128,11 +131,20 @@ const RequestDescription = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value: newValue } = e.target;
|
||||
setTitle(newValue);
|
||||
};
|
||||
|
||||
const onSuggestionChange = (value: string) => {
|
||||
setSuggestion(value);
|
||||
};
|
||||
|
||||
const onCreateTask = () => {
|
||||
if (assignees.length) {
|
||||
const data: CreateThread = {
|
||||
from: currentUser?.name as string,
|
||||
message,
|
||||
message: title || message,
|
||||
about: getEntityFeedLink(entityType, entityFQN, getTaskAbout()),
|
||||
taskDetails: {
|
||||
assignees: assignees.map((assignee) => ({
|
||||
@ -176,6 +188,7 @@ const RequestDescription = () => {
|
||||
setAssignees(defaultAssignee);
|
||||
setOptions(defaultAssignee);
|
||||
}
|
||||
setTitle(message);
|
||||
}, [entityData]);
|
||||
|
||||
return (
|
||||
@ -191,9 +204,19 @@ const RequestDescription = () => {
|
||||
className="tw-col-span-2"
|
||||
key="request-description"
|
||||
style={{ ...cardStyles }}
|
||||
title={`Task: ${message}`}>
|
||||
title="Create Task">
|
||||
<div data-testid="title">
|
||||
<span>Title:</span>{' '}
|
||||
<Input
|
||||
placeholder="Task title"
|
||||
style={{ margin: '4px 0px' }}
|
||||
value={title}
|
||||
onChange={onTitleChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div data-testid="assignees">
|
||||
<span className="tw-text-grey-muted">Assignees:</span>{' '}
|
||||
<span>Assignees:</span>{' '}
|
||||
<Assignees
|
||||
assignees={assignees}
|
||||
options={options}
|
||||
@ -203,16 +226,16 @@ const RequestDescription = () => {
|
||||
</div>
|
||||
|
||||
<p data-testid="description-label">
|
||||
<span>Description:</span>{' '}
|
||||
<span className="tw-text-grey-muted">
|
||||
description below will be suggested to the assignees
|
||||
</span>
|
||||
<span>Suggest description:</span>{' '}
|
||||
</p>
|
||||
|
||||
<RichTextEditor
|
||||
className="tw-my-0"
|
||||
initialValue=""
|
||||
placeHolder="Suggest description"
|
||||
ref={markdownRef}
|
||||
style={{ marginTop: '4px' }}
|
||||
onTextChange={onSuggestionChange}
|
||||
/>
|
||||
|
||||
<div className="tw-flex tw-justify-end" data-testid="cta-buttons">
|
||||
@ -223,7 +246,7 @@ const RequestDescription = () => {
|
||||
className="ant-btn-primary-custom"
|
||||
type="primary"
|
||||
onClick={onCreateTask}>
|
||||
Submit
|
||||
{suggestion ? 'Suggest' : 'Submit'}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
|
@ -16,7 +16,7 @@ import { Button, Card, Layout, Tabs } from 'antd';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { compare, Operation } from 'fast-json-patch';
|
||||
import { isEmpty, isEqual, isUndefined, toLower } from 'lodash';
|
||||
import { isEmpty, isEqual, isUndefined, toLower, uniqueId } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { EditorContentRef, EntityTags } from 'Models';
|
||||
import React, { Fragment, useEffect, useMemo, useRef, useState } from 'react';
|
||||
@ -39,7 +39,7 @@ import Ellipses from '../../../components/common/Ellipses/Ellipses';
|
||||
import ErrorPlaceHolder from '../../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import UserPopOverCard from '../../../components/common/PopOverCard/UserPopOverCard';
|
||||
import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePicture';
|
||||
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import RichTextEditor from '../../../components/common/rich-text-editor/RichTextEditor';
|
||||
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
||||
import { TaskOperation } from '../../../constants/feed.constants';
|
||||
@ -47,6 +47,7 @@ import { EntityType } from '../../../enums/entity.enum';
|
||||
import { CreateThread } from '../../../generated/api/feed/createThread';
|
||||
import { Column } from '../../../generated/entity/data/table';
|
||||
import {
|
||||
TaskType,
|
||||
Thread,
|
||||
ThreadTaskStatus,
|
||||
ThreadType,
|
||||
@ -61,6 +62,7 @@ import {
|
||||
getEntityType,
|
||||
updateThreadData,
|
||||
} from '../../../utils/FeedUtils';
|
||||
import { getEncodedFqn } from '../../../utils/StringsUtils';
|
||||
import SVGIcons from '../../../utils/SvgUtils';
|
||||
import {
|
||||
getEntityLink,
|
||||
@ -72,11 +74,13 @@ import {
|
||||
fetchOptions,
|
||||
getBreadCrumbList,
|
||||
getColumnObject,
|
||||
getDescriptionDiff,
|
||||
} from '../../../utils/TasksUtils';
|
||||
import { getDayTimeByTimeStamp } from '../../../utils/TimeUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../../utils/ToastUtils';
|
||||
import Assignees from '../shared/Assignees';
|
||||
import { DescriptionTabs } from '../shared/DescriptionTabs';
|
||||
import { DiffView } from '../shared/DiffView';
|
||||
import { background, cardStyles, contentStyles } from '../TaskPage.styles';
|
||||
import { EntityData, Option } from '../TasksPage.interface';
|
||||
|
||||
@ -136,7 +140,7 @@ const TaskDetailPage = () => {
|
||||
}, [taskDetail, entityData]);
|
||||
|
||||
const currentDescription = () => {
|
||||
if (entityField) {
|
||||
if (entityField && !isEmpty(columnObject)) {
|
||||
return columnObject.description || '';
|
||||
} else {
|
||||
return entityData.description || '';
|
||||
@ -239,12 +243,12 @@ const TaskDetailPage = () => {
|
||||
};
|
||||
|
||||
const onTaskResolve = () => {
|
||||
const description =
|
||||
markdownRef.current?.getEditorContent() || taskDetail.task?.suggestion;
|
||||
const description = markdownRef.current?.getEditorContent();
|
||||
|
||||
updateTask(TaskOperation.RESOLVE, taskDetail.task?.id, {
|
||||
newValue: description,
|
||||
})
|
||||
const data = { newValue: description };
|
||||
|
||||
if (description) {
|
||||
updateTask(TaskOperation.RESOLVE, taskDetail.task?.id, data)
|
||||
.then(() => {
|
||||
showSuccessToast('Task Resolved Successfully');
|
||||
history.push(
|
||||
@ -255,6 +259,9 @@ const TaskDetailPage = () => {
|
||||
);
|
||||
})
|
||||
.catch((err: AxiosError) => showErrorToast(err));
|
||||
} else {
|
||||
showErrorToast('Cannot accept empty description');
|
||||
}
|
||||
};
|
||||
|
||||
const onTaskReject = () => {
|
||||
@ -329,7 +336,7 @@ const TaskDetailPage = () => {
|
||||
entityFQN &&
|
||||
fetchEntityDetail(
|
||||
entityType as EntityType,
|
||||
entityFQN as string,
|
||||
getEncodedFqn(entityFQN as string),
|
||||
setEntityData
|
||||
);
|
||||
fetchTaskFeed(taskDetail.id);
|
||||
@ -439,35 +446,36 @@ const TaskDetailPage = () => {
|
||||
};
|
||||
|
||||
const getCurrentDescription = () => {
|
||||
let markdown;
|
||||
let newDescription;
|
||||
let oldDescription;
|
||||
if (taskDetail.task?.status === ThreadTaskStatus.Open) {
|
||||
markdown = taskDetail.task.suggestion;
|
||||
newDescription = taskDetail.task.suggestion;
|
||||
oldDescription = !isEmpty(columnObject)
|
||||
? columnObject.description
|
||||
: entityData.description;
|
||||
} else {
|
||||
markdown = taskDetail.task?.newValue;
|
||||
newDescription = taskDetail.task?.newValue;
|
||||
oldDescription = taskDetail.task?.oldValue;
|
||||
}
|
||||
|
||||
return markdown ? (
|
||||
<RichTextEditorPreviewer
|
||||
className="tw-p-2"
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={markdown}
|
||||
/>
|
||||
) : (
|
||||
<span className="tw-no-description tw-p-2">No description </span>
|
||||
const diffs = getDescriptionDiff(
|
||||
oldDescription || '',
|
||||
newDescription || ''
|
||||
);
|
||||
|
||||
return <DiffView className="tw-p-2" diffArr={diffs} />;
|
||||
};
|
||||
|
||||
const hasEditAccess = () => {
|
||||
const isOwner = entityData.owner?.id === currentUser?.id;
|
||||
const isAssignee = taskDetail.task?.assignees?.some(
|
||||
(assignee) => assignee.id === currentUser?.id
|
||||
);
|
||||
|
||||
const isTaskClosed = taskDetail.task?.status === ThreadTaskStatus.Closed;
|
||||
const isCreator = taskDetail.createdBy === currentUser?.name;
|
||||
|
||||
return (
|
||||
(isAdminUser || isAuthDisabled || isAssignee || isOwner) && !isTaskClosed
|
||||
);
|
||||
const hasEditAccess = () => {
|
||||
return isAdminUser || isAuthDisabled || isAssignee || isOwner;
|
||||
};
|
||||
|
||||
return (
|
||||
@ -529,9 +537,7 @@ const TaskDetailPage = () => {
|
||||
<ColumnDetail column={columnObject} />
|
||||
|
||||
<div className="tw-flex tw-mb-4" data-testid="task-assignees">
|
||||
<span className="tw-text-grey-muted tw-self-center tw-mr-1">
|
||||
Assignees:
|
||||
</span>
|
||||
<span className="tw-text-grey-muted tw-mr-1">Assignees:</span>
|
||||
{editAssignee ? (
|
||||
<Fragment>
|
||||
<Assignees
|
||||
@ -563,14 +569,32 @@ const TaskDetailPage = () => {
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
<span className="tw-self-center tw-mr-1">
|
||||
{taskDetail.task?.assignees
|
||||
?.map((assignee) => getEntityName(assignee))
|
||||
?.join(', ')}
|
||||
<span
|
||||
className={classNames('tw-self-center tw-mr-1 tw-flex', {
|
||||
'tw-grid tw-grid-cols-4':
|
||||
(taskDetail.task?.assignees || []).length > 2,
|
||||
})}>
|
||||
{taskDetail.task?.assignees?.map((assignee) => (
|
||||
<UserPopOverCard
|
||||
key={uniqueId()}
|
||||
type={assignee.type}
|
||||
userName={assignee.name || ''}>
|
||||
<span className="tw-flex tw-m-1.5 tw-mt-0">
|
||||
<ProfilePicture
|
||||
id=""
|
||||
name={getEntityName(assignee)}
|
||||
width="20"
|
||||
/>
|
||||
<span className="tw-ml-1">
|
||||
{getEntityName(assignee)}
|
||||
</span>
|
||||
{hasEditAccess() && (
|
||||
</span>
|
||||
</UserPopOverCard>
|
||||
))}
|
||||
</span>
|
||||
{(hasEditAccess() || isCreator) && !isTaskClosed && (
|
||||
<button
|
||||
className="focus:tw-outline-none tw-self-baseline tw-p-2 tw-pl-0"
|
||||
className="focus:tw-outline-none tw-self-baseline tw-p-2 tw-pt-0 tw-pl-0"
|
||||
data-testid="edit-suggestion"
|
||||
onClick={() => setEditAssignee(true)}>
|
||||
<SVGIcons
|
||||
@ -586,8 +610,29 @@ const TaskDetailPage = () => {
|
||||
</div>
|
||||
|
||||
<div data-testid="task-description-tabs">
|
||||
<span className="tw-text-grey-muted">Description:</span>{' '}
|
||||
<p className="tw-text-grey-muted tw-mb-1">Description:</p>{' '}
|
||||
{!isEmpty(taskDetail) && (
|
||||
<Fragment>
|
||||
{taskDetail.task?.type === TaskType.RequestDescription ? (
|
||||
<Fragment>
|
||||
{taskDetail.task.status === ThreadTaskStatus.Open ? (
|
||||
<RichTextEditor
|
||||
height="208px"
|
||||
initialValue={taskDetail.task.suggestion || ''}
|
||||
placeHolder="Add description"
|
||||
ref={markdownRef}
|
||||
/>
|
||||
) : (
|
||||
<DiffView
|
||||
className="tw-border tw-border-main tw-p-2 tw-rounded tw-my-1 tw-mb-3"
|
||||
diffArr={getDescriptionDiff(
|
||||
taskDetail.task.oldValue || '',
|
||||
taskDetail.task.newValue || ''
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
{showEdit ? (
|
||||
<DescriptionTabs
|
||||
@ -598,7 +643,7 @@ const TaskDetailPage = () => {
|
||||
) : (
|
||||
<div className="tw-flex tw-border tw-border-main tw-rounded tw-mb-4">
|
||||
{getCurrentDescription()}
|
||||
{hasEditAccess() && (
|
||||
{hasEditAccess() && !isTaskClosed && (
|
||||
<button
|
||||
className="focus:tw-outline-none tw-self-baseline tw-p-2 tw-pl-0"
|
||||
data-testid="edit-suggestion"
|
||||
@ -615,23 +660,27 @@ const TaskDetailPage = () => {
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasEditAccess() && (
|
||||
{hasEditAccess() && !isTaskClosed && (
|
||||
<div
|
||||
className="tw-flex tw-justify-end"
|
||||
data-testid="task-cta-buttons">
|
||||
{showEdit && (
|
||||
<Button
|
||||
className="ant-btn-link-custom"
|
||||
type="link"
|
||||
onClick={onTaskReject}>
|
||||
{showEdit ? 'Cancel' : 'Reject'}
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
className="ant-btn-primary-custom"
|
||||
type="primary"
|
||||
onClick={onTaskResolve}>
|
||||
{showEdit ? 'Submit' : 'Accept'}
|
||||
Accept
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
@ -15,7 +15,7 @@ export const cardStyles = {
|
||||
border: '1px rgb(221, 227, 234) solid',
|
||||
borderRadius: '8px',
|
||||
marginBottom: '20px',
|
||||
boxShadow: '1px 1px 6px rgb(0 0 0 / 12%)',
|
||||
boxShadow: '1px 1px 6px rgb(0 0 0 / 6%)',
|
||||
marginTop: '8px',
|
||||
};
|
||||
|
||||
@ -25,4 +25,4 @@ export const contentStyles = {
|
||||
paddingTop: 0,
|
||||
};
|
||||
|
||||
export const background = { background: '#ffffff' };
|
||||
export const background = { background: '#F8F9FA' };
|
||||
|
@ -11,11 +11,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Card } from 'antd';
|
||||
import { Button, Card, Input } from 'antd';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { capitalize, isEmpty, isNil, isUndefined } from 'lodash';
|
||||
import { EditorContentRef, EntityTags } from 'Models';
|
||||
import React, {
|
||||
ChangeEvent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@ -70,6 +71,7 @@ const UpdateDescription = () => {
|
||||
const [options, setOptions] = useState<Option[]>([]);
|
||||
const [assignees, setAssignees] = useState<Array<Option>>([]);
|
||||
const [currentDescription, setCurrentDescription] = useState<string>('');
|
||||
const [title, setTitle] = useState<string>('');
|
||||
|
||||
const entityTier = useMemo(() => {
|
||||
const tierFQN = getTierTags(entityData.tags || [])?.tagFQN;
|
||||
@ -138,11 +140,16 @@ const UpdateDescription = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onTitleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value: newValue } = e.target;
|
||||
setTitle(newValue);
|
||||
};
|
||||
|
||||
const onCreateTask = () => {
|
||||
if (assignees.length) {
|
||||
const data: CreateThread = {
|
||||
from: currentUser?.name as string,
|
||||
message,
|
||||
message: title || message,
|
||||
about: getEntityFeedLink(entityType, entityFQN, getTaskAbout()),
|
||||
taskDetails: {
|
||||
assignees: assignees.map((assignee) => ({
|
||||
@ -186,6 +193,7 @@ const UpdateDescription = () => {
|
||||
setAssignees(defaultAssignee);
|
||||
setOptions(defaultAssignee);
|
||||
}
|
||||
setTitle(message);
|
||||
}, [entityData]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -205,9 +213,18 @@ const UpdateDescription = () => {
|
||||
className="tw-col-span-2"
|
||||
key="update-description"
|
||||
style={{ ...cardStyles }}
|
||||
title={`Task: ${message}`}>
|
||||
title="Create Task">
|
||||
<div data-testid="title">
|
||||
<span>Title:</span>{' '}
|
||||
<Input
|
||||
placeholder="Task title"
|
||||
style={{ margin: '4px 0px' }}
|
||||
value={title}
|
||||
onChange={onTitleChange}
|
||||
/>
|
||||
</div>
|
||||
<div data-testid="assignees">
|
||||
<span className="tw-text-grey-muted">Assignees:</span>{' '}
|
||||
<span>Assignees:</span>{' '}
|
||||
<Assignees
|
||||
assignees={assignees}
|
||||
options={options}
|
||||
|
@ -12,12 +12,13 @@
|
||||
*/
|
||||
|
||||
import { Tabs } from 'antd';
|
||||
import { isEqual, uniqueId } from 'lodash';
|
||||
import { isEqual } from 'lodash';
|
||||
import { Diff, EditorContentRef } from 'Models';
|
||||
import React, { useState } from 'react';
|
||||
import RichTextEditor from '../../../components/common/rich-text-editor/RichTextEditor';
|
||||
import RichTextEditorPreviewer from '../../../components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import { getDescriptionDiff } from '../../../utils/TasksUtils';
|
||||
import { DiffView } from './DiffView';
|
||||
|
||||
interface Props {
|
||||
description: string;
|
||||
@ -42,44 +43,11 @@ export const DescriptionTabs = ({
|
||||
if (newDescription) {
|
||||
setDiffs(getDescriptionDiff(description, newDescription));
|
||||
}
|
||||
} else {
|
||||
setDiffs([]);
|
||||
}
|
||||
};
|
||||
|
||||
const DiffView = ({ diffArr }: { diffArr: Diff[] }) => {
|
||||
const elements = diffArr.map((diff) => {
|
||||
if (diff.added) {
|
||||
return (
|
||||
<ins className="diff-added" key={uniqueId()}>
|
||||
{diff.value}
|
||||
</ins>
|
||||
);
|
||||
}
|
||||
if (diff.removed) {
|
||||
return (
|
||||
<del
|
||||
key={uniqueId()}
|
||||
style={{ color: '#b30000', background: '#fadad7' }}>
|
||||
{diff.value}
|
||||
</del>
|
||||
);
|
||||
}
|
||||
|
||||
return <div key={uniqueId()}>No diff available</div>;
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="tw-w-full tw-border tw-border-main tw-p-2 tw-rounded tw-my-3 tw-max-h-52 tw-overflow-y-auto">
|
||||
<pre className="tw-whitespace-pre-wrap tw-mb-0">
|
||||
{diffArr.length ? (
|
||||
elements
|
||||
) : (
|
||||
<span className="tw-text-grey-muted">No diff available</span>
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
activeKey={activeTab}
|
||||
@ -101,13 +69,17 @@ export const DescriptionTabs = ({
|
||||
</div>
|
||||
</TabPane>
|
||||
<TabPane key="2" tab="Diff">
|
||||
<DiffView diffArr={diffs} />
|
||||
<DiffView
|
||||
className="tw-border tw-border-main tw-p-2 tw-rounded tw-my-3"
|
||||
diffArr={diffs}
|
||||
/>
|
||||
</TabPane>
|
||||
<TabPane key="3" tab="New">
|
||||
<RichTextEditor
|
||||
className="tw-my-0"
|
||||
height="208px"
|
||||
initialValue={suggestion}
|
||||
placeHolder="Update description"
|
||||
ref={markdownRef}
|
||||
/>
|
||||
</TabPane>
|
||||
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 classNames from 'classnames';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { Diff } from 'Models';
|
||||
import React from 'react';
|
||||
|
||||
export const DiffView = ({
|
||||
diffArr,
|
||||
className,
|
||||
}: {
|
||||
diffArr: Diff[];
|
||||
className?: string;
|
||||
}) => {
|
||||
const elements = diffArr.map((diff) => {
|
||||
if (diff.added) {
|
||||
return (
|
||||
<ins className="diff-added" key={uniqueId()}>
|
||||
{diff.value}
|
||||
</ins>
|
||||
);
|
||||
}
|
||||
if (diff.removed) {
|
||||
return (
|
||||
<del
|
||||
key={uniqueId()}
|
||||
style={{ color: '#b30000', background: '#fadad7' }}>
|
||||
{diff.value}
|
||||
</del>
|
||||
);
|
||||
}
|
||||
|
||||
return <span key={uniqueId()}>{diff.value}</span>;
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'tw-w-full tw-max-h-52 tw-overflow-y-auto',
|
||||
className
|
||||
)}>
|
||||
<pre className="tw-whitespace-pre-wrap tw-mb-0">
|
||||
{diffArr.length ? (
|
||||
elements
|
||||
) : (
|
||||
<span className="tw-text-grey-muted">No diff available</span>
|
||||
)}
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
};
|
@ -22,7 +22,7 @@ const TaskPageLayout: FC<Props> = ({ children }) => {
|
||||
const { Content, Sider } = Layout;
|
||||
|
||||
return (
|
||||
<Layout style={background}>
|
||||
<Layout style={{ ...background, height: '100vh' }}>
|
||||
<Sider style={background} width={180} />
|
||||
<Content style={contentStyles}>{children}</Content>
|
||||
<Sider style={background} width={180} />
|
||||
|
@ -1231,7 +1231,7 @@ code {
|
||||
.ant-layout-sider-task-detail {
|
||||
background: #ffffff;
|
||||
border: 1px solid #dde3ea;
|
||||
box-shadow: -1px 3px 6px rgba(0, 0, 0, 0.16);
|
||||
box-shadow: -1px 3px 6px rgba(0, 0, 0, 0.06);
|
||||
padding: 16px;
|
||||
padding-top: 0px;
|
||||
overflow-y: auto;
|
||||
@ -1296,3 +1296,10 @@ code {
|
||||
div.ant-typography-ellipsis-custom {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.ant-input:hover,
|
||||
.ant-input:focus {
|
||||
border-color: #7147e8;
|
||||
border-right-width: 0px;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { diffLines } from 'diff';
|
||||
import { diffWordsWithSpace } from 'diff';
|
||||
import { isEqual, isUndefined } from 'lodash';
|
||||
import { Diff } from 'Models';
|
||||
import { getDashboardByFqn } from '../axiosAPIs/dashboardAPI';
|
||||
@ -92,7 +92,7 @@ export const getDescriptionDiff = (
|
||||
oldValue: string,
|
||||
newValue: string
|
||||
): Diff[] => {
|
||||
return diffLines(oldValue, newValue, { ignoreWhitespace: false });
|
||||
return diffWordsWithSpace(oldValue, newValue);
|
||||
};
|
||||
|
||||
export const fetchOptions = (
|
||||
|
Loading…
x
Reference in New Issue
Block a user