Feat #10584 : Support Activity tab for DataModel Entity (#10941)

* Supported DataModel in Dashboard Page

* url fqn changes

* Support Activity tab for DataModel Entity

* Remove dataModel resource descriptor

* Fix tasks for dashboard data models

* changes as per comments

* minor fix

* changes as per comments and minor improvements

---------

Co-authored-by: Nahuel Verdugo Revigliono <nahuel@getcollate.io>
This commit is contained in:
Ashish Gupta 2023-04-07 11:35:40 +05:30 committed by GitHub
parent 27984c25f3
commit c997d8c80b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 325 additions and 27 deletions

View File

@ -19,6 +19,7 @@ import static org.openmetadata.schema.type.Relationship.CREATED;
import static org.openmetadata.schema.type.Relationship.IS_ABOUT; import static org.openmetadata.schema.type.Relationship.IS_ABOUT;
import static org.openmetadata.schema.type.Relationship.REPLIED_TO; import static org.openmetadata.schema.type.Relationship.REPLIED_TO;
import static org.openmetadata.service.Entity.DASHBOARD; import static org.openmetadata.service.Entity.DASHBOARD;
import static org.openmetadata.service.Entity.DASHBOARD_DATA_MODEL;
import static org.openmetadata.service.Entity.DATABASE_SCHEMA; import static org.openmetadata.service.Entity.DATABASE_SCHEMA;
import static org.openmetadata.service.Entity.FIELD_DESCRIPTION; import static org.openmetadata.service.Entity.FIELD_DESCRIPTION;
import static org.openmetadata.service.Entity.PIPELINE; import static org.openmetadata.service.Entity.PIPELINE;
@ -61,6 +62,7 @@ import org.openmetadata.schema.api.feed.EntityLinkThreadCount;
import org.openmetadata.schema.api.feed.ResolveTask; import org.openmetadata.schema.api.feed.ResolveTask;
import org.openmetadata.schema.api.feed.ThreadCount; import org.openmetadata.schema.api.feed.ThreadCount;
import org.openmetadata.schema.entity.data.Dashboard; import org.openmetadata.schema.entity.data.Dashboard;
import org.openmetadata.schema.entity.data.DashboardDataModel;
import org.openmetadata.schema.entity.data.DatabaseSchema; import org.openmetadata.schema.entity.data.DatabaseSchema;
import org.openmetadata.schema.entity.data.Pipeline; import org.openmetadata.schema.entity.data.Pipeline;
import org.openmetadata.schema.entity.data.Table; import org.openmetadata.schema.entity.data.Table;
@ -312,6 +314,50 @@ public class FeedRepository {
patch = JsonUtils.getJsonPatch(oldJson, updatedEntityJson); patch = JsonUtils.getJsonPatch(oldJson, updatedEntityJson);
repository.patch(uriInfo, dashboard.getId(), user, patch); repository.patch(uriInfo, dashboard.getId(), user, patch);
break; break;
case DASHBOARD_DATA_MODEL:
DashboardDataModel dashboardDataModel = JsonUtils.readValue(json, DashboardDataModel.class);
oldJson = JsonUtils.pojoToJson(dashboardDataModel);
if (entityLink.getFieldName() != null) {
if (entityLink.getFieldName().equals("columns")) {
Optional<Column> col =
dashboardDataModel.getColumns().stream()
.filter(c -> c.getName().equals(entityLink.getArrayFieldName()))
.findFirst();
if (col.isPresent()) {
Column column = col.get();
if (descriptionTasks.contains(taskType)) {
column.setDescription(newValue);
} else if (tagTasks.contains(taskType)) {
List<TagLabel> tags = JsonUtils.readObjects(newValue, TagLabel.class);
column.setTags(tags);
}
} else {
throw new IllegalArgumentException(
String.format(
"The Column with name '%s' is not found in the dashboard data model.",
entityLink.getArrayFieldName()));
}
} else if (descriptionTasks.contains(taskType) && entityLink.getFieldName().equals(FIELD_DESCRIPTION)) {
dashboardDataModel.setDescription(newValue);
} else if (tagTasks.contains(taskType) && entityLink.getFieldName().equals("tags")) {
List<TagLabel> tags = JsonUtils.readObjects(newValue, TagLabel.class);
dashboardDataModel.setTags(tags);
} else {
// Not supported
throw new IllegalArgumentException(
String.format(UNSUPPORTED_FIELD_NAME_FOR_TASK, entityLink.getFieldName(), task.getType()));
}
} else {
// Not supported
throw new IllegalArgumentException(
String.format(
"The Entity link with no field name - %s is not supported for %s task.",
entityLink, task.getType()));
}
updatedEntityJson = JsonUtils.pojoToJson(dashboardDataModel);
patch = JsonUtils.getJsonPatch(oldJson, updatedEntityJson);
repository.patch(uriInfo, dashboardDataModel.getId(), user, patch);
break;
case PIPELINE: case PIPELINE:
Pipeline pipeline = JsonUtils.readValue(json, Pipeline.class); Pipeline pipeline = JsonUtils.readValue(json, Pipeline.class);
oldJson = JsonUtils.pojoToJson(pipeline); oldJson = JsonUtils.pojoToJson(pipeline);

View File

@ -549,20 +549,6 @@
"EditDescription" "EditDescription"
] ]
}, },
{
"name": "dataModel",
"operations": [
"Create",
"Delete",
"ViewAll",
"EditAll",
"EditDescription",
"EditDisplayName",
"EditTags",
"EditOwner",
"EditLineage"
]
},
{ {
"name": "dashboardDataModel", "name": "dashboardDataModel",
"operations": [ "operations": [

View File

@ -33,6 +33,7 @@ ENTITY_TYPE
| 'testDefinition' | 'testDefinition'
| 'testSuite' | 'testSuite'
| 'testCase' | 'testCase'
| 'dashboardDataModel'
; ;
ENTITY_FIELD ENTITY_FIELD
: 'columns' : 'columns'

View File

@ -51,6 +51,7 @@ export enum AssetsType {
DASHBOARD = 'dashboard', DASHBOARD = 'dashboard',
PIPELINE = 'pipeline', PIPELINE = 'pipeline',
MLMODEL = 'mlmodel', MLMODEL = 'mlmodel',
DASHBOARD_DATA_MODEL = 'dashboardDataModel',
} }
export enum ChangeType { export enum ChangeType {

View File

@ -298,6 +298,7 @@
"feature-plural-used": "Features Used", "feature-plural-used": "Features Used",
"february": "February", "february": "February",
"feed-lowercase": "feed", "feed-lowercase": "feed",
"feed-plural": "Feeds",
"field-change": "Field Change", "field-change": "Field Change",
"field-invalid": "{{field}} is invalid", "field-invalid": "{{field}} is invalid",
"field-plural": "Fields", "field-plural": "Fields",

View File

@ -298,6 +298,7 @@
"feature-plural-used": "Funcionalidades utilizadas", "feature-plural-used": "Funcionalidades utilizadas",
"february": "Febrero", "february": "Febrero",
"feed-lowercase": "feed", "feed-lowercase": "feed",
"feed-plural": "Feeds",
"field-change": "Cambio de Campo", "field-change": "Cambio de Campo",
"field-invalid": "{{field}} es inválido", "field-invalid": "{{field}} es inválido",
"field-plural": "Campos", "field-plural": "Campos",

View File

@ -298,6 +298,7 @@
"feature-plural-used": "Features Used", "feature-plural-used": "Features Used",
"february": "February", "february": "February",
"feed-lowercase": "feed", "feed-lowercase": "feed",
"feed-plural": "Feeds",
"field-change": "Field Change", "field-change": "Field Change",
"field-invalid": "{{field}} est imvalide", "field-invalid": "{{field}} est imvalide",
"field-plural": "Champs", "field-plural": "Champs",

View File

@ -298,6 +298,7 @@
"feature-plural-used": "使用される機能", "feature-plural-used": "使用される機能",
"february": "2月", "february": "2月",
"feed-lowercase": "フィード", "feed-lowercase": "フィード",
"feed-plural": "Feeds",
"field-change": "フィールドを変更", "field-change": "フィールドを変更",
"field-invalid": "{{field}}は不正です", "field-invalid": "{{field}}は不正です",
"field-plural": "フィールド", "field-plural": "フィールド",

View File

@ -298,6 +298,7 @@
"feature-plural-used": "Funções usadas", "feature-plural-used": "Funções usadas",
"february": "Fevereiro", "february": "Fevereiro",
"feed-lowercase": "feed", "feed-lowercase": "feed",
"feed-plural": "Feeds",
"field-change": "Mudança de campo", "field-change": "Mudança de campo",
"field-invalid": "{{field}} inválido", "field-invalid": "{{field}} inválido",
"field-plural": "Campos", "field-plural": "Campos",

View File

@ -298,6 +298,7 @@
"feature-plural-used": "Features Used", "feature-plural-used": "Features Used",
"february": "February", "february": "February",
"feed-lowercase": "feed", "feed-lowercase": "feed",
"feed-plural": "Feeds",
"field-change": "域变动", "field-change": "域变动",
"field-invalid": "{{field}} 无效", "field-invalid": "{{field}} 无效",
"field-plural": "域", "field-plural": "域",

View File

@ -11,9 +11,11 @@
* limitations under the License. * limitations under the License.
*/ */
import { Card, Space, Tabs } from 'antd'; import { Card, Col, Row, Space, Tabs } from 'antd';
import AppState from 'AppState'; import AppState from 'AppState';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
import ActivityFeedList from 'components/ActivityFeed/ActivityFeedList/ActivityFeedList';
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
import Description from 'components/common/description/Description'; import Description from 'components/common/description/Description';
import EntityPageInfo from 'components/common/entityPageInfo/EntityPageInfo'; import EntityPageInfo from 'components/common/entityPageInfo/EntityPageInfo';
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
@ -29,16 +31,22 @@ import {
import SchemaEditor from 'components/schema-editor/SchemaEditor'; import SchemaEditor from 'components/schema-editor/SchemaEditor';
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants'; import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
import { getServiceDetailsPath } from 'constants/constants'; import { getServiceDetailsPath } from 'constants/constants';
import { ENTITY_CARD_CLASS } from 'constants/entity.constants'; import { EntityField } from 'constants/Feeds.constants';
import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil'; import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
import { CSMode } from 'enums/codemirror.enum'; import { CSMode } from 'enums/codemirror.enum';
import { EntityInfo, EntityType } from 'enums/entity.enum'; import { EntityInfo, EntityType } from 'enums/entity.enum';
import { FeedFilter } from 'enums/mydata.enum';
import { ServiceCategory } from 'enums/service.enum'; import { ServiceCategory } from 'enums/service.enum';
import { OwnerType } from 'enums/user.enum'; import { OwnerType } from 'enums/user.enum';
import { compare } from 'fast-json-patch'; import { compare, Operation } from 'fast-json-patch';
import { CreateThread } from 'generated/api/feed/createThread';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel'; import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { Post, Thread, ThreadType } from 'generated/entity/feed/thread';
import { Paging } from 'generated/type/paging';
import { LabelType, State, TagSource } from 'generated/type/tagLabel'; import { LabelType, State, TagSource } from 'generated/type/tagLabel';
import { isUndefined, omitBy } from 'lodash'; import { EntityFieldThreadCount } from 'interface/feed.interface';
import jsonData from 'jsons/en';
import { isUndefined, omitBy, toString } from 'lodash';
import { EntityTags, ExtraInfo } from 'Models'; import { EntityTags, ExtraInfo } from 'Models';
import React, { useCallback, useEffect, useMemo, useState } from 'react'; import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -49,14 +57,22 @@ import {
patchDataModelDetails, patchDataModelDetails,
removeDataModelFollower, removeDataModelFollower,
} from 'rest/dataModelsAPI'; } from 'rest/dataModelsAPI';
import { getAllFeeds, postFeedById, postThread } from 'rest/feedsAPI';
import { import {
getCountBadge,
getCurrentUserId, getCurrentUserId,
getEntityMissingError, getEntityMissingError,
getEntityPlaceHolder, getEntityPlaceHolder,
getFeedCounts,
getOwnerValue, getOwnerValue,
} from 'utils/CommonUtils'; } from 'utils/CommonUtils';
import { getDataModelsDetailPath } from 'utils/DataModelsUtils'; import { getDataModelsDetailPath } from 'utils/DataModelsUtils';
import { getEntityName } from 'utils/EntityUtils'; import { getEntityFeedLink, getEntityName } from 'utils/EntityUtils';
import {
deletePost,
getEntityFieldThreadCounts,
updateThreadData,
} from 'utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils'; import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import { serviceTypeLogo } from 'utils/ServiceUtils'; import { serviceTypeLogo } from 'utils/ServiceUtils';
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils'; import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
@ -77,6 +93,21 @@ const DataModelsPage = () => {
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION); useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
const [dataModelData, setDataModelData] = useState<DashboardDataModel>(); const [dataModelData, setDataModelData] = useState<DashboardDataModel>();
const [threadLink, setThreadLink] = useState<string>('');
const [entityThread, setEntityThread] = useState<Thread[]>([]);
const [isEntityThreadLoading, setIsEntityThreadLoading] =
useState<boolean>(false);
const [paging, setPaging] = useState<Paging>({} as Paging);
const [feedCount, setFeedCount] = useState<number>(0);
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
EntityFieldThreadCount[]
>([]);
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
EntityFieldThreadCount[]
>([]);
// get current user details // get current user details
const currentUser = useMemo( const currentUser = useMemo(
() => AppState.getCurrentUserDetails(), () => AppState.getCurrentUserDetails(),
@ -159,6 +190,110 @@ const DataModelsPage = () => {
]; ];
}, [dataModelData, dashboardDataModelFQN, entityName]); }, [dataModelData, dashboardDataModelFQN, entityName]);
const getFeedData = useCallback(
async (
after?: string,
feedFilter?: FeedFilter,
threadType?: ThreadType
) => {
setIsEntityThreadLoading(true);
!after && setEntityThread([]);
try {
const { data, paging: pagingObj } = await getAllFeeds(
getEntityFeedLink(
EntityType.DASHBOARD_DATA_MODEL,
dashboardDataModelFQN
),
after,
threadType,
feedFilter,
undefined,
currentUser?.id
);
setPaging(pagingObj);
setEntityThread((prevData) => [...prevData, ...data]);
} catch (err) {
showErrorToast(
err as AxiosError,
t('server.entity-fetch-error', {
entity: t('label.feed-plural'),
})
);
} finally {
setIsEntityThreadLoading(false);
}
},
[dashboardDataModelFQN]
);
const getEntityFeedCount = () => {
getFeedCounts(
EntityType.DASHBOARD_DATA_MODEL,
dashboardDataModelFQN,
setEntityFieldThreadCount,
setEntityFieldTaskCount,
setFeedCount
);
};
const deletePostHandler = (
threadId: string,
postId: string,
isThread: boolean
) => {
deletePost(threadId, postId, isThread, setEntityThread);
};
const onThreadLinkSelect = (link: string) => {
setThreadLink(link);
};
const postFeedHandler = (value: string, id: string) => {
const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name;
const data = {
message: value,
from: currentUser,
} as Post;
postFeedById(id, data)
.then((res) => {
if (res) {
const { id, posts } = res;
setEntityThread((pre) => {
return pre.map((thread) => {
if (thread.id === id) {
return { ...res, posts: posts?.slice(-3) };
} else {
return thread;
}
});
});
getEntityFeedCount();
} else {
throw jsonData['api-error-messages']['unexpected-server-response'];
}
})
.catch((err: AxiosError) => {
showErrorToast(err, jsonData['api-error-messages']['add-feed-error']);
});
};
const updateThreadHandler = (
threadId: string,
postId: string,
isThread: boolean,
data: Operation[]
) => {
updateThreadData(threadId, postId, isThread, data, setEntityThread);
};
const handleFeedFilterChange = useCallback(
(feedType, threadType) => {
getFeedData(undefined, feedType, threadType);
},
[paging]
);
const fetchResourcePermission = async (dashboardDataModelFQN: string) => { const fetchResourcePermission = async (dashboardDataModelFQN: string) => {
setIsLoading(true); setIsLoading(true);
try { try {
@ -178,6 +313,25 @@ const DataModelsPage = () => {
} }
}; };
const createThread = (data: CreateThread) => {
postThread(data)
.then((res) => {
if (res) {
setEntityThread((pre) => [...pre, res]);
getEntityFeedCount();
} else {
showErrorToast(
jsonData['api-error-messages']['unexpected-server-response']
);
}
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['create-conversation-error']
);
});
};
const fetchDataModelDetails = async (dashboardDataModelFQN: string) => { const fetchDataModelDetails = async (dashboardDataModelFQN: string) => {
setIsLoading(true); setIsLoading(true);
try { try {
@ -194,10 +348,8 @@ const DataModelsPage = () => {
} }
}; };
const handleUpdateDataModelData = (updatedData: DashboardDataModel) => { const onThreadPanelClose = () => {
const jsonPatch = compare(omitBy(dataModelData, isUndefined), updatedData); setThreadLink('');
return patchDataModelDetails(dataModelData?.id ?? '', jsonPatch);
}; };
const handleTabChange = (tabValue: string) => { const handleTabChange = (tabValue: string) => {
@ -208,6 +360,12 @@ const DataModelsPage = () => {
} }
}; };
const handleUpdateDataModelData = (updatedData: DashboardDataModel) => {
const jsonPatch = compare(omitBy(dataModelData, isUndefined), updatedData);
return patchDataModelDetails(dataModelData?.id ?? '', jsonPatch);
};
const handleUpdateDescription = async (updatedDescription: string) => { const handleUpdateDescription = async (updatedDescription: string) => {
try { try {
const { description: newDescription, version } = const { description: newDescription, version } =
@ -301,6 +459,7 @@ const DataModelsPage = () => {
tags: newTags, tags: newTags,
version, version,
})); }));
getEntityFeedCount();
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} }
@ -319,6 +478,7 @@ const DataModelsPage = () => {
owner: newOwner, owner: newOwner,
version, version,
})); }));
getEntityFeedCount();
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
} }
@ -347,6 +507,7 @@ const DataModelsPage = () => {
tags: newTags, tags: newTags,
version, version,
})); }));
getEntityFeedCount();
} }
} catch (error) { } catch (error) {
showErrorToast(error as AxiosError); showErrorToast(error as AxiosError);
@ -372,9 +533,16 @@ const DataModelsPage = () => {
} }
}; };
useEffect(() => {
if (tab === DATA_MODELS_DETAILS_TABS.ACTIVITY) {
getFeedData();
}
}, [tab, dashboardDataModelFQN]);
useEffect(() => { useEffect(() => {
if (hasViewPermission) { if (hasViewPermission) {
fetchDataModelDetails(dashboardDataModelFQN); fetchDataModelDetails(dashboardDataModelFQN);
getEntityFeedCount();
} }
}, [dashboardDataModelFQN, dataModelPermissions]); }, [dashboardDataModelFQN, dataModelPermissions]);
@ -406,6 +574,14 @@ const DataModelsPage = () => {
canDelete={dataModelPermissions.Delete} canDelete={dataModelPermissions.Delete}
currentOwner={owner} currentOwner={owner}
deleted={deleted} deleted={deleted}
entityFieldTasks={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldTaskCount
)}
entityFieldThreads={getEntityFieldThreadCounts(
EntityField.TAGS,
entityFieldThreadCount
)}
entityFqn={dashboardDataModelFQN} entityFqn={dashboardDataModelFQN}
entityId={entityId} entityId={entityId}
entityName={entityName || ''} entityName={entityName || ''}
@ -423,7 +599,8 @@ const DataModelsPage = () => {
titleLinks={breadcrumbTitles} titleLinks={breadcrumbTitles}
updateOwner={hasEditOwnerPermission ? handleUpdateOwner : undefined} updateOwner={hasEditOwnerPermission ? handleUpdateOwner : undefined}
updateTier={hasEditTierPermission ? handleUpdateTier : undefined} updateTier={hasEditTierPermission ? handleUpdateTier : undefined}
version={version + ''} version={toString(version)}
onThreadLinkSelect={onThreadLinkSelect}
/> />
<Tabs activeKey={tab} className="h-full" onChange={handleTabChange}> <Tabs activeKey={tab} className="h-full" onChange={handleTabChange}>
<Tabs.TabPane <Tabs.TabPane
@ -433,7 +610,7 @@ const DataModelsPage = () => {
{t('label.model')} {t('label.model')}
</span> </span>
}> }>
<Card className={ENTITY_CARD_CLASS}> <Card className="h-full">
<Space className="w-full" direction="vertical" size={8}> <Space className="w-full" direction="vertical" size={8}>
<Description <Description
description={description} description={description}
@ -460,6 +637,38 @@ const DataModelsPage = () => {
</Card> </Card>
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.ACTIVITY}
tab={
<span data-testid={DATA_MODELS_DETAILS_TABS.ACTIVITY}>
{t('label.activity-feed-and-task-plural')}{' '}
{getCountBadge(
feedCount,
'',
DATA_MODELS_DETAILS_TABS.ACTIVITY === tab
)}
</span>
}>
<Card className="m-y-md">
<Row justify="center">
<Col span={18}>
<div id="activityfeed">
<ActivityFeedList
isEntityFeed
withSidePanel
deletePostHandler={deletePostHandler}
entityName={entityName}
feedList={entityThread}
isFeedLoading={isEntityThreadLoading}
postFeedHandler={postFeedHandler}
updateThreadHandler={updateThreadHandler}
onFeedFiltersUpdate={handleFeedFilterChange}
/>
</div>
</Col>
</Row>
</Card>
</Tabs.TabPane>
{dataModelData?.sql && ( {dataModelData?.sql && (
<Tabs.TabPane <Tabs.TabPane
key={DATA_MODELS_DETAILS_TABS.SQL} key={DATA_MODELS_DETAILS_TABS.SQL}
@ -468,7 +677,7 @@ const DataModelsPage = () => {
{t('label.sql-uppercase')} {t('label.sql-uppercase')}
</span> </span>
}> }>
<Card className={ENTITY_CARD_CLASS}> <Card className="h-full">
<SchemaEditor <SchemaEditor
editorClass="custom-code-mirror-theme full-screen-editor-height" editorClass="custom-code-mirror-theme full-screen-editor-height"
mode={{ name: CSMode.SQL }} mode={{ name: CSMode.SQL }}
@ -500,6 +709,18 @@ const DataModelsPage = () => {
</Card> </Card>
</Tabs.TabPane> </Tabs.TabPane>
</Tabs> </Tabs>
{threadLink ? (
<ActivityThreadPanel
createThread={createThread}
deletePostHandler={deletePostHandler}
open={Boolean(threadLink)}
postFeedHandler={postFeedHandler}
threadLink={threadLink}
updateThreadHandler={updateThreadHandler}
onCancel={onThreadPanelClose}
/>
) : null}
</div> </div>
</PageContainerV1> </PageContainerV1>
); );

View File

@ -11,6 +11,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { DatabaseSchema } from 'generated/entity/data/databaseSchema'; import { DatabaseSchema } from 'generated/entity/data/databaseSchema';
import { Dashboard } from '../../generated/entity/data/dashboard'; import { Dashboard } from '../../generated/entity/data/dashboard';
import { Mlmodel } from '../../generated/entity/data/mlmodel'; import { Mlmodel } from '../../generated/entity/data/mlmodel';
@ -24,7 +25,8 @@ export type EntityData =
| Dashboard | Dashboard
| Pipeline | Pipeline
| Mlmodel | Mlmodel
| DatabaseSchema; | DatabaseSchema
| DashboardDataModel;
export interface Option { export interface Option {
label: string; label: string;

View File

@ -14,6 +14,7 @@ import { AxiosResponse } from 'axios';
import { Operation } from 'fast-json-patch'; import { Operation } from 'fast-json-patch';
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel'; import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
import { EntityReference } from 'generated/type/entityReference'; import { EntityReference } from 'generated/type/entityReference';
import { getURLWithQueryFields } from 'utils/APIUtils';
import APIClient from './index'; import APIClient from './index';
const URL = '/dashboard/datamodels'; const URL = '/dashboard/datamodels';
@ -37,6 +38,20 @@ export const getDataModelsByName = async (
return response.data; return response.data;
}; };
export const getDataModelDetailsByFQN = async (
databaseSchemaName: string,
arrQueryFields?: string | string[]
) => {
const url = `${getURLWithQueryFields(
`/dashboard/datamodels/name/${databaseSchemaName}`,
arrQueryFields
)}`;
const response = await APIClient.get<DashboardDataModel>(url);
return response.data;
};
export const patchDataModelDetails = async (id: string, data: Operation[]) => { export const patchDataModelDetails = async (id: string, data: Operation[]) => {
const response = await APIClient.patch< const response = await APIClient.patch<
Operation[], Operation[],

View File

@ -15,6 +15,7 @@ import {
PLACEHOLDER_ROUTE_TAB, PLACEHOLDER_ROUTE_TAB,
ROUTES, ROUTES,
} from 'constants/constants'; } from 'constants/constants';
import { TabSpecificField } from 'enums/entity.enum';
import { Column } from 'generated/entity/data/dashboardDataModel'; import { Column } from 'generated/entity/data/dashboardDataModel';
import { LabelType, State, TagLabel } from 'generated/type/tagLabel'; import { LabelType, State, TagLabel } from 'generated/type/tagLabel';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
@ -112,3 +113,6 @@ export const updateDataModelColumnTags = (
} }
}); });
}; };
export const defaultFields = `${TabSpecificField.TAGS}, ${TabSpecificField.OWNER},
${TabSpecificField.FOLLOWERS}`;

