ui: worked on ui feedback part 2 (#12228)

* updated request description in topic details

* miner fix

* worked on feedback

* fixed redirection link for activity feed

* fixed failing test

* miner fix

* fixed infinite loading issue when there is no permission
This commit is contained in:
Shailesh Parmar 2023-06-29 22:07:35 +05:30 committed by GitHub
parent a8c6f8bce0
commit ccf585efba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 340 additions and 126 deletions

View File

@ -140,9 +140,9 @@ const TestCaseForm: React.FC<TestCaseFormProps> = ({
); );
const name = const name =
value.testName?.trim() || value.testName?.trim() ||
`${columnName ? columnName : table.name}_${snakeCase( `${replaceAllSpacialCharWith_(
selectedTestType columnName ? columnName : table.name
)}_${cryptoRandomString({ )}_${snakeCase(selectedTestType)}_${cryptoRandomString({
length: 4, length: 4,
type: 'alphanumeric', type: 'alphanumeric',
})}`; })}`;

View File

@ -319,9 +319,9 @@ const Appbar: React.FC = (): JSX.Element => {
{remainingTeamsCount} {t('label.more')} {remainingTeamsCount} {t('label.more')}
</Link> </Link>
) : null} ) : null}
<hr className="m-t-sm" />
</div> </div>
) : null} ) : null}
<hr className="m-t-sm" />
</div> </div>
); );
}; };

View File

