Task improvements (#12361)

* chore(#6741): use antlr parser for fqn and entityLink

* chore: use this for class context

* chore: address comments

* chore: convert js to ts

* fix: typescript errors

* fix: entity link split method issue

* fix: split listener

* chore(ui): task UI improvements

* feat: add support for edit task assignees

* fix: validation issue

* minor improvements

* chore: address comments

* chore: show dropdown button on task action button

---------

Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
Sachin Chaurasiya 2023-07-13 15:34:21 +05:30 committed by GitHub
parent e200a42926
commit a47e80baf0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 231 additions and 50 deletions

View File

@ -27,6 +27,7 @@ interface ActivityFeedListV1Props {
onFeedClick?: (feed: Thread) => void; onFeedClick?: (feed: Thread) => void;
activeFeedId?: string; activeFeedId?: string;
hidePopover: boolean; hidePopover: boolean;
isForFeedTab?: boolean;
emptyPlaceholderText: string; emptyPlaceholderText: string;
} }
@ -37,6 +38,7 @@ const ActivityFeedListV1 = ({
onFeedClick, onFeedClick,
activeFeedId, activeFeedId,
hidePopover = false, hidePopover = false,
isForFeedTab = false,
emptyPlaceholderText, emptyPlaceholderText,
}: ActivityFeedListV1Props) => { }: ActivityFeedListV1Props) => {
const [entityThread, setEntityThread] = useState<Thread[]>([]); const [entityThread, setEntityThread] = useState<Thread[]>([]);
@ -76,6 +78,7 @@ const ActivityFeedListV1 = ({
feed={feed} feed={feed}
hidePopover={hidePopover} hidePopover={hidePopover}
isActive={activeFeedId === feed.id} isActive={activeFeedId === feed.id}
isForFeedTab={isForFeedTab}
key={feed.id} key={feed.id}
showThread={showThread} showThread={showThread}
onFeedClick={onFeedClick} onFeedClick={onFeedClick}

View File

@ -26,6 +26,7 @@ interface FeedPanelBodyPropV1 {
isOpenInDrawer?: boolean; isOpenInDrawer?: boolean;
onFeedClick?: (feed: Thread) => void; onFeedClick?: (feed: Thread) => void;
isActive?: boolean; isActive?: boolean;
isForFeedTab?: boolean;
hidePopover: boolean; hidePopover: boolean;
} }
@ -37,6 +38,7 @@ const FeedPanelBodyV1: FC<FeedPanelBodyPropV1> = ({
onFeedClick, onFeedClick,
isActive, isActive,
hidePopover = false, hidePopover = false,
isForFeedTab = false,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const mainFeed = { const mainFeed = {
@ -64,6 +66,7 @@ const FeedPanelBodyV1: FC<FeedPanelBodyPropV1> = ({
feed={feed} feed={feed}
hidePopover={hidePopover} hidePopover={hidePopover}
isActive={isActive} isActive={isActive}
isForFeedTab={isForFeedTab}
isOpenInDrawer={isOpenInDrawer} isOpenInDrawer={isOpenInDrawer}
key={feed.id} key={feed.id}
post={mainFeed} post={mainFeed}

View File

@ -350,6 +350,7 @@ export const ActivityFeedTab = ({
)} )}
<ActivityFeedListV1 <ActivityFeedListV1
hidePopover hidePopover
isForFeedTab
activeFeedId={selectedThread?.id} activeFeedId={selectedThread?.id}
emptyPlaceholderText={placeholderText} emptyPlaceholderText={placeholderText}
feedList={threads} feedList={threads}
@ -383,6 +384,7 @@ export const ActivityFeedTab = ({
/> />
</div> </div>
<FeedPanelBodyV1 <FeedPanelBodyV1
isForFeedTab
isOpenInDrawer isOpenInDrawer
showThread showThread
feed={selectedThread} feed={selectedThread}

View File

@ -11,24 +11,26 @@
* limitations under the License. * limitations under the License.
*/ */
import Icon from '@ant-design/icons'; import Icon from '@ant-design/icons';
import { Col, Row, Tooltip, Typography } from 'antd'; import { Button, Col, Row, Tooltip, Typography } from 'antd';
import classNames from 'classnames'; import classNames from 'classnames';
import AssigneeList from 'components/common/AssigneeList/AssigneeList'; import AssigneeList from 'components/common/AssigneeList/AssigneeList';
import EntityPopOverCard from 'components/common/PopOverCard/EntityPopOverCard'; import EntityPopOverCard from 'components/common/PopOverCard/EntityPopOverCard';
import UserPopOverCard from 'components/common/PopOverCard/UserPopOverCard'; import UserPopOverCard from 'components/common/PopOverCard/UserPopOverCard';
import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture'; import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture';
import { Post, Thread, ThreadTaskStatus } from 'generated/entity/feed/thread'; import { Post, Thread, ThreadTaskStatus } from 'generated/entity/feed/thread';
import { isUndefined, noop } from 'lodash'; import { isEmpty, isUndefined, noop } from 'lodash';
import React, { useState } from 'react'; import React, { useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { getNameFromFQN } from 'utils/CommonUtils'; import { getNameFromFQN } from 'utils/CommonUtils';
import EntityLink from 'utils/EntityLink';
import { import {
getEntityFieldDisplay, getEntityFieldDisplay,
getEntityFQN, getEntityFQN,
getEntityType, getEntityType,
prepareFeedLink, prepareFeedLink,
} from 'utils/FeedUtils'; } from 'utils/FeedUtils';
import { getTaskDetailPath } from 'utils/TasksUtils';
import { import {
getDateTimeFromMilliSeconds, getDateTimeFromMilliSeconds,
getDayTimeByTimeStamp, getDayTimeByTimeStamp,
@ -48,6 +50,7 @@ interface TaskFeedCardProps {
isEntityFeed?: boolean; isEntityFeed?: boolean;
isOpenInDrawer?: boolean; isOpenInDrawer?: boolean;
isActive?: boolean; isActive?: boolean;
isForFeedTab?: boolean;
hidePopover: boolean; hidePopover: boolean;
} }
@ -59,19 +62,33 @@ const TaskFeedCard = ({
showThread = true, showThread = true,
isActive, isActive,
hidePopover = false, hidePopover = false,
isForFeedTab = false,
}: TaskFeedCardProps) => { }: TaskFeedCardProps) => {
const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
const timeStamp = feed.threadTs; const timeStamp = feed.threadTs;
const taskDetails = feed.task; const taskDetails = feed.task;
const postLength = feed?.postsCount ?? 0; const postLength = feed?.postsCount ?? 0;
const entityType = getEntityType(feed.about) ?? ''; const entityType = getEntityType(feed.about) ?? '';
const entityFQN = getEntityFQN(feed.about) ?? ''; const entityFQN = getEntityFQN(feed.about) ?? '';
const entityCheck = !isUndefined(entityFQN) && !isUndefined(entityType); const entityCheck = !isUndefined(entityFQN) && !isUndefined(entityType);
const [isEditPost, setIsEditPost] = useState(false); const [isEditPost, setIsEditPost] = useState(false);
const repliedUsers = [...new Set((feed?.posts ?? []).map((f) => f.from))]; const repliedUsers = [...new Set((feed?.posts ?? []).map((f) => f.from))];
const repliedUniqueUsersList = repliedUsers.slice(0, postLength >= 3 ? 2 : 1); const repliedUniqueUsersList = repliedUsers.slice(0, postLength >= 3 ? 2 : 1);
const { showDrawer } = useActivityFeedProvider(); const { showDrawer, setActiveThread } = useActivityFeedProvider();
const taskField = useMemo(() => {
const entityField = EntityLink.getEntityField(feed.about) ?? '';
const columnName = EntityLink.getTableColumnName(feed.about) ?? '';
if (columnName) {
return `${entityField}/${columnName}`;
}
return entityField;
}, [feed]);
const showReplies = () => { const showReplies = () => {
showDrawer?.(feed); showDrawer?.(feed);
@ -81,11 +98,21 @@ const TaskFeedCard = ({
setIsEditPost(!isEditPost); setIsEditPost(!isEditPost);
}; };
const handleTaskLinkClick = () => {
history.push({
pathname: getTaskDetailPath(feed),
});
setActiveThread(feed);
};
const getTaskLinkElement = entityCheck && ( const getTaskLinkElement = entityCheck && (
<Typography.Text> <Typography.Text>
<span>{`#${taskDetails?.id} `}</span> <Button
className="p-0"
type="link"
onClick={handleTaskLinkClick}>{`#${taskDetails?.id} `}</Button>
<Typography.Text>{taskDetails?.type}</Typography.Text> <Typography.Text className="p-l-xss">{taskDetails?.type}</Typography.Text>
<span className="m-x-xss">{t('label.for-lowercase')}</span> <span className="m-x-xss">{t('label.for-lowercase')}</span>
{isEntityFeed ? ( {isEntityFeed ? (
<span className="tw-heading" data-testid="headerText-entityField"> <span className="tw-heading" data-testid="headerText-entityField">
@ -93,16 +120,26 @@ const TaskFeedCard = ({
</span> </span>
) : ( ) : (
<> <>
<span className="p-r-xss">{entityType}</span> {isForFeedTab ? null : (
<EntityPopOverCard entityFQN={entityFQN} entityType={entityType}> <>
<Link <span className="p-r-xss">{entityType}</span>
className="break-all" <EntityPopOverCard entityFQN={entityFQN} entityType={entityType}>
data-testid="entitylink" <Link
to={prepareFeedLink(entityType, entityFQN)} className="break-all"
onClick={(e) => e.stopPropagation()}> data-testid="entitylink"
{getNameFromFQN(entityFQN)} to={prepareFeedLink(entityType, entityFQN)}
</Link> onClick={(e) => e.stopPropagation()}>
</EntityPopOverCard> {getNameFromFQN(entityFQN)}
</Link>
</EntityPopOverCard>
</>
)}
{!isEmpty(taskField) ? (
<span className={classNames({ 'p-l-xss': !isForFeedTab })}>
{taskField}
</span>
) : null}
</> </>
)} )}
</Typography.Text> </Typography.Text>

View File

@ -12,6 +12,7 @@
*/ */
import { CheckOutlined, CloseOutlined } from '@ant-design/icons'; import { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Space } from 'antd'; import { Button, Space } from 'antd';
import classNames from 'classnames';
import React from 'react'; import React from 'react';
import { InlineEditProps } from './InlineEdit.interface'; import { InlineEditProps } from './InlineEdit.interface';
@ -20,10 +21,11 @@ const InlineEdit = ({
onCancel, onCancel,
onSave, onSave,
direction, direction,
className,
}: InlineEditProps) => { }: InlineEditProps) => {
return ( return (
<Space <Space
className="w-full" className={classNames(className, 'w-full')}
data-testid="inline-edit-container" data-testid="inline-edit-container"
direction={direction}> direction={direction}>
{children} {children}

View File

@ -14,6 +14,7 @@ import { SpaceProps } from 'antd';
import { ReactNode } from 'react'; import { ReactNode } from 'react';
export interface InlineEditProps { export interface InlineEditProps {
className?: string;
children: ReactNode; children: ReactNode;
onCancel: () => void; onCancel: () => void;
// onSave it can be API call or normal function // onSave it can be API call or normal function

View File

@ -10,7 +10,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
import Icon from '@ant-design/icons'; import Icon, { DownOutlined } from '@ant-design/icons';
import { import {
Button, Button,
Col, Col,
@ -21,40 +21,51 @@ import {
Space, Space,
Typography, Typography,
} from 'antd'; } from 'antd';
import { useForm } from 'antd/lib/form/Form';
import Modal from 'antd/lib/modal/Modal'; import Modal from 'antd/lib/modal/Modal';
import AppState from 'AppState'; import AppState from 'AppState';
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import classNames from 'classnames';
import ActivityFeedCardV1 from 'components/ActivityFeed/ActivityFeedCard/ActivityFeedCardV1'; import ActivityFeedCardV1 from 'components/ActivityFeed/ActivityFeedCard/ActivityFeedCardV1';
import ActivityFeedEditor from 'components/ActivityFeed/ActivityFeedEditor/ActivityFeedEditor'; import ActivityFeedEditor from 'components/ActivityFeed/ActivityFeedEditor/ActivityFeedEditor';
import { useActivityFeedProvider } from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider'; import { useActivityFeedProvider } from 'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
import AssigneeList from 'components/common/AssigneeList/AssigneeList';
import { OwnerLabel } from 'components/common/OwnerLabel/OwnerLabel.component'; import { OwnerLabel } from 'components/common/OwnerLabel/OwnerLabel.component';
import EntityPopOverCard from 'components/common/PopOverCard/EntityPopOverCard'; import InlineEdit from 'components/InlineEdit/InlineEdit.component';
import { DE_ACTIVE_COLOR } from 'constants/constants';
import { TaskOperation } from 'constants/Feeds.constants'; import { TaskOperation } from 'constants/Feeds.constants';
import { compare } from 'fast-json-patch';
import { TaskType } from 'generated/api/feed/createThread'; import { TaskType } from 'generated/api/feed/createThread';
import { TaskDetails, ThreadTaskStatus } from 'generated/entity/feed/thread'; import { TaskDetails, ThreadTaskStatus } from 'generated/entity/feed/thread';
import { TagLabel } from 'generated/type/tagLabel'; import { TagLabel } from 'generated/type/tagLabel';
import { useAuth } from 'hooks/authHooks'; import { useAuth } from 'hooks/authHooks';
import { isEmpty, isEqual, isUndefined, noop } from 'lodash'; import { isEmpty, isEqual, isUndefined, noop } from 'lodash';
import Assignees from 'pages/TasksPage/shared/Assignees';
import DescriptionTask from 'pages/TasksPage/shared/DescriptionTask'; import DescriptionTask from 'pages/TasksPage/shared/DescriptionTask';
import TagsTask from 'pages/TasksPage/shared/TagsTask'; import TagsTask from 'pages/TasksPage/shared/TagsTask';
import { import {
Option,
TaskAction, TaskAction,
TaskActionMode, TaskActionMode,
} from 'pages/TasksPage/TasksPage.interface'; } from 'pages/TasksPage/TasksPage.interface';
import { MenuInfo } from 'rc-menu/lib/interface'; import { MenuInfo } from 'rc-menu/lib/interface';
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
import { updateTask } from 'rest/feedsAPI'; import { updateTask, updateThread } from 'rest/feedsAPI';
import { getNameFromFQN } from 'utils/CommonUtils'; import EntityLink from 'utils/EntityLink';
import { getEntityFQN, prepareFeedLink } from 'utils/FeedUtils'; import { getEntityName } from 'utils/EntityUtils';
import { getEntityFQN } from 'utils/FeedUtils';
import { getEntityLink } from 'utils/TableUtils'; import { getEntityLink } from 'utils/TableUtils';
import { import {
fetchOptions,
isDescriptionTask, isDescriptionTask,
isTagsTask, isTagsTask,
TASK_ACTION_LIST, TASK_ACTION_LIST,
} from 'utils/TasksUtils'; } from 'utils/TasksUtils';
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils'; import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
import './task-tab.less';
import { TaskTabProps } from './TaskTab.interface'; import { TaskTabProps } from './TaskTab.interface';
import { ReactComponent as TaskCloseIcon } from '/assets/svg/ic-close-task.svg'; import { ReactComponent as TaskCloseIcon } from '/assets/svg/ic-close-task.svg';
import { ReactComponent as TaskOpenIcon } from '/assets/svg/ic-open-task.svg'; import { ReactComponent as TaskOpenIcon } from '/assets/svg/ic-open-task.svg';
@ -65,6 +76,9 @@ export const TaskTab = ({
entityType, entityType,
...rest ...rest
}: TaskTabProps) => { }: TaskTabProps) => {
const [assigneesForm] = useForm();
const updatedAssignees = Form.useWatch('assignees', assigneesForm);
const { task: taskDetails } = taskThread; const { task: taskDetails } = taskThread;
const entityFQN = getEntityFQN(taskThread.about) ?? ''; const entityFQN = getEntityFQN(taskThread.about) ?? '';
const entityCheck = !isUndefined(entityFQN) && !isUndefined(entityType); const entityCheck = !isUndefined(entityFQN) && !isUndefined(entityType);
@ -72,12 +86,24 @@ export const TaskTab = ({
const [form] = Form.useForm(); const [form] = Form.useForm();
const history = useHistory(); const history = useHistory();
const { isAdminUser } = useAuth(); const { isAdminUser } = useAuth();
const { postFeed } = useActivityFeedProvider(); const { postFeed, setActiveThread } = useActivityFeedProvider();
const [taskAction, setTaskAction] = useState<TaskAction>(TASK_ACTION_LIST[0]); const [taskAction, setTaskAction] = useState<TaskAction>(TASK_ACTION_LIST[0]);
const isTaskClosed = isEqual(taskDetails?.status, ThreadTaskStatus.Closed); const isTaskClosed = isEqual(taskDetails?.status, ThreadTaskStatus.Closed);
const [showEditTaskModel, setShowEditTaskModel] = useState(false); const [showEditTaskModel, setShowEditTaskModel] = useState(false);
const [comment, setComment] = useState(''); const [comment, setComment] = useState('');
const [isEditAssignee, setIsEditAssignee] = useState<boolean>(false);
const [options, setOptions] = useState<Option[]>([]);
const initialAssignees = useMemo(
() =>
taskDetails?.assignees.map((assignee) => ({
label: getEntityName(assignee),
value: assignee.id || '',
type: assignee.type,
})) ?? [],
[taskDetails]
);
// get current user details // get current user details
const currentUser = useMemo( const currentUser = useMemo(
@ -85,6 +111,17 @@ export const TaskTab = ({
[AppState.userDetails, AppState.nonSecureUserDetails] [AppState.userDetails, AppState.nonSecureUserDetails]
); );
const taskField = useMemo(() => {
const entityField = EntityLink.getEntityField(taskThread.about) ?? '';
const columnName = EntityLink.getTableColumnName(taskThread.about) ?? '';
if (columnName) {
return `${entityField}/${columnName}`;
}
return entityField;
}, [taskThread]);
const isOwner = isEqual(owner?.id, currentUser?.id); const isOwner = isEqual(owner?.id, currentUser?.id);
const isCreator = isEqual(taskThread.createdBy, currentUser?.name); const isCreator = isEqual(taskThread.createdBy, currentUser?.name);
@ -113,18 +150,8 @@ export const TaskTab = ({
<Typography.Text>{taskDetails?.type}</Typography.Text> <Typography.Text>{taskDetails?.type}</Typography.Text>
<span className="m-x-xss">{t('label.for-lowercase')}</span> <span className="m-x-xss">{t('label.for-lowercase')}</span>
<>
<span className="p-r-xss">{entityType}</span> {!isEmpty(taskField) ? <span>{taskField}</span> : null}
<EntityPopOverCard entityFQN={entityFQN} entityType={entityType}>
<Link
className="break-all"
data-testid="entitylink"
to={prepareFeedLink(entityType, entityFQN)}
onClick={(e) => e.stopPropagation()}>
{getNameFromFQN(entityFQN)}
</Link>
</EntityPopOverCard>
</>
</Typography.Text> </Typography.Text>
); );
@ -259,6 +286,7 @@ export const TaskTab = ({
</Button> </Button>
) : ( ) : (
<Dropdown.Button <Dropdown.Button
icon={<DownOutlined />}
menu={{ menu={{
items: TASK_ACTION_LIST, items: TASK_ACTION_LIST,
selectable: true, selectable: true,
@ -304,6 +332,32 @@ export const TaskTab = ({
} }
}, [taskDetails, isTaskDescription]); }, [taskDetails, isTaskDescription]);
const handleAssigneeUpdate = async () => {
const updatedTaskThread = {
...taskThread,
task: {
...taskThread.task,
assignees: updatedAssignees.map((assignee: Option) => ({
id: assignee.value,
type: assignee.type,
})),
},
};
try {
const patch = compare(taskThread, updatedTaskThread);
const data = await updateThread(taskThread.id, patch);
setIsEditAssignee(false);
setActiveThread(data);
} catch (error) {
showErrorToast(error as AxiosError);
}
};
useEffect(() => {
assigneesForm.setFieldValue('assignees', initialAssignees);
setOptions(initialAssignees);
}, [initialAssignees]);
return ( return (
<Row className="p-y-sm p-x-md" gutter={[0, 24]}> <Row className="p-y-sm p-x-md" gutter={[0, 24]}>
<Col className="d-flex items-center" span={24}> <Col className="d-flex items-center" span={24}>
@ -320,19 +374,78 @@ export const TaskTab = ({
{getTaskLinkElement} {getTaskLinkElement}
</Col> </Col>
<Col span={24}> <Col span={24}>
<div className="d-flex justify-between"> <div
<div className="flex-center gap-2"> className={classNames('d-flex justify-between', {
<Typography.Text className="text-grey-muted"> 'flex-column': isEditAssignee,
{t('label.assignee-plural')}:{' '} })}>
</Typography.Text> <div
className={classNames('gap-2', { 'flex-center': !isEditAssignee })}>
<OwnerLabel {isEditAssignee ? (
hasPermission={false} <Form
owner={taskDetails?.assignees[0]} form={assigneesForm}
onUpdate={noop} layout="vertical"
/> onFinish={handleAssigneeUpdate}>
<Form.Item
data-testid="assignees"
label={`${t('label.assignee-plural')}:`}
name="assignees"
rules={[
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.assignee-plural'),
}),
},
]}>
<InlineEdit
className="assignees-edit-input"
direction="horizontal"
onCancel={() => {
setIsEditAssignee(false);
assigneesForm.setFieldValue(
'assignees',
initialAssignees
);
}}
onSave={() => assigneesForm.submit()}>
<Assignees
options={options}
value={updatedAssignees}
onChange={(values) =>
assigneesForm.setFieldValue('assignees', values)
}
onSearch={(query) => fetchOptions(query, setOptions)}
/>
</InlineEdit>
</Form.Item>
</Form>
) : (
<>
<Typography.Text className="text-grey-muted">
{t('label.assignee-plural')}:{' '}
</Typography.Text>
<AssigneeList
assignees={taskDetails?.assignees ?? []}
className="d-flex gap-1"
profilePicType="circle"
profileWidth="24"
showUserName={false}
/>
{isCreator || hasTaskUpdateAccess() ? (
<Button
className="flex-center p-0"
data-testid="edit-assignees"
icon={<EditIcon color={DE_ACTIVE_COLOR} width="14px" />}
size="small"
type="text"
onClick={() => setIsEditAssignee(true)}
/>
) : null}
</>
)}
</div> </div>
<div className="flex-center gap-2"> <div
className={classNames('gap-2', { 'flex-center': !isEditAssignee })}>
<Typography.Text className="text-grey-muted"> <Typography.Text className="text-grey-muted">
{t('label.created-by')}:{' '} {t('label.created-by')}:{' '}
</Typography.Text> </Typography.Text>

View File

@ -0,0 +1,17 @@
/*
* Copyright 2023 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.
*/
.assignees-edit-input {
.ant-space-item:first-child {
width: 100%;
}
}

View File

@ -142,6 +142,9 @@
.flex-row { .flex-row {
flex-direction: row; flex-direction: row;
} }
.flex-column {
flex-direction: column;
}
// Justify Items // Justify Items
.justify-center { .justify-center {