metapilot cleanup (#15144)

This commit is contained in:
Karan Hotchandani 2024-02-12 16:36:13 +05:30 committed by GitHub
parent b3401a32de
commit e5fad139db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 81 additions and 993 deletions

View File

@ -25,7 +25,6 @@ import DomainProvider from './components/Domain/DomainProvider/DomainProvider';
import { EntityExportModalProvider } from './components/Entity/EntityExportModalProvider/EntityExportModalProvider.component';
import ErrorBoundary from './components/ErrorBoundary/ErrorBoundary';
import GlobalSearchProvider from './components/GlobalSearchProvider/GlobalSearchProvider';
import MetaPilotProvider from './components/MetaPilot/MetaPilotProvider/MetaPilotProvider';
import PermissionProvider from './components/PermissionProvider/PermissionProvider';
import TourProvider from './components/TourProvider/TourProvider';
import WebAnalyticsProvider from './components/WebAnalytics/WebAnalyticsProvider';
@ -51,11 +50,9 @@ const App: FC = () => {
<WebSocketProvider>
<GlobalSearchProvider>
<DomainProvider>
<MetaPilotProvider>
<EntityExportModalProvider>
<AppRouter />
</EntityExportModalProvider>
</MetaPilotProvider>
<EntityExportModalProvider>
<AppRouter />
</EntityExportModalProvider>
</DomainProvider>
</GlobalSearchProvider>
</WebSocketProvider>

View File

@ -21,8 +21,6 @@ import SignUpPage from '../../pages/SignUp/SignUpPage';
import Appbar from '../AppBar/Appbar';
import AuthenticatedAppRouter from '../AppRouter/AuthenticatedAppRouter';
import { useAuthContext } from '../Auth/AuthProviders/AuthProvider';
import { useMetaPilotContext } from '../MetaPilot/MetaPilotProvider/MetaPilotProvider';
import MetaPilotSidebar from '../MetaPilot/MetaPilotSidebar/MetaPilotSidebar';
import LeftSidebar from '../MyData/LeftSidebar/LeftSidebar.component';
import './app-container.less';
@ -30,7 +28,6 @@ const AppContainer = () => {
const { i18n } = useTranslation();
const { Header, Sider, Content } = Layout;
const { currentUser } = useAuthContext();
const { suggestionsVisible } = useMetaPilotContext();
const isDirectionRTL = useMemo(() => i18n.dir() === 'rtl', [i18n]);
@ -56,11 +53,6 @@ const AppContainer = () => {
<Content className="main-content">
<AuthenticatedAppRouter />
</Content>
{suggestionsVisible && (
<Sider className="right-sidebar-col" width={340}>
<MetaPilotSidebar />
</Sider>
)}
</Layout>
</Layout>
</Layout>

View File

@ -1,95 +0,0 @@
/*
* Copyright 2024 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 { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Card, Space, Typography } from 'antd';
import React, { useLayoutEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as SuggestionsIcon } from '../../../assets/svg/ic-suggestions.svg';
import { ReactComponent as MetaPilotIcon } from '../../../assets/svg/MetaPilotApplication.svg';
import RichTextEditorPreviewer from '../../common/RichTextEditor/RichTextEditorPreviewer';
import { useMetaPilotContext } from '../MetaPilotProvider/MetaPilotProvider';
import { SuggestionAction } from '../MetaPilotProvider/MetaPilotProvider.interface';
import { MetaPilotDescriptionAlertProps } from './MetaPilotDescriptionAlert.interface';
const MetaPilotDescriptionAlert = ({
showHeading = true,
suggestion,
hasEditAccess = false,
}: MetaPilotDescriptionAlertProps) => {
const { t } = useTranslation();
const { onUpdateActiveSuggestion, acceptRejectSuggestion } =
useMetaPilotContext();
useLayoutEffect(() => {
const element = document.querySelector('.suggested-description-card');
if (element) {
element.scrollIntoView({ block: 'center', behavior: 'smooth' });
}
}, []);
if (!suggestion) {
return null;
}
return (
<Space
className="schema-description d-flex"
data-testid="asset-description-container"
direction="vertical"
size={12}>
{showHeading && (
<Space size={4}>
<Typography.Text className="right-panel-label">
{t('label.description')}
</Typography.Text>
<MetaPilotIcon className="d-flex" height={24} width={24} />
</Space>
)}
<Card className="suggested-description-card">
<div className="d-flex m-b-xs justify-between">
<div className="d-flex items-center gap-2">
<SuggestionsIcon height={20} width={20} />
<Typography.Text className="m-b-0 font-medium text-md">
{t('label.metapilot-suggested-description')}
</Typography.Text>
</div>
<CloseOutlined onClick={() => onUpdateActiveSuggestion(undefined)} />
</div>
<RichTextEditorPreviewer markdown={suggestion.description ?? ''} />
{hasEditAccess && (
<div className="d-flex justify-end p-t-sm gap-2">
<Button
ghost
icon={<CloseOutlined />}
type="primary"
onClick={() =>
acceptRejectSuggestion(suggestion, SuggestionAction.Reject)
}>
{t('label.reject')}
</Button>
<Button
icon={<CheckOutlined />}
type="primary"
onClick={() =>
acceptRejectSuggestion(suggestion, SuggestionAction.Accept)
}>
{t('label.accept')}
</Button>
</div>
)}
</Card>
</Space>
);
};
export default MetaPilotDescriptionAlert;

View File

@ -1,19 +0,0 @@
/*
* Copyright 2024 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 { Suggestion } from '../../../generated/entity/feed/suggestion';
export interface MetaPilotDescriptionAlertProps {
showHeading?: boolean;
suggestion: Suggestion;
hasEditAccess?: boolean;
}

View File

@ -1,96 +0,0 @@
/*
* Copyright 2024 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 { CheckOutlined, CloseOutlined } from '@ant-design/icons';
import { Button, Card, Space, Typography } from 'antd';
import React, { useLayoutEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as QuestionMarkIcon } from '../../../assets/svg/ic-question-mark.svg';
import { ReactComponent as SuggestionsIcon } from '../../../assets/svg/ic-suggestions.svg';
import RichTextEditorPreviewer from '../../common/RichTextEditor/RichTextEditorPreviewer';
import { useMetaPilotContext } from '../MetaPilotProvider/MetaPilotProvider';
import { SuggestionAction } from '../MetaPilotProvider/MetaPilotProvider.interface';
import './metapilot-popover-content.less';
import { MetaPilotPopoverContentProps } from './MetaPilotPopoverContent.interface';
const MetaPilotPopoverContent = ({
suggestion,
hasEditAccess = false,
}: MetaPilotPopoverContentProps) => {
const { t } = useTranslation();
const { acceptRejectSuggestion } = useMetaPilotContext();
useLayoutEffect(() => {
const element = document.querySelector('.has-suggestion');
if (element) {
element.scrollIntoView({ block: 'center', behavior: 'smooth' });
}
}, []);
if (!suggestion) {
return null;
}
return (
<Space
className="schema-description d-flex"
data-testid="asset-description-container"
direction="vertical"
size={12}>
<Card className="card-padding-0 card-body-border-none">
<div className="p-sm">
<SuggestionsIcon
className="float-left m-r-xs"
height={16}
width={16}
/>
<RichTextEditorPreviewer markdown={suggestion.description ?? ''} />
</div>
<div className="p-xs popover-card-footer">
<div className="d-flex items-center text-xs">
<QuestionMarkIcon height={20} width={20} />
<Typography.Text className="m-l-xs">
{t('label.generated-by')}
</Typography.Text>
<Typography.Text strong className="m-l-xss text-primary">
{t('label.metapilot')}
</Typography.Text>
</div>
{hasEditAccess && (
<div className="d-flex gap-2">
<Button
ghost
icon={<CloseOutlined />}
size="small"
type="primary"
onClick={() =>
acceptRejectSuggestion(suggestion, SuggestionAction.Reject)
}
/>
<Button
icon={<CheckOutlined />}
size="small"
type="primary"
onClick={() =>
acceptRejectSuggestion(suggestion, SuggestionAction.Accept)
}
/>
</div>
)}
</div>
</Card>
</Space>
);
};
export default MetaPilotPopoverContent;

View File

@ -1,19 +0,0 @@
/*
* Copyright 2024 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 { Suggestion } from '../../../generated/entity/feed/suggestion';
export interface MetaPilotPopoverContentProps {
showHeading?: boolean;
suggestion: Suggestion;
hasEditAccess?: boolean;
}

View File

@ -1,32 +0,0 @@
/*
* Copyright 2024 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 url('../../../styles/variables.less');
.popover-card-footer {
background-color: @grey-1;
display: flex;
align-items: center;
justify-content: space-between;
}
.metapilot-popover {
width: 350px;
.ant-popover-inner-content {
padding: 0;
}
.markdown-parser .toastui-editor-contents {
p {
color: @text-grey-muted;
}
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2024 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 { ReactNode } from 'react';
import { Suggestion } from '../../../generated/entity/feed/suggestion';
export interface MetaPilotContextType {
suggestionsVisible: boolean;
isMetaPilotEnabled: boolean;
onToggleSuggestionsVisible: (state: boolean) => void;
activeSuggestion?: Suggestion;
suggestions: Suggestion[];
loading: boolean;
entityFqn: string;
onUpdateActiveSuggestion: (suggestion?: Suggestion) => void;
fetchSuggestions: (entityFqn: string) => void;
acceptRejectSuggestion: (
suggestion: Suggestion,
action: SuggestionAction
) => void;
onUpdateEntityFqn: (entityFqn: string) => void;
resetMetaPilot: () => void;
initMetaPilot: (
entityFqn: string,
refreshEntity?: (suggestion: Suggestion) => void
) => void;
}
export interface MetaPilotContextProps {
children: ReactNode;
}
export enum SuggestionAction {
Accept = 'accept',
Reject = 'reject',
}

View File

@ -1,194 +0,0 @@
/*
* Copyright 2024 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 { Button } from 'antd';
import { AxiosError } from 'axios';
import { isEmpty } from 'lodash';
import React, {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as MetaPilotIcon } from '../../../assets/svg/MetaPilotApplication.svg';
import { Suggestion } from '../../../generated/entity/feed/suggestion';
import { Include } from '../../../generated/type/include';
import { getApplicationByName } from '../../../rest/applicationAPI';
import {
getMetaPilotSuggestionsList,
updateSuggestionStatus,
} from '../../../rest/suggestionsAPI';
import { showErrorToast } from '../../../utils/ToastUtils';
import { usePermissionProvider } from '../../PermissionProvider/PermissionProvider';
import {
MetaPilotContextProps,
MetaPilotContextType,
SuggestionAction,
} from './MetaPilotProvider.interface';
export const MetaPilotContext = createContext({} as MetaPilotContextType);
const MetaPilotProvider = ({ children }: MetaPilotContextProps) => {
const { t } = useTranslation();
const [suggestionsVisible, setSuggestionsVisible] = useState(false);
const [isMetaPilotEnabled, setIsMetaPilotEnabled] = useState(false);
const [activeSuggestion, setActiveSuggestion] = useState<
Suggestion | undefined
>();
const [entityFqn, setEntityFqn] = useState('');
const [suggestions, setSuggestions] = useState<Suggestion[]>([]);
const [loading, setLoading] = useState(false);
const refreshEntity = useRef<(suggestion: Suggestion) => void>();
const { permissions } = usePermissionProvider();
const fetchMetaPilotAppDetails = useCallback(async () => {
try {
await getApplicationByName('MetaPilotApplication', {
fields: 'owner',
include: Include.All,
});
setIsMetaPilotEnabled(true);
} catch (error) {
setIsMetaPilotEnabled(false);
}
}, []);
const fetchSuggestions = useCallback(async (entityFQN: string) => {
setLoading(true);
try {
const res = await getMetaPilotSuggestionsList({
entityFQN,
});
setSuggestions(res.data);
} catch (err) {
showErrorToast(
err as AxiosError,
t('server.entity-fetch-error', {
entity: t('label.lineage-data-lowercase'),
})
);
} finally {
setLoading(false);
}
}, []);
const acceptRejectSuggestion = useCallback(
async (suggestion: Suggestion, status: SuggestionAction) => {
try {
await updateSuggestionStatus(suggestion, status);
await fetchSuggestions(entityFqn);
setActiveSuggestion(undefined);
if (status === SuggestionAction.Accept) {
refreshEntity.current?.(suggestion);
}
} catch (err) {
showErrorToast(err as AxiosError);
}
},
[entityFqn, refreshEntity]
);
const onToggleSuggestionsVisible = useCallback((state: boolean) => {
setSuggestionsVisible(state);
}, []);
const onUpdateActiveSuggestion = useCallback((suggestion?: Suggestion) => {
setActiveSuggestion(suggestion);
}, []);
const onUpdateEntityFqn = useCallback((entityFqn: string) => {
setEntityFqn(entityFqn);
}, []);
const resetMetaPilot = useCallback(() => {
setSuggestionsVisible(false);
setActiveSuggestion(undefined);
setEntityFqn('');
}, []);
const initMetaPilot = useCallback(
(entityFqn: string, refreshEntityFn?: (suggestion: Suggestion) => void) => {
setEntityFqn(entityFqn);
refreshEntity.current = refreshEntityFn;
},
[]
);
useEffect(() => {
if (isMetaPilotEnabled && !isEmpty(entityFqn)) {
fetchSuggestions(entityFqn);
}
}, [isMetaPilotEnabled, entityFqn]);
useEffect(() => {
if (!isEmpty(permissions)) {
fetchMetaPilotAppDetails();
}
}, [permissions]);
const metaPilotContextObj = useMemo(() => {
return {
suggestionsVisible,
isMetaPilotEnabled,
suggestions,
activeSuggestion,
entityFqn,
loading,
onToggleSuggestionsVisible,
onUpdateEntityFqn,
onUpdateActiveSuggestion,
fetchSuggestions,
acceptRejectSuggestion,
initMetaPilot,
resetMetaPilot,
};
}, [
suggestionsVisible,
isMetaPilotEnabled,
suggestions,
activeSuggestion,
entityFqn,
loading,
onToggleSuggestionsVisible,
onUpdateEntityFqn,
onUpdateActiveSuggestion,
fetchSuggestions,
acceptRejectSuggestion,
initMetaPilot,
resetMetaPilot,
]);
return (
<MetaPilotContext.Provider value={metaPilotContextObj}>
{children}
{isMetaPilotEnabled && (
<div className="floating-button-container">
<Button
icon={<MetaPilotIcon height={60} width={60} />}
shape="circle"
style={{ height: '60px', width: '60px' }}
type="text"
onClick={() => onToggleSuggestionsVisible(true)}
/>
</div>
)}
</MetaPilotContext.Provider>
);
};
export const useMetaPilotContext = () => useContext(MetaPilotContext);
export default MetaPilotProvider;

View File

@ -1,102 +0,0 @@
/*
* Copyright 2024 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 { CloseOutlined } from '@ant-design/icons';
import { Card, Drawer, Typography } from 'antd';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as MetaPilotIcon } from '../../../assets/svg/MetaPilotApplication.svg';
import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum';
import { Suggestion } from '../../../generated/entity/feed/suggestion';
import EntityLink from '../../../utils/EntityLink';
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import RichTextEditorPreviewer from '../../common/RichTextEditor/RichTextEditorPreviewer';
import Loader from '../../Loader/Loader';
import { useMetaPilotContext } from '../MetaPilotProvider/MetaPilotProvider';
import './meta-pilot-sidebar.less';
const MetaPilotSidebar = () => {
const { t } = useTranslation();
const {
onUpdateActiveSuggestion,
suggestions,
loading,
suggestionsVisible,
onToggleSuggestionsVisible,
} = useMetaPilotContext();
const descriptionsView = useMemo(() => {
return suggestions.map((item: Suggestion) => {
return (
<Card
className="suggestion-card m-t-xs border-primary"
key={item.id}
onClick={() => onUpdateActiveSuggestion(item)}>
<RichTextEditorPreviewer
enableSeeMoreVariant={false}
markdown={item.description ?? ''}
/>
<Typography.Text className="text-xss text-grey-muted">
{EntityLink.getTableColumnName(item.entityLink) ??
EntityLink.getEntityFqn(item.entityLink)}
</Typography.Text>
</Card>
);
});
}, [suggestions]);
return (
<Drawer
destroyOnClose
className="meta-pilot-drawer"
closable={false}
extra={
<CloseOutlined
data-testid="entity-panel-close-icon"
onClick={() => onToggleSuggestionsVisible(false)}
/>
}
getContainer={false}
headerStyle={{ padding: 16 }}
mask={false}
open={suggestionsVisible}
title={
<div className="d-flex items-center gap-2">
<MetaPilotIcon height={24} width={24} />
<Typography.Title className="m-b-0" level={4}>
{t('label.metapilot')}
</Typography.Title>
</div>
}
width={340}>
{loading ? (
<Loader />
) : (
<>
{suggestions?.length === 0 && (
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.NO_DATA} />
)}
{suggestions.length > 0 && (
<>
<Typography.Text className="text-grey-muted m-m-xs">
{t('label.suggested-description-plural')}
</Typography.Text>
{descriptionsView}
</>
)}
</>
)}
</Drawer>
);
};
export default MetaPilotSidebar;

View File

@ -1,35 +0,0 @@
/*
* Copyright 2024 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 url('../../../styles/variables.less');
.meta-pilot-drawer {
.ant-drawer-content {
background-color: #f6f9fd;
}
.ant-drawer-body {
padding: 0 16px 16px;
}
.suggestion-card {
border-radius: 10px;
cursor: pointer;
&:hover {
p {
color: @primary-color;
}
}
}
.ant-drawer-content-wrapper {
box-shadow: none !important;
}
}

View File

@ -11,21 +11,14 @@
* limitations under the License.
*/
import { Button, Popover, Space } from 'antd';
import classNames from 'classnames';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Button, Space } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { ReactComponent as EditIcon } from '../../assets/svg/edit-new.svg';
import RichTextEditorPreviewer from '../../components/common/RichTextEditor/RichTextEditorPreviewer';
import { DE_ACTIVE_COLOR } from '../../constants/constants';
import { EntityField } from '../../constants/Feeds.constants';
import { EntityType } from '../../enums/entity.enum';
import EntityTasks from '../../pages/TasksPage/EntityTasks/EntityTasks.component';
import EntityLink from '../../utils/EntityLink';
import { getEntityFeedLink } from '../../utils/EntityUtils';
import MetaPilotPopoverContent from '../MetaPilot/MetaPilotPopoverContent/MetaPilotPopoverContent.component';
import { useMetaPilotContext } from '../MetaPilot/MetaPilotProvider/MetaPilotProvider';
import { TableDescriptionProps } from './TableDescription.interface';
const TableDescription = ({
@ -39,118 +32,49 @@ const TableDescription = ({
onThreadLinkSelect,
}: TableDescriptionProps) => {
const { t } = useTranslation();
const { activeSuggestion, suggestions, onUpdateActiveSuggestion } =
useMetaPilotContext();
const [showSuggestionPopover, setShowSuggestionPopover] = useState(false);
const entityLink = useMemo(
() =>
entityType === EntityType.TABLE
? EntityLink.getTableEntityLink(
entityFqn,
columnData.record?.name ?? ''
)
: getEntityFeedLink(entityType, columnData.fqn),
[entityType, entityFqn]
);
const suggestionForEmptyData = useMemo(() => {
if (isEmpty(columnData.field ?? ''.trim())) {
return suggestions.find(
(suggestion) => suggestion.entityLink === entityLink
);
}
return null;
}, [suggestions, columnData.field, entityLink]);
const suggestionData = useMemo(() => {
if (activeSuggestion?.entityLink === entityLink) {
return (
<MetaPilotPopoverContent
hasEditAccess={hasEditPermission}
suggestion={activeSuggestion}
/>
);
}
return null;
}, [hasEditPermission, suggestionForEmptyData, activeSuggestion]);
useEffect(() => {
if (activeSuggestion?.entityLink === entityLink) {
setShowSuggestionPopover(true);
} else {
setShowSuggestionPopover(false);
}
}, [activeSuggestion, entityLink]);
const onPopoverOpenChange = useCallback(
(data: boolean) => {
setShowSuggestionPopover(data);
if (!data) {
onUpdateActiveSuggestion();
}
},
[onUpdateActiveSuggestion]
);
return (
<Popover
align={{ targetOffset: [0, 40] }}
content={suggestionData}
open={showSuggestionPopover}
overlayClassName="metapilot-popover"
overlayStyle={{
bottom: 'auto',
}}
placement="bottom"
trigger="click"
onOpenChange={onPopoverOpenChange}>
<Space
className={classNames('hover-icon-group', {
'has-suggestion': Boolean(suggestionData),
})}
data-testid="description"
direction="vertical"
id={`field-description-${index}`}>
{columnData.field ? (
<RichTextEditorPreviewer markdown={columnData.field} />
) : (
<span className="text-grey-muted">
{t('label.no-entity', {
entity: t('label.description'),
})}
</span>
)}
{!isReadOnly ? (
<Space align="baseline" size="middle">
{hasEditPermission && (
<Button
className="cursor-pointer hover-cell-icon"
data-testid="edit-button"
style={{
color: DE_ACTIVE_COLOR,
padding: 0,
border: 'none',
background: 'transparent',
}}
onClick={onClick}>
<EditIcon />
</Button>
)}
<Space
className="hover-icon-group"
data-testid="description"
direction="vertical"
id={`field-description-${index}`}>
{columnData.field ? (
<RichTextEditorPreviewer markdown={columnData.field} />
) : (
<span className="text-grey-muted">
{t('label.no-entity', {
entity: t('label.description'),
})}
</span>
)}
{!isReadOnly ? (
<Space align="baseline" size="middle">
{hasEditPermission && (
<Button
className="cursor-pointer hover-cell-icon"
data-testid="edit-button"
style={{
color: DE_ACTIVE_COLOR,
padding: 0,
border: 'none',
background: 'transparent',
}}
onClick={onClick}>
<EditIcon />
</Button>
)}
<EntityTasks
data={columnData}
entityFqn={entityFqn}
entityTaskType={EntityField.DESCRIPTION}
entityType={entityType}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
) : null}
</Space>
</Popover>
<EntityTasks
data={columnData}
entityFqn={entityFqn}
entityTaskType={EntityField.DESCRIPTION}
entityType={entityType}
onThreadLinkSelect={onThreadLinkSelect}
/>
</Space>
) : null}
</Space>
);
};

View File

@ -74,12 +74,6 @@ jest.mock('../../TableTags/TableTags.component', () =>
))
);
jest.mock('../../MetaPilot/MetaPilotProvider/MetaPilotProvider', () => ({
useMetaPilotContext: jest.fn().mockReturnValue({
suggestions: [],
}),
}));
jest.mock('../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () =>
jest
.fn()

View File

@ -43,14 +43,6 @@ jest.mock('react-router-dom', () => ({
useParams: jest.fn().mockImplementation(() => mockParams),
}));
jest.mock('../MetaPilot/MetaPilotProvider/MetaPilotProvider', () => ({
useMetaPilotContext: jest.fn().mockReturnValue({
suggestions: [],
initMetaPilot: jest.fn(),
resetMetaPilot: jest.fn(),
}),
}));
jest.mock('../../rest/rolesAPIV1', () => ({
getRoles: jest.fn().mockImplementation(() => Promise.resolve(mockUserRole)),
}));

View File

@ -12,11 +12,9 @@
*/
import Icon from '@ant-design/icons';
import { Card, Popover, Space, Tooltip, Typography } from 'antd';
import classNames from 'classnames';
import { Card, Space, Tooltip, Typography } from 'antd';
import { t } from 'i18next';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import React, { useCallback, useMemo } from 'react';
import { useHistory } from 'react-router';
import { ReactComponent as CommentIcon } from '../../../assets/svg/comment.svg';
import { ReactComponent as EditIcon } from '../../../assets/svg/edit-new.svg';
@ -31,8 +29,6 @@ import {
getUpdateDescriptionPath,
TASK_ENTITIES,
} from '../../../utils/TasksUtils';
import MetaPilotPopoverContent from '../../MetaPilot/MetaPilotPopoverContent/MetaPilotPopoverContent.component';
import { useMetaPilotContext } from '../../MetaPilot/MetaPilotProvider/MetaPilotProvider';
import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
import RichTextEditorPreviewer from '../RichTextEditor/RichTextEditorPreviewer';
const { Text } = Typography;
@ -77,9 +73,6 @@ const DescriptionV1 = ({
reduceDescription,
}: Props) => {
const history = useHistory();
const { activeSuggestion, suggestions, onUpdateActiveSuggestion } =
useMetaPilotContext();
const [showSuggestionPopover, setShowSuggestionPopover] = useState(false);
const handleRequestDescription = useCallback(() => {
history.push(
getRequestDescriptionPath(entityType as string, entityFqn as string)
@ -92,18 +85,8 @@ const DescriptionV1 = ({
);
}, [entityType, entityFqn]);
const { entityLink, entityLinkWithoutField } = useMemo(() => {
const entityLink = getEntityFeedLink(
entityType,
entityFqn,
EntityField.DESCRIPTION
);
const entityLinkWithoutField = getEntityFeedLink(entityType, entityFqn);
return {
entityLink,
entityLinkWithoutField,
};
const entityLink = useMemo(() => {
return getEntityFeedLink(entityType, entityFqn, EntityField.DESCRIPTION);
}, [entityType, entityFqn]);
const taskActionButton = useMemo(() => {
@ -174,89 +157,38 @@ const DescriptionV1 = ({
]
);
const suggestionForEmptyData = useMemo(() => {
if (isEmpty(description.trim())) {
return suggestions.find(
(suggestion) => suggestion.entityLink === entityLinkWithoutField
);
}
return null;
}, [suggestions, description]);
const suggestionData = useMemo(() => {
if (activeSuggestion?.entityLink === entityLinkWithoutField) {
return (
<MetaPilotPopoverContent
hasEditAccess={hasEditAccess}
suggestion={activeSuggestion}
/>
);
}
return null;
}, [hasEditAccess, suggestionForEmptyData, activeSuggestion]);
useEffect(() => {
if (activeSuggestion?.entityLink === entityLinkWithoutField) {
setShowSuggestionPopover(true);
} else {
setShowSuggestionPopover(false);
}
}, [activeSuggestion, entityLinkWithoutField]);
const onPopoverOpenChange = useCallback(
(data: boolean) => {
setShowSuggestionPopover(data);
if (!data) {
onUpdateActiveSuggestion();
}
},
[onUpdateActiveSuggestion]
);
const content = (
<Popover
content={suggestionData}
open={showSuggestionPopover}
overlayClassName="metapilot-popover"
placement="bottom"
trigger="click"
onOpenChange={onPopoverOpenChange}>
<Space
className={classNames('schema-description d-flex', {
'has-suggestion': Boolean(suggestionData),
})}
data-testid="asset-description-container"
direction="vertical"
size={16}>
<Space size="middle">
<Text className="right-panel-label">{t('label.description')}</Text>
{showActions && actionButtons}
</Space>
<div>
{description.trim() ? (
<RichTextEditorPreviewer
className={reduceDescription ? 'max-two-lines' : ''}
enableSeeMoreVariant={!removeBlur}
markdown={description}
/>
) : (
<span>{t('label.no-description')}</span>
)}
<ModalWithMarkdownEditor
header={t('label.edit-description-for', { entityName })}
placeholder={t('label.enter-entity', {
entity: t('label.description'),
})}
value={description}
visible={Boolean(isEdit)}
onCancel={onCancel}
onSave={onDescriptionUpdate}
/>
</div>
<Space
className="schema-description d-flex"
data-testid="asset-description-container"
direction="vertical"
size={16}>
<Space size="middle">
<Text className="right-panel-label">{t('label.description')}</Text>
{showActions && actionButtons}
</Space>
</Popover>
<div>
{description.trim() ? (
<RichTextEditorPreviewer
className={reduceDescription ? 'max-two-lines' : ''}
enableSeeMoreVariant={!removeBlur}
markdown={description}
/>
) : (
<span>{t('label.no-description')}</span>
)}
<ModalWithMarkdownEditor
header={t('label.edit-description-for', { entityName })}
placeholder={t('label.enter-entity', {
entity: t('label.description'),
})}
value={description}
visible={Boolean(isEdit)}
onCancel={onCancel}
onSave={onDescriptionUpdate}
/>
</div>
</Space>
);
return wrapInCard ? <Card>{content}</Card> : content;

View File

@ -30,17 +30,6 @@ jest.mock('../../components/PermissionProvider/PermissionProvider', () => ({
})),
}));
jest.mock(
'../../components/MetaPilot/MetaPilotProvider/MetaPilotProvider',
() => ({
useMetaPilotContext: jest.fn().mockReturnValue({
suggestions: [],
initMetaPilot: jest.fn(),
resetMetaPilot: jest.fn(),
}),
})
);
jest.mock('../../rest/tableAPI', () => ({
getTableDetailsByFQN: jest.fn().mockImplementation(() =>
Promise.resolve({

View File

@ -33,7 +33,6 @@ import EntityRightPanel from '../../components/Entity/EntityRightPanel/EntityRig
import Lineage from '../../components/Lineage/Lineage.component';
import LineageProvider from '../../components/LineageProvider/LineageProvider';
import Loader from '../../components/Loader/Loader';
import { useMetaPilotContext } from '../../components/MetaPilot/MetaPilotProvider/MetaPilotProvider';
import { EntityName } from '../../components/Modals/EntityNameModal/EntityNameModal.interface';
import PageLayoutV1 from '../../components/PageLayoutV1/PageLayoutV1';
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
@ -63,7 +62,6 @@ import {
import { CreateThread } from '../../generated/api/feed/createThread';
import { Tag } from '../../generated/entity/classification/tag';
import { JoinedWith, Table } from '../../generated/entity/data/table';
import { Suggestion } from '../../generated/entity/feed/suggestion';
import { ThreadType } from '../../generated/entity/feed/thread';
import { TagLabel } from '../../generated/type/tagLabel';
import { useFqn } from '../../hooks/useFqn';
@ -86,7 +84,6 @@ import {
sortTagsCaseInsensitive,
} from '../../utils/CommonUtils';
import { defaultFields } from '../../utils/DatasetDetailsUtils';
import EntityLink from '../../utils/EntityLink';
import { getEntityName } from '../../utils/EntityUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
@ -117,7 +114,6 @@ const TableDetailsPageV1 = () => {
ThreadType.Conversation
);
const [queryCount, setQueryCount] = useState(0);
const { resetMetaPilot, initMetaPilot } = useMetaPilotContext();
const [loading, setLoading] = useState(!isTourOpen);
const [tablePermissions, setTablePermissions] = useState<OperationPermission>(
@ -281,59 +277,11 @@ const TableDetailsPageV1 = () => {
[getEntityPermissionByFqn, setTablePermissions]
);
const updateDescriptionFromMetaPilot = useCallback(
(suggestion: Suggestion) => {
setTableDetails((prev) => {
if (!prev) {
return;
}
const activeCol = prev?.columns.find((column) => {
return (
EntityLink.getTableEntityLink(
prev.fullyQualifiedName ?? '',
column.name ?? ''
) === suggestion.entityLink
);
});
if (!activeCol) {
return {
...prev,
description: suggestion.description,
};
} else {
const updatedColumns = prev.columns.map((column) => {
if (column.fullyQualifiedName === activeCol.fullyQualifiedName) {
return {
...column,
description: suggestion.description,
};
} else {
return column;
}
});
return {
...prev,
columns: updatedColumns,
};
}
});
},
[]
);
useEffect(() => {
if (tableFqn && updateDescriptionFromMetaPilot) {
if (tableFqn) {
fetchResourcePermission(tableFqn);
initMetaPilot(tableFqn, updateDescriptionFromMetaPilot);
}
return () => {
resetMetaPilot();
};
}, [tableFqn, updateDescriptionFromMetaPilot]);
}, [tableFqn]);
const handleFeedCount = useCallback((data: FeedCounts) => {
setFeedCount(data);

View File

@ -1,43 +0,0 @@
/*
* Copyright 2024 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 { AxiosResponse } from 'axios';
import { PagingResponse } from 'Models';
import { SuggestionAction } from '../components/MetaPilot/MetaPilotProvider/MetaPilotProvider.interface';
import { Suggestion } from '../generated/entity/feed/suggestion';
import { ListParams } from '../interface/API.interface';
import APIClient from './index';
const BASE_URL = '/suggestions';
export type ListSuggestionsParams = ListParams & {
entityFQN?: string;
};
export const getMetaPilotSuggestionsList = async (
params?: ListSuggestionsParams
) => {
const response = await APIClient.get<PagingResponse<Suggestion[]>>(BASE_URL, {
params,
});
return response.data;
};
export const updateSuggestionStatus = (
data: Suggestion,
action: SuggestionAction
): Promise<AxiosResponse> => {
const url = `${BASE_URL}/${data.id}/${action}`;
return APIClient.put(url, {});
};