@ -13,6 +13,8 @@
import { Badge, Button, List, Tabs, Typography } from 'antd'; import { Badge, Button, List, Tabs, Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { ActivityFeedTabs } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.interface';
import { EntityTabs } from 'enums/entity.enum';
import { UserProfileTab } from 'enums/user.enum'; import { UserProfileTab } from 'enums/user.enum';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
@ -52,9 +54,11 @@ const NotificationBox = ({
const [isLoading, setIsLoading] = useState<boolean>(false); const [isLoading, setIsLoading] = useState<boolean>(false);
const [viewAllPath, setViewAllPath] = useState<string>( const [viewAllPath, setViewAllPath] = useState<string>(
`${getUserPath(currentUser?.name as string)}/tasks?feedFilter=${ getUserPath(
FeedFilter.ASSIGNED_TO currentUser?.name as string,
}` EntityTabs.ACTIVITY_FEED,
ActivityFeedTabs.TASKS
)
); );
const notificationDropDownList = useMemo(() => { const notificationDropDownList = useMemo(() => {

View File

@ -169,7 +169,7 @@ const Services = ({
<div> <div>
<Link <Link
to={getServiceDetailsPath( to={getServiceDetailsPath(
service.name, service.fullyQualifiedName ?? service.name,
serviceName serviceName
)}> )}>
<button> <button>

View File

@ -158,11 +158,11 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
excludeColumns, excludeColumns,
} = tableProfilerConfig; } = tableProfilerConfig;
handleStateChange({ handleStateChange({
sqlQuery: profileQuery || '', sqlQuery: profileQuery ?? '',
profileSample: profileSample, profileSample: profileSample,
excludeCol: excludeColumns || [], excludeCol: excludeColumns ?? [],
selectedProfileSampleType: selectedProfileSampleType:
profileSampleType || ProfileSampleType.Percentage, profileSampleType ?? ProfileSampleType.Percentage,
}); });
const profileSampleTypeCheck = const profileSampleTypeCheck =
@ -429,6 +429,7 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
})} })}
name="profileSampleType"> name="profileSampleType">
<Select <Select
autoFocus
className="w-full" className="w-full"
data-testid="profile-sample" data-testid="profile-sample"
options={PROFILE_SAMPLE_OPTIONS} options={PROFILE_SAMPLE_OPTIONS}
@ -477,13 +478,13 @@ const ProfilerSettingsModal: React.FC<ProfilerSettingsModalProps> = ({
</p> </p>
<SchemaEditor <SchemaEditor
className="custom-query-editor query-editor-h-200" className="sql-editor-container custom-query-editor query-editor-h-200 custom-code-mirror-theme"
data-testid="profiler-setting-sql-editor" data-testid="profiler-setting-sql-editor"
mode={{ name: CSMode.SQL }} mode={{ name: CSMode.SQL }}
options={{ options={{
readOnly: false, readOnly: false,
}} }}
value={state?.sqlQuery || ''} value={state?.sqlQuery ?? ''}
onChange={handleCodeMirrorChange} onChange={handleCodeMirrorChange}
/> />
</Col> </Col>

View File

@ -53,6 +53,13 @@
overflow: scroll; overflow: scroll;
} }
} }
.sql-editor-container {
.CodeMirror {
.CodeMirror-foldgutter {
width: 2em;
}
}
}
.include-columns-add-button.ant-btn-icon-only.ant-btn-sm { .include-columns-add-button.ant-btn-icon-only.ant-btn-sm {
width: 18px; width: 18px;
height: 18px; height: 18px;

View File

@ -22,7 +22,9 @@ import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlac
import QueryViewer from 'components/common/QueryViewer/QueryViewer.component'; import QueryViewer from 'components/common/QueryViewer/QueryViewer.component';
import PageLayoutV1 from 'components/containers/PageLayoutV1'; import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component'; import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component';
import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component';
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface'; import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
import SampleDataTopic from 'components/SampleDataTopic/SampleDataTopic';
import TabsLabel from 'components/TabsLabel/TabsLabel.component'; import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2'; import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { getTopicDetailsPath } from 'constants/constants'; import { getTopicDetailsPath } from 'constants/constants';
@ -51,8 +53,6 @@ import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel'; import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropertyTable'; import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropertyTable';
import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface'; import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface';
import EntityLineageComponent from '../EntityLineage/EntityLineage.component';
import SampleDataTopic from '../SampleDataTopic/SampleDataTopic';
import { TopicDetailsProps } from './TopicDetails.interface'; import { TopicDetailsProps } from './TopicDetails.interface';
import TopicSchemaFields from './TopicSchema/TopicSchema'; import TopicSchemaFields from './TopicSchema/TopicSchema';
@ -294,6 +294,15 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
onThreadLinkSelect={onThreadLinkSelect} onThreadLinkSelect={onThreadLinkSelect}
/> />
<TopicSchemaFields <TopicSchemaFields
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.COLUMNS,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.COLUMNS,
entityFieldThreadCount
)}
entityFqn={topicDetails.fullyQualifiedName ?? ''}
hasDescriptionEditAccess={ hasDescriptionEditAccess={
topicPermissions.EditAll || topicPermissions.EditDescription topicPermissions.EditAll || topicPermissions.EditDescription
} }
@ -302,6 +311,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
} }
isReadOnly={Boolean(topicDetails.deleted)} isReadOnly={Boolean(topicDetails.deleted)}
messageSchema={topicDetails.messageSchema} messageSchema={topicDetails.messageSchema}
onThreadLinkSelect={onThreadLinkSelect}
onUpdate={handleSchemaFieldsUpdate} onUpdate={handleSchemaFieldsUpdate}
/> />
</div> </div>
@ -464,6 +474,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
</Col> </Col>
<Col span={24}> <Col span={24}>
<Tabs <Tabs
destroyInactiveTabPane
activeKey={activeTab ?? EntityTabs.SCHEMA} activeKey={activeTab ?? EntityTabs.SCHEMA}
className="entity-details-page-tabs" className="entity-details-page-tabs"
data-testid="tabs" data-testid="tabs"

View File