View File

@ -43,6 +43,7 @@ import {
getDashboardDetailsPath, getDashboardDetailsPath,
getDatabaseDetailsPath, getDatabaseDetailsPath,
getDatabaseSchemaDetailsPath, getDatabaseSchemaDetailsPath,
getDataModelDetailsPath,
getEditWebhookPath, getEditWebhookPath,
getMlModelPath, getMlModelPath,
getPipelineDetailsPath, getPipelineDetailsPath,
@ -260,6 +261,9 @@ export const getEntityLink = (
case SearchIndex.TAG: case SearchIndex.TAG:
return getTagsDetailsPath(fullyQualifiedName); return getTagsDetailsPath(fullyQualifiedName);
case EntityType.DASHBOARD_DATA_MODEL:
return getDataModelDetailsPath(fullyQualifiedName);
case SearchIndex.TABLE: case SearchIndex.TABLE:
case EntityType.TABLE: case EntityType.TABLE:
default: default:

View File

@ -23,6 +23,7 @@ import {
} from 'pages/TasksPage/TasksPage.interface'; } from 'pages/TasksPage/TasksPage.interface';
import { getDashboardByFqn } from 'rest/dashboardAPI'; import { getDashboardByFqn } from 'rest/dashboardAPI';
import { getDatabaseSchemaDetailsByFQN } from 'rest/databaseAPI'; import { getDatabaseSchemaDetailsByFQN } from 'rest/databaseAPI';
import { getDataModelDetailsByFQN } from 'rest/dataModelsAPI';
import { getUserSuggestions } from 'rest/miscAPI'; import { getUserSuggestions } from 'rest/miscAPI';
import { getMlModelByFQN } from 'rest/mlModelAPI'; import { getMlModelByFQN } from 'rest/mlModelAPI';
import { getPipelineByFqn } from 'rest/pipelineAPI'; import { getPipelineByFqn } from 'rest/pipelineAPI';
@ -44,6 +45,7 @@ import { TaskType } from '../generated/entity/feed/thread';
import { getPartialNameFromTableFQN } from './CommonUtils'; import { getPartialNameFromTableFQN } from './CommonUtils';
import { defaultFields as DashboardFields } from './DashboardDetailsUtils'; import { defaultFields as DashboardFields } from './DashboardDetailsUtils';
import { defaultFields as DatabaseSchemaFields } from './DatabaseSchemaDetailsUtils'; import { defaultFields as DatabaseSchemaFields } from './DatabaseSchemaDetailsUtils';
import { defaultFields as DataModelFields } from './DataModelsUtils';
import { defaultFields as TableFields } from './DatasetDetailsUtils'; import { defaultFields as TableFields } from './DatasetDetailsUtils';
import { getEntityName } from './EntityUtils'; import { getEntityName } from './EntityUtils';
import { defaultFields as MlModelFields } from './MlModelDetailsUtils'; import { defaultFields as MlModelFields } from './MlModelDetailsUtils';
@ -189,6 +191,7 @@ export const TASK_ENTITIES = [
EntityType.PIPELINE, EntityType.PIPELINE,
EntityType.MLMODEL, EntityType.MLMODEL,
EntityType.DATABASE_SCHEMA, EntityType.DATABASE_SCHEMA,
EntityType.DASHBOARD_DATA_MODEL,
]; ];
export const getBreadCrumbList = ( export const getBreadCrumbList = (
@ -327,6 +330,15 @@ export const fetchEntityDetail = (
break; break;
case EntityType.DASHBOARD_DATA_MODEL:
getDataModelDetailsByFQN(entityFQN, DataModelFields)
.then((res) => {
setEntityData(res);
})
.catch((err: AxiosError) => showErrorToast(err));
break;
default: default:
break; break;
} }