@ -12,6 +12,8 @@
*/ */
import { TableProps } from 'antd'; import { TableProps } from 'antd';
import { ThreadType } from 'generated/api/feed/createThread';
import { EntityFieldThreads } from 'interface/feed.interface';
import { HTMLAttributes, ReactNode } from 'react'; import { HTMLAttributes, ReactNode } from 'react';
import { Field, Topic } from '../../../generated/entity/data/topic'; import { Field, Topic } from '../../../generated/entity/data/topic';
@ -27,9 +29,13 @@ export interface TopicSchemaFieldsProps
hasDescriptionEditAccess: boolean; hasDescriptionEditAccess: boolean;
hasTagEditAccess: boolean; hasTagEditAccess: boolean;
isReadOnly: boolean; isReadOnly: boolean;
entityFqn: string;
defaultExpandAllRows?: boolean; defaultExpandAllRows?: boolean;
showSchemaDisplayTypeSwitch?: boolean; showSchemaDisplayTypeSwitch?: boolean;
onUpdate?: (updatedMessageSchema: Topic['messageSchema']) => Promise<void>; onUpdate?: (updatedMessageSchema: Topic['messageSchema']) => Promise<void>;
entityFieldThreads?: EntityFieldThreads[];
entityFieldTasks?: EntityFieldThreads[];
onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void;
} }
export enum SchemaViewType { export enum SchemaViewType {

View File

@ -35,6 +35,7 @@ const mockProps: TopicSchemaFieldsProps = {
isReadOnly: false, isReadOnly: false,
onUpdate: mockOnUpdate, onUpdate: mockOnUpdate,
hasTagEditAccess: true, hasTagEditAccess: true,
entityFqn: 'topic.fqn',
}; };
jest.mock('utils/TagsUtils', () => ({ jest.mock('utils/TagsUtils', () => ({

View File

@ -28,14 +28,28 @@ import classNames from 'classnames';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
import SchemaEditor from 'components/schema-editor/SchemaEditor'; import SchemaEditor from 'components/schema-editor/SchemaEditor';
import TableTags from 'components/TableTags/TableTags.component'; import TableTags from 'components/TableTags/TableTags.component';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { DE_ACTIVE_COLOR } from 'constants/constants';
import { EntityField } from 'constants/Feeds.constants';
import { TABLE_SCROLL_VALUE } from 'constants/Table.constants'; import { TABLE_SCROLL_VALUE } from 'constants/Table.constants';
import { CSMode } from 'enums/codemirror.enum'; import { CSMode } from 'enums/codemirror.enum';
import { EntityType } from 'enums/entity.enum';
import { ThreadType } from 'generated/api/feed/createThread';
import { TagLabel, TagSource } from 'generated/type/tagLabel'; import { TagLabel, TagSource } from 'generated/type/tagLabel';
import { EntityFieldThreads } from 'interface/feed.interface';
import { cloneDeep, isEmpty, isUndefined, map } from 'lodash'; import { cloneDeep, isEmpty, isUndefined, map } from 'lodash';
import { EntityTags, TagOption } from 'Models'; import { EntityTags, TagOption } from 'Models';
import React, { FC, useMemo, useState } from 'react'; import React, { FC, Fragment, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { getEntityName } from 'utils/EntityUtils'; import { useHistory } from 'react-router-dom';
import { getPartialNameFromTopicFQN } from 'utils/CommonUtils';
import { ENTITY_LINK_SEPARATOR, getEntityName } from 'utils/EntityUtils';
import { getFieldThreadElement } from 'utils/FeedElementUtils';
import {
getRequestDescriptionPath,
getUpdateDescriptionPath,
} from 'utils/TasksUtils';
import { ReactComponent as IconRequest } from '../../../assets/svg/request-icon.svg';
import { DataTypeTopic, Field } from '../../../generated/entity/data/topic'; import { DataTypeTopic, Field } from '../../../generated/entity/data/topic';
import { getTableExpandableConfig } from '../../../utils/TableUtils'; import { getTableExpandableConfig } from '../../../utils/TableUtils';
import { import {
@ -59,13 +73,28 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
hasTagEditAccess, hasTagEditAccess,
defaultExpandAllRows = false, defaultExpandAllRows = false,
showSchemaDisplayTypeSwitch = true, showSchemaDisplayTypeSwitch = true,
entityFqn,
entityFieldThreads,
onThreadLinkSelect,
entityFieldTasks,
}) => { }) => {
const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
const [editFieldDescription, setEditFieldDescription] = useState<Field>(); const [editFieldDescription, setEditFieldDescription] = useState<Field>();
const [viewType, setViewType] = useState<SchemaViewType>( const [viewType, setViewType] = useState<SchemaViewType>(
SchemaViewType.FIELDS SchemaViewType.FIELDS
); );
const getColumnName = (cell: Field) => {
const fqn = cell?.fullyQualifiedName || '';
const columnName = getPartialNameFromTopicFQN(fqn);
// wrap it in quotes if dot is present
return columnName.includes(FQN_SEPARATOR_CHAR)
? `"${columnName}"`
: columnName;
};
const handleFieldTagsChange = async ( const handleFieldTagsChange = async (
selectedTags: EntityTags[], selectedTags: EntityTags[],
editColumnTag: Field editColumnTag: Field
@ -101,6 +130,66 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
} }
}; };
const onUpdateDescriptionHandler = (cell: Field) => {
const field = EntityField.COLUMNS;
const value = getColumnName(cell);
history.push(
getUpdateDescriptionPath(
EntityType.TOPIC,
entityFqn as string,
field,
value
)
);
};
const onRequestDescriptionHandler = (cell: Field) => {
const field = EntityField.COLUMNS;
const value = getColumnName(cell);
history.push(
getRequestDescriptionPath(
EntityType.TOPIC,
entityFqn as string,
field,
value
)
);
};
const getRequestDescriptionElement = (cell: Field) => {
const hasDescription = Boolean(cell?.description ?? '');
return (
<Button
className="p-0 w-7 h-7 flex-none flex-center link-text focus:tw-outline-none hover-cell-icon m-r-xss"
data-testid="request-description"
type="text"
onClick={() =>
hasDescription
? onUpdateDescriptionHandler(cell)
: onRequestDescriptionHandler(cell)
}>
<Popover
destroyTooltipOnHide
content={
hasDescription
? t('message.request-update-description')
: t('message.request-description')
}
overlayClassName="ant-popover-request-description"
trigger="hover"
zIndex={9999}>
<IconRequest
height={14}
name={t('message.request-description')}
style={{ color: DE_ACTIVE_COLOR }}
width={14}
/>
</Popover>
</Button>
);
};
const renderFieldDescription: CellRendered<Field, 'description'> = ( const renderFieldDescription: CellRendered<Field, 'description'> = (
description, description,
record, record,
@ -110,28 +199,68 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
<Space <Space
className="custom-group w-full" className="custom-group w-full"
data-testid="description" data-testid="description"
direction={isEmpty(description) ? 'horizontal' : 'vertical'}
id={`field-description-${index}`} id={`field-description-${index}`}
size={4}> size={4}>
<> <div>
{description ? ( {description ? (
<RichTextEditorPreviewer markdown={description} /> <RichTextEditorPreviewer markdown={description} />
) : ( ) : (
<Typography.Text className="text-grey-muted"> <span className="text-grey-muted">
{t('label.no-entity', { {t('label.no-entity', {
entity: t('label.description'), entity: t('label.description'),
})} })}
</Typography.Text> </span>
)} )}
</> </div>
{isReadOnly && !hasDescriptionEditAccess ? null : ( <div className="d-flex tw--mt-1.5">
{!isReadOnly ? (
<Fragment>
{hasDescriptionEditAccess && (
<>
<Button <Button
className="p-0 opacity-0 group-hover-opacity-100" className="p-0 tw-self-start flex-center w-7 h-7 d-flex-none hover-cell-icon"
data-testid="edit-button" data-testid="edit-button"
icon={<EditIcon width={16} />}
type="text" type="text"
onClick={() => setEditFieldDescription(record)} onClick={() => setEditFieldDescription(record)}>
<EditIcon
height={14}
name={t('label.edit')}
style={{ color: DE_ACTIVE_COLOR }}
width={14}
/> />
</Button>
</>
)} )}
{getRequestDescriptionElement(record)}
{getFieldThreadElement(
getColumnName(record),
EntityField.DESCRIPTION,
entityFieldThreads as EntityFieldThreads[],
onThreadLinkSelect,
EntityType.TOPIC,
entityFqn,
`columns${ENTITY_LINK_SEPARATOR}${getColumnName(
record
)}${ENTITY_LINK_SEPARATOR}description`,
Boolean(record)
)}
{getFieldThreadElement(
getColumnName(record),
EntityField.DESCRIPTION,
entityFieldTasks as EntityFieldThreads[],
onThreadLinkSelect,
EntityType.TOPIC,
entityFqn,
`columns${ENTITY_LINK_SEPARATOR}${getColumnName(
record
)}${ENTITY_LINK_SEPARATOR}description`,
Boolean(record),
ThreadType.Task
)}
</Fragment>
) : null}
</div>
</Space> </Space>
); );
}; };
@ -264,6 +393,7 @@ const TopicSchemaFields: FC<TopicSchemaFieldsProps> = ({
isEmpty(messageSchema?.schemaFields) ? ( isEmpty(messageSchema?.schemaFields) ? (
messageSchema?.schemaText && ( messageSchema?.schemaText && (
<SchemaEditor <SchemaEditor
className="custom-code-mirror-theme custom-query-editor"
editorClass={classNames('table-query-editor')} editorClass={classNames('table-query-editor')}
mode={{ name: CSMode.JAVASCRIPT }} mode={{ name: CSMode.JAVASCRIPT }}
options={{ options={{

View File

@ -127,6 +127,7 @@ const TopicVersion: FC<TopicVersionProp> = ({
<TopicSchemaFields <TopicSchemaFields
defaultExpandAllRows defaultExpandAllRows
isReadOnly isReadOnly
entityFqn={currentVersionData?.fullyQualifiedName ?? ''}
hasDescriptionEditAccess={false} hasDescriptionEditAccess={false}
hasTagEditAccess={false} hasTagEditAccess={false}
messageSchema={messageSchemaDiff} messageSchema={messageSchemaDiff}

View File

@ -273,8 +273,7 @@ const Users = ({
); );
} else { } else {
return ( return (
<div className="p-x-sm"> <Typography.Paragraph className="m-b-0">
<p className="m-t-xs">
{userData.description || ( {userData.description || (
<span className="text-grey-muted"> <span className="text-grey-muted">
{t('label.no-entity', { {t('label.no-entity', {
@ -282,8 +281,7 @@ const Users = ({
})} })}
</span> </span>
)} )}
</p> </Typography.Paragraph>
</div>
); );
} }
}; };
@ -584,7 +582,11 @@ const Users = ({
)} )}
<Space className="p-sm w-full" direction="vertical" size={8}> <Space className="p-sm w-full" direction="vertical" size={8}>
{getDisplayNameComponent()} {getDisplayNameComponent()}
<p>{userData.email}</p> <Typography.Paragraph
className="m-b-0"
ellipsis={{ tooltip: true }}>
{userData.email}
</Typography.Paragraph>
{getDescriptionComponent()} {getDescriptionComponent()}
{isAuthProviderBasic && {isAuthProviderBasic &&
(isAdminUser || isLoggedinUser) && (isAdminUser || isLoggedinUser) &&
@ -719,7 +721,7 @@ const Users = ({
} }
/> />
) : ( ) : (
<ErrorPlaceHolder> <ErrorPlaceHolder className="m-0">
<Typography.Paragraph> <Typography.Paragraph>
{tab === UserPageTabs.MY_DATA {tab === UserPageTabs.MY_DATA
? t('server.you-have-not-action-anything-yet', { ? t('server.you-have-not-action-anything-yet', {

View File

@ -31,6 +31,7 @@ import { getMlModelByFQN } from 'rest/mlModelAPI';
import { getPipelineByFqn } from 'rest/pipelineAPI'; import { getPipelineByFqn } from 'rest/pipelineAPI';
import { getTableDetailsByFQN } from 'rest/tableAPI'; import { getTableDetailsByFQN } from 'rest/tableAPI';
import { getTopicByFqn } from 'rest/topicsAPI'; import { getTopicByFqn } from 'rest/topicsAPI';
import { getTableFQNFromColumnFQN } from 'utils/CommonUtils';
import { getEntityName } from 'utils/EntityUtils'; import { getEntityName } from 'utils/EntityUtils';
import AppState from '../../../AppState'; import AppState from '../../../AppState';
import { EntityType } from '../../../enums/entity.enum'; import { EntityType } from '../../../enums/entity.enum';
@ -60,6 +61,13 @@ const PopoverContent: React.FC<{
case EntityType.TABLE: case EntityType.TABLE:
promise = getTableDetailsByFQN(entityFQN, fields); promise = getTableDetailsByFQN(entityFQN, fields);
break;
case EntityType.TEST_CASE:
promise = getTableDetailsByFQN(
getTableFQNFromColumnFQN(entityFQN),
fields
);
break; break;
case EntityType.TOPIC: case EntityType.TOPIC:
promise = getTopicByFqn(entityFQN, fields); promise = getTopicByFqn(entityFQN, fields);

View File

@ -508,6 +508,7 @@
"loading": "Loading", "loading": "Loading",
"local-config-source": "Local Config Source", "local-config-source": "Local Config Source",
"log-plural": "Logs", "log-plural": "Logs",
"log-viewer": "Log Viewer",
"logged-in-user-lowercase": "logged-in user", "logged-in-user-lowercase": "logged-in user",
"login": "Login", "login": "Login",
"logo-url": "Logo URL", "logo-url": "Logo URL",

View File

@ -508,6 +508,7 @@
"loading": "Cargando", "loading": "Cargando",
"local-config-source": "Origen de configuración local", "local-config-source": "Origen de configuración local",
"log-plural": "Registros", "log-plural": "Registros",
"log-viewer": "Log Viewer",
"logged-in-user-lowercase": "usuario conectado", "logged-in-user-lowercase": "usuario conectado",
"login": "Iniciar sesión", "login": "Iniciar sesión",
"logo-url": "Logo URL", "logo-url": "Logo URL",

View File

@ -508,6 +508,7 @@
"loading": "Chargement", "loading": "Chargement",
"local-config-source": "Source de Configuration Locale", "local-config-source": "Source de Configuration Locale",
"log-plural": "Journal", "log-plural": "Journal",
"log-viewer": "Log Viewer",
"logged-in-user-lowercase": "Utilisateur Connecté", "logged-in-user-lowercase": "Utilisateur Connecté",
"login": "Se Connecter", "login": "Se Connecter",
"logo-url": "Logo URL", "logo-url": "Logo URL",

View File

@ -508,6 +508,7 @@
"loading": "読み込み中", "loading": "読み込み中",
"local-config-source": "Local Config Source", "local-config-source": "Local Config Source",
"log-plural": "ログ", "log-plural": "ログ",
"log-viewer": "Log Viewer",
"logged-in-user-lowercase": "logged-in user", "logged-in-user-lowercase": "logged-in user",
"login": "ログイン", "login": "ログイン",
"logo-url": "Logo URL", "logo-url": "Logo URL",

View File

@ -508,6 +508,7 @@
"loading": "Carregando", "loading": "Carregando",
"local-config-source": "Origem da configuração local", "local-config-source": "Origem da configuração local",
"log-plural": "Logs", "log-plural": "Logs",
"log-viewer": "Log Viewer",
"logged-in-user-lowercase": "conectar com usuário", "logged-in-user-lowercase": "conectar com usuário",
"login": "Entrar", "login": "Entrar",
"logo-url": "Logo URL", "logo-url": "Logo URL",

View File

@ -508,6 +508,7 @@
"loading": "加载中", "loading": "加载中",
"local-config-source": "本地配置源", "local-config-source": "本地配置源",
"log-plural": "日志", "log-plural": "日志",
"log-viewer": "Log Viewer",
"logged-in-user-lowercase": "已登录用户", "logged-in-user-lowercase": "已登录用户",
"login": "登录", "login": "登录",
"logo-url": "Logo URL", "logo-url": "Logo URL",

View File

@ -267,11 +267,11 @@ const AddQueryPage = () => {
<Tooltip <Tooltip
placement="top" placement="top"
title={ title={
!permissions.query.Create && NO_PERMISSION_FOR_ACTION !permissions.query?.Create && NO_PERMISSION_FOR_ACTION
}> }>
<Button <Button
data-testid="save-btn" data-testid="save-btn"
disabled={!permissions.query.Create} disabled={!permissions.query?.Create}
htmlType="submit" htmlType="submit"
loading={isSaving} loading={isSaving}
type="primary"> type="primary">

View File

@ -22,11 +22,19 @@ jest.mock('react-router-dom', () => ({
ingestionName: 'ingestion_123456', ingestionName: 'ingestion_123456',
}), }),
})); }));
jest.mock('../../utils/LogsViewer.utils', () => ({
getLogBreadCrumbs: jest
.fn()
.mockReturnValue({ name: 'getLogBreadCrumbs', url: '' }),
}));
jest.mock( jest.mock(
'components/common/title-breadcrumb/title-breadcrumb.component', 'components/common/title-breadcrumb/title-breadcrumb.component',
() => () => <>TitleBreadcrumb.component</> () => () => <>TitleBreadcrumb.component</>
); );
jest.mock('components/containers/PageLayoutV1', () =>
jest.fn().mockImplementation(({ children }) => <div>{children}</div>)
);
jest.mock('react-lazylog', () => ({ jest.mock('react-lazylog', () => ({
LazyLog: jest LazyLog: jest

View File

@ -11,10 +11,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { Button, Card, Col, Row, Space, Typography } from 'antd'; import { Button, Col, Row, Space, Typography } from 'antd';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import { CopyToClipboardButton } from 'components/buttons/CopyToClipboardButton/CopyToClipboardButton'; import { CopyToClipboardButton } from 'components/buttons/CopyToClipboardButton/CopyToClipboardButton';
import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component';
import PageLayoutV1 from 'components/containers/PageLayoutV1';
import { IngestionRecentRuns } from 'components/Ingestion/IngestionRecentRun/IngestionRecentRuns.component'; import { IngestionRecentRuns } from 'components/Ingestion/IngestionRecentRun/IngestionRecentRuns.component';
import Loader from 'components/Loader/Loader'; import Loader from 'components/Loader/Loader';
import { isEmpty, isNil, isUndefined, toNumber } from 'lodash'; import { isEmpty, isNil, isUndefined, toNumber } from 'lodash';
@ -220,10 +221,12 @@ const LogsViewer = () => {
}; };
}, [ingestionDetails]); }, [ingestionDetails]);
return isLoading ? ( if (isLoading) {
<Loader /> return <Loader />;
) : ( }
<div className="m-xs ">
return (
<PageLayoutV1 pageTitle={t('label.log-viewer')}>
<Space align="start" className="w-full m-md m-t-xs" direction="vertical"> <Space align="start" className="w-full m-md m-t-xs" direction="vertical">
<Space align="center"> <Space align="center">
<TitleBreadcrumb <TitleBreadcrumb
@ -241,10 +244,9 @@ const LogsViewer = () => {
</Space> </Space>
</Space> </Space>
<Card className="h-full p-0 log-card">
{!isEmpty(logs) ? ( {!isEmpty(logs) ? (
<Row> <Row className="border-top">
<Col className="p-md" span={18}> <Col className="p-md border-right" span={18}>
<Row className="relative" gutter={[16, 16]}> <Row className="relative" gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<Row justify="end"> <Row justify="end">
@ -277,8 +279,10 @@ const LogsViewer = () => {
</Row> </Row>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Card className="h-full" data-testid="summary-card"> <Space
<Space className="p-md w-full" direction="vertical"> className="p-md w-full"
data-testid="summary-card"
direction="vertical">
<Typography.Title level={5}> <Typography.Title level={5}>
{t('label.summary')} {t('label.summary')}
</Typography.Title> </Typography.Title>
@ -304,14 +308,12 @@ const LogsViewer = () => {
</Row> </Row>
</div> </div>
</Space> </Space>
</Card>
</Col> </Col>
</Row> </Row>
) : ( ) : (
<LogViewerSkeleton /> <LogViewerSkeleton />
)} )}
</Card> </PageLayoutV1>
</div>
); );
}; };

View File

@ -254,6 +254,8 @@ const TableDetailsPageV1 = () => {
entity: t('label.resource-permission-lowercase'), entity: t('label.resource-permission-lowercase'),
}) })
); );
} finally {
setLoading(false);
} }
}, },
[getEntityPermissionByFqn, setTablePermissions] [getEntityPermissionByFqn, setTablePermissions]
@ -862,7 +864,12 @@ const TableDetailsPageV1 = () => {
} }
if (!(tablePermissions.ViewAll || tablePermissions.ViewBasic)) { if (!(tablePermissions.ViewAll || tablePermissions.ViewBasic)) {
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />; return (
<ErrorPlaceHolder
className="m-0"
type={ERROR_PLACEHOLDER_TYPE.PERMISSION}
/>
);
} }
if (!tableDetails) { if (!tableDetails) {

View File

@ -977,3 +977,7 @@ export const getEntityDetailLink = (
return path; return path;
}; };
export const getPartialNameFromTopicFQN = (fqn: string): string => {
return Fqn.split(fqn).slice(2).join(FQN_SEPARATOR_CHAR);
};

View File

@ -14,6 +14,8 @@
import { OPEN_METADATA } from 'constants/service-guide.constant'; import { OPEN_METADATA } from 'constants/service-guide.constant';
import { isUndefined, startCase } from 'lodash'; import { isUndefined, startCase } from 'lodash';
import { IngestionPipeline } from '../generated/entity/services/ingestionPipelines/ingestionPipeline'; import { IngestionPipeline } from '../generated/entity/services/ingestionPipelines/ingestionPipeline';
import { getNameFromFQN } from './CommonUtils';
import Fqn from './Fqn';
import { getSettingsPathFromPipelineType } from './IngestionUtils'; import { getSettingsPathFromPipelineType } from './IngestionUtils';
import { getLogEntityPath } from './RouterUtils'; import { getLogEntityPath } from './RouterUtils';
@ -32,7 +34,8 @@ export const getLogBreadCrumbs = (
ingestionName: string, ingestionName: string,
ingestionDetails: IngestionPipeline | undefined ingestionDetails: IngestionPipeline | undefined
) => { ) => {
if (ingestionName.split('.')[0] === OPEN_METADATA && ingestionDetails) { const updateIngestionName = Fqn.split(ingestionName);
if (updateIngestionName.includes(OPEN_METADATA) && ingestionDetails) {
return [ return [
{ {
name: startCase(ingestionDetails.pipelineType), name: startCase(ingestionDetails.pipelineType),
@ -40,7 +43,7 @@ export const getLogBreadCrumbs = (
activeTitle: true, activeTitle: true,
}, },
{ {
name: startCase(ingestionName.split('.')[1]), name: getNameFromFQN(ingestionName),
url: '', url: '',
activeTitle: true, activeTitle: true,
}, },
@ -50,7 +53,7 @@ export const getLogBreadCrumbs = (
return []; return [];
} }
const urlPath = [serviceType, ...ingestionName.split('.')]; const urlPath = [serviceType, ...updateIngestionName];
return urlPath.map((path, index) => { return urlPath.map((path, index) => {
return { return {

View File

@ -124,7 +124,11 @@ import {
PipelineServiceType, PipelineServiceType,
} from '../generated/entity/services/pipelineService'; } from '../generated/entity/services/pipelineService';
import { ServicesType } from '../interface/service.interface'; import { ServicesType } from '../interface/service.interface';
import { getEntityDeleteMessage, pluralize } from './CommonUtils'; import {
getEntityDeleteMessage,
pluralize,
replaceAllSpacialCharWith_,
} from './CommonUtils';
import { getDashboardURL } from './DashboardServiceUtils'; import { getDashboardURL } from './DashboardServiceUtils';
import { getBrokers } from './MessagingServiceUtils'; import { getBrokers } from './MessagingServiceUtils';
import { showErrorToast } from './ToastUtils'; import { showErrorToast } from './ToastUtils';
@ -604,7 +608,9 @@ export const getIngestionName = (
IngestionPipelineType.Dbt, IngestionPipelineType.Dbt,
].includes(type) ].includes(type)
) { ) {
return `${serviceName}_${type}_${cryptoRandomString({ return `${replaceAllSpacialCharWith_(
serviceName
)}_${type}_${cryptoRandomString({
length: 8, length: 8,
type: 'alphanumeric', type: 'alphanumeric',
})}`; })}`;

View File

@ -51,12 +51,13 @@ import {
getPipelineDetailsPath, getPipelineDetailsPath,
getServiceDetailsPath, getServiceDetailsPath,
getTableDetailsPath, getTableDetailsPath,
getTableTabPath,
getTagsDetailsPath, getTagsDetailsPath,
getTopicDetailsPath, getTopicDetailsPath,
TEXT_BODY_COLOR, TEXT_BODY_COLOR,
} from '../constants/constants'; } from '../constants/constants';
import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constants'; import { GlobalSettingsMenuCategory } from '../constants/GlobalSettings.constants';
import { EntityType, FqnPart } from '../enums/entity.enum'; import { EntityTabs, EntityType, FqnPart } from '../enums/entity.enum';
import { SearchIndex } from '../enums/search.enum'; import { SearchIndex } from '../enums/search.enum';
import { ConstraintTypes, PrimaryTableDataTypes } from '../enums/table.enum'; import { ConstraintTypes, PrimaryTableDataTypes } from '../enums/table.enum';
import { import {
@ -272,6 +273,12 @@ export const getEntityLink = (
case EntityType.DASHBOARD_DATA_MODEL: case EntityType.DASHBOARD_DATA_MODEL:
return getDataModelDetailsPath(fullyQualifiedName); return getDataModelDetailsPath(fullyQualifiedName);
case EntityType.TEST_CASE:
return `${getTableTabPath(
getTableFQNFromColumnFQN(fullyQualifiedName),
EntityTabs.PROFILER
)}?activeTab=Data Quality`;
case SearchIndex.TABLE: case SearchIndex.TABLE:
case EntityType.TABLE: case EntityType.TABLE:
default: default: