mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2026-01-06 20:47:06 +00:00
* feat(#16208): custom properties for Dashboard Data Models * chore: update the padding for custom properties tab content * Backend : Support extension field for dashboardDataModel * ui: update ui to fetch the extension data * feat: show custom properties on right panel * chore: add custom properties in version page * test: add playwright test for data model custom properties * test: remove duplicate checks --------- Co-authored-by: sonikashah <sonikashah94@gmail.com>
This commit is contained in:
parent
fc9033b953
commit
55cd180ffa
@ -71,7 +71,7 @@ import org.openmetadata.service.util.ResultList;
|
||||
public class DashboardDataModelResource
|
||||
extends EntityResource<DashboardDataModel, DashboardDataModelRepository> {
|
||||
public static final String COLLECTION_PATH = "/v1/dashboard/datamodels";
|
||||
protected static final String FIELDS = "owner,tags,followers,domain,sourceHash";
|
||||
protected static final String FIELDS = "owner,tags,followers,domain,sourceHash,extension";
|
||||
|
||||
@Override
|
||||
public DashboardDataModel addHref(UriInfo uriInfo, DashboardDataModel dashboardDataModel) {
|
||||
|
||||
@ -79,6 +79,10 @@
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 32
|
||||
},
|
||||
"extension": {
|
||||
"description": "Entity extension data with custom attributes added to the entity.",
|
||||
"$ref": "../../type/basic.json#/definitions/entityExtension"
|
||||
}
|
||||
},
|
||||
"required": ["name", "service", "dataModelType", "columns"],
|
||||
|
||||
@ -159,6 +159,10 @@
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 32
|
||||
},
|
||||
"extension": {
|
||||
"description": "Entity extension data with custom attributes added to the entity.",
|
||||
"$ref": "../../type/basic.json#/definitions/entityExtension"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
|
||||
@ -25,6 +25,7 @@ export const CustomPropertySupportedEntityList = [
|
||||
EntityTypeEndpoint.MlModel,
|
||||
EntityTypeEndpoint.GlossaryTerm,
|
||||
EntityTypeEndpoint.SearchIndex,
|
||||
EntityTypeEndpoint.DataModel,
|
||||
];
|
||||
|
||||
export const ENTITY_REFERENCE_PROPERTIES = [
|
||||
|
||||
@ -56,4 +56,5 @@ export enum ENTITY_PATH {
|
||||
glossaryTerm = 'glossaryTerm',
|
||||
databases = 'database',
|
||||
databaseSchemas = 'databaseSchema',
|
||||
'dashboard/datamodels' = 'dashboardDataModel',
|
||||
}
|
||||
|
||||
@ -159,32 +159,6 @@ export const setValueForProperty = async (data: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await page.waitForResponse('/api/v1/*/*');
|
||||
if (propertyType === 'enum') {
|
||||
await expect(
|
||||
page.getByLabel('Custom Properties').getByTestId('enum-value')
|
||||
).toContainText(value);
|
||||
} else if (propertyType === 'timeInterval') {
|
||||
const [startValue, endValue] = value.split(',');
|
||||
|
||||
await expect(
|
||||
page.getByLabel('Custom Properties').getByTestId('time-interval-value')
|
||||
).toContainText(startValue);
|
||||
await expect(
|
||||
page.getByLabel('Custom Properties').getByTestId('time-interval-value')
|
||||
).toContainText(endValue);
|
||||
} else if (propertyType === 'sqlQuery') {
|
||||
await expect(
|
||||
page.getByLabel('Custom Properties').locator('.CodeMirror-scroll')
|
||||
).toContainText(value);
|
||||
} else if (
|
||||
!['entityReference', 'entityReferenceList'].includes(propertyType)
|
||||
) {
|
||||
await expect(page.getByRole('row', { name: propertyName })).toContainText(
|
||||
value.replace(/\*|_/gi, '')
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const validateValueForProperty = async (data: {
|
||||
|
||||
@ -16,7 +16,9 @@ import classNames from 'classnames';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { FC, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../../../constants/char.constants';
|
||||
import { getVersionPath } from '../../../../constants/constants';
|
||||
import { EntityField } from '../../../../constants/Feeds.constants';
|
||||
import { EntityTabs, EntityType, FqnPart } from '../../../../enums/entity.enum';
|
||||
import {
|
||||
@ -32,6 +34,7 @@ import {
|
||||
getEntityVersionByField,
|
||||
getEntityVersionTags,
|
||||
} from '../../../../utils/EntityVersionUtils';
|
||||
import { CustomPropertyTable } from '../../../common/CustomPropertyTable/CustomPropertyTable';
|
||||
import DescriptionV1 from '../../../common/EntityDescription/DescriptionV1';
|
||||
import Loader from '../../../common/Loader/Loader';
|
||||
import TabsLabel from '../../../common/TabsLabel/TabsLabel.component';
|
||||
@ -55,12 +58,20 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
|
||||
deleted = false,
|
||||
backHandler,
|
||||
versionHandler,
|
||||
entityPermissions,
|
||||
}: DataModelVersionProp) => {
|
||||
const history = useHistory();
|
||||
const { t } = useTranslation();
|
||||
const { tab } = useParams<{ tab: EntityTabs }>();
|
||||
const [changeDescription, setChangeDescription] = useState<ChangeDescription>(
|
||||
currentVersionData.changeDescription as ChangeDescription
|
||||
);
|
||||
|
||||
const entityFqn = useMemo(
|
||||
() => currentVersionData.fullyQualifiedName ?? '',
|
||||
[currentVersionData.fullyQualifiedName]
|
||||
);
|
||||
|
||||
const { ownerDisplayName, ownerRef, tierDisplayName, domainDisplayName } =
|
||||
useMemo(
|
||||
() =>
|
||||
@ -107,6 +118,17 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
|
||||
);
|
||||
}, [currentVersionData, changeDescription]);
|
||||
|
||||
const handleTabChange = (activeKey: string) => {
|
||||
history.push(
|
||||
getVersionPath(
|
||||
EntityType.DASHBOARD_DATA_MODEL,
|
||||
entityFqn,
|
||||
String(version),
|
||||
activeKey
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const tabItems: TabsProps['items'] = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -160,8 +182,28 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
|
||||
</Row>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: EntityTabs.CUSTOM_PROPERTIES,
|
||||
label: (
|
||||
<TabsLabel
|
||||
id={EntityTabs.CUSTOM_PROPERTIES}
|
||||
name={t('label.custom-property-plural')}
|
||||
/>
|
||||
),
|
||||
children: (
|
||||
<div className="p-md">
|
||||
<CustomPropertyTable
|
||||
isVersionView
|
||||
entityDetails={currentVersionData as DashboardDataModel}
|
||||
entityType={EntityType.DASHBOARD_DATA_MODEL}
|
||||
hasEditAccess={false}
|
||||
hasPermission={entityPermissions.ViewAll}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
],
|
||||
[description, columns]
|
||||
[description, columns, currentVersionData, entityPermissions]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -191,7 +233,11 @@ const DataModelVersion: FC<DataModelVersionProp> = ({
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Tabs activeKey={EntityTabs.MODEL} items={tabItems} />
|
||||
<Tabs
|
||||
activeKey={tab ?? EntityTabs.MODEL}
|
||||
items={tabItems}
|
||||
onChange={handleTabChange}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { OperationPermission } from '../../../../context/PermissionProvider/PermissionProvider.interface';
|
||||
import { DashboardDataModel } from '../../../../generated/entity/data/dashboardDataModel';
|
||||
import { EntityHistory } from '../../../../generated/type/entityHistory';
|
||||
import { TagLabel } from '../../../../generated/type/tagLabel';
|
||||
@ -30,4 +31,5 @@ export interface DataModelVersionProp {
|
||||
deleted?: boolean;
|
||||
backHandler: () => void;
|
||||
versionHandler: (v: string) => void;
|
||||
entityPermissions: OperationPermission;
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import { FEED_COUNT_INITIAL_DATA } from '../../../../constants/entity.constants'
|
||||
import LineageProvider from '../../../../context/LineageProvider/LineageProvider';
|
||||
import { CSMode } from '../../../../enums/codemirror.enum';
|
||||
import { EntityTabs, EntityType } from '../../../../enums/entity.enum';
|
||||
import { DashboardDataModel } from '../../../../generated/entity/data/dashboardDataModel';
|
||||
import { TagLabel } from '../../../../generated/type/tagLabel';
|
||||
import { useFqn } from '../../../../hooks/useFqn';
|
||||
import { FeedCounts } from '../../../../interface/feed.interface';
|
||||
@ -39,6 +40,7 @@ import { useActivityFeedProvider } from '../../../ActivityFeed/ActivityFeedProvi
|
||||
import { ActivityFeedTab } from '../../../ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
|
||||
import ActivityThreadPanel from '../../../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||
import { withActivityFeed } from '../../../AppRouter/withActivityFeed';
|
||||
import { CustomPropertyTable } from '../../../common/CustomPropertyTable/CustomPropertyTable';
|
||||
import DescriptionV1 from '../../../common/EntityDescription/DescriptionV1';
|
||||
import ResizablePanels from '../../../common/ResizablePanels/ResizablePanels';
|
||||
import TabsLabel from '../../../common/TabsLabel/TabsLabel.component';
|
||||
@ -209,6 +211,18 @@ const DataModelDetails = ({
|
||||
|
||||
setIsEditDescription(false);
|
||||
};
|
||||
const handelExtensionUpdate = useCallback(
|
||||
async (updatedDataModel: DashboardDataModel) => {
|
||||
await onUpdateDataModel(
|
||||
{
|
||||
...dataModelData,
|
||||
extension: updatedDataModel.extension,
|
||||
},
|
||||
'extension'
|
||||
);
|
||||
},
|
||||
[onUpdateDataModel, dataModelData]
|
||||
);
|
||||
|
||||
const modelComponent = useMemo(() => {
|
||||
return (
|
||||
@ -251,14 +265,22 @@ const DataModelDetails = ({
|
||||
secondPanel={{
|
||||
children: (
|
||||
<div data-testid="entity-right-panel">
|
||||
<EntityRightPanel
|
||||
<EntityRightPanel<EntityType.DASHBOARD_DATA_MODEL>
|
||||
customProperties={dataModelData}
|
||||
dataProducts={dataModelData?.dataProducts ?? []}
|
||||
domain={dataModelData?.domain}
|
||||
editCustomAttributePermission={
|
||||
(dataModelPermissions.EditAll ||
|
||||
dataModelPermissions.EditCustomFields) &&
|
||||
!deleted
|
||||
}
|
||||
editTagPermission={editTagsPermission}
|
||||
entityFQN={decodedDataModelFQN}
|
||||
entityId={dataModelData.id}
|
||||
entityType={EntityType.DASHBOARD_DATA_MODEL}
|
||||
selectedTags={tags}
|
||||
viewAllPermission={dataModelPermissions.ViewAll}
|
||||
onExtensionUpdate={handelExtensionUpdate}
|
||||
onTagSelectionChange={handleTagSelection}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
@ -370,6 +392,30 @@ const DataModelDetails = ({
|
||||
</LineageProvider>
|
||||
),
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<TabsLabel
|
||||
id={EntityTabs.CUSTOM_PROPERTIES}
|
||||
name={t('label.custom-property-plural')}
|
||||
/>
|
||||
),
|
||||
key: EntityTabs.CUSTOM_PROPERTIES,
|
||||
children: (
|
||||
<div className="p-md">
|
||||
<CustomPropertyTable<EntityType.DASHBOARD_DATA_MODEL>
|
||||
entityDetails={dataModelData}
|
||||
entityType={EntityType.DASHBOARD_DATA_MODEL}
|
||||
handleExtensionUpdate={handelExtensionUpdate}
|
||||
hasEditAccess={dataModelPermissions.ViewAll}
|
||||
hasPermission={
|
||||
dataModelPermissions.EditAll ||
|
||||
dataModelPermissions.EditCustomFields
|
||||
}
|
||||
isVersionView={false}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return allTabs;
|
||||
|
||||
@ -14,6 +14,7 @@
|
||||
import { EntityType } from '../../../enums/entity.enum';
|
||||
import { Container } from '../../../generated/entity/data/container';
|
||||
import { Dashboard } from '../../../generated/entity/data/dashboard';
|
||||
import { DashboardDataModel } from '../../../generated/entity/data/dashboardDataModel';
|
||||
import { Database } from '../../../generated/entity/data/database';
|
||||
import { DatabaseSchema } from '../../../generated/entity/data/databaseSchema';
|
||||
import { GlossaryTerm } from '../../../generated/entity/data/glossaryTerm';
|
||||
@ -38,6 +39,7 @@ export type ExtentionEntities = {
|
||||
[EntityType.GLOSSARY_TERM]: GlossaryTerm;
|
||||
[EntityType.DATABASE]: Database;
|
||||
[EntityType.DATABASE_SCHEMA]: DatabaseSchema;
|
||||
[EntityType.DASHBOARD_DATA_MODEL]: DashboardDataModel;
|
||||
};
|
||||
|
||||
export type ExtentionEntitiesKeys = keyof ExtentionEntities;
|
||||
|
||||
@ -69,6 +69,7 @@ export enum GlobalSettingOptions {
|
||||
OM_HEALTH = 'om-health',
|
||||
PROFILER_CONFIGURATION = 'profiler-configuration',
|
||||
APPEARANCE = 'appearance',
|
||||
DASHBOARD_DATA_MODEL = 'dashboardDataModels',
|
||||
}
|
||||
|
||||
export const GLOBAL_SETTING_PERMISSION_RESOURCES = [
|
||||
|
||||
@ -108,6 +108,12 @@ export const PAGE_HEADERS = {
|
||||
entity: i18n.t('label.dashboard-plural'),
|
||||
}),
|
||||
},
|
||||
DASHBOARD_DATA_MODEL_CUSTOM_ATTRIBUTES: {
|
||||
header: i18n.t('label.dashboard-data-model-plural'),
|
||||
subHeader: i18n.t('message.define-custom-property-for-entity', {
|
||||
entity: i18n.t('label.dashboard-data-model-plural'),
|
||||
}),
|
||||
},
|
||||
PIPELINES_CUSTOM_ATTRIBUTES: {
|
||||
header: i18n.t('label.pipeline-plural'),
|
||||
subHeader: i18n.t('message.define-custom-property-for-entity', {
|
||||
|
||||
@ -522,6 +522,7 @@ export const ENTITY_PATH = {
|
||||
glossaryTerm: 'glossaryTerm',
|
||||
databases: 'database',
|
||||
databaseSchemas: 'databaseSchema',
|
||||
dashboardDataModels: 'dashboardDataModel',
|
||||
};
|
||||
|
||||
export const VALIDATION_MESSAGES = {
|
||||
|
||||
@ -154,6 +154,9 @@ const CustomEntityDetailV1 = () => {
|
||||
case ENTITY_PATH.dashboards:
|
||||
return PAGE_HEADERS.DASHBOARD_CUSTOM_ATTRIBUTES;
|
||||
|
||||
case ENTITY_PATH.dashboardDataModels:
|
||||
return PAGE_HEADERS.DASHBOARD_DATA_MODEL_CUSTOM_ATTRIBUTES;
|
||||
|
||||
case ENTITY_PATH.pipelines:
|
||||
return PAGE_HEADERS.PIPELINES_CUSTOM_ATTRIBUTES;
|
||||
|
||||
|
||||
@ -34,6 +34,7 @@ import {
|
||||
ResourceEntity,
|
||||
} from '../../context/PermissionProvider/PermissionProvider.interface';
|
||||
import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum';
|
||||
import { TabSpecificField } from '../../enums/entity.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { Tag } from '../../generated/entity/classification/tag';
|
||||
import { DashboardDataModel } from '../../generated/entity/data/dashboardDataModel';
|
||||
@ -124,7 +125,8 @@ const DataModelsPage = () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getDataModelByFqn(dashboardDataModelFQN, {
|
||||
fields: 'owner,tags,followers,votes,domain,dataProducts',
|
||||
// eslint-disable-next-line max-len
|
||||
fields: `${TabSpecificField.OWNER},${TabSpecificField.TAGS},${TabSpecificField.FOLLOWERS},${TabSpecificField.VOTES},${TabSpecificField.DOMAIN},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`,
|
||||
include: Include.All,
|
||||
});
|
||||
setDataModelData(response);
|
||||
|
||||
@ -593,6 +593,7 @@ const EntityVersionPage: FunctionComponent = () => {
|
||||
dataProducts={currentVersionData.dataProducts}
|
||||
deleted={currentVersionData.deleted}
|
||||
domain={domain}
|
||||
entityPermissions={entityPermissions}
|
||||
isVersionLoading={isVersionLoading}
|
||||
owner={owner}
|
||||
slashedDataModelName={slashedEntityName}
|
||||
|
||||
@ -18,6 +18,7 @@ import { ReactComponent as BotIcon } from '../assets/svg/bot-colored.svg';
|
||||
import { ReactComponent as AppearanceIcon } from '../assets/svg/custom-logo-colored.svg';
|
||||
import { ReactComponent as CustomDashboardLogoIcon } from '../assets/svg/customize-landing-page-colored.svg';
|
||||
import { ReactComponent as DashboardIcon } from '../assets/svg/dashboard-colored.svg';
|
||||
import { ReactComponent as DashboardDataModelIcon } from '../assets/svg/data-model.svg';
|
||||
import { ReactComponent as DatabaseIcon } from '../assets/svg/database-colored.svg';
|
||||
import { ReactComponent as SchemaIcon } from '../assets/svg/database-schema.svg';
|
||||
import { ReactComponent as EmailIcon } from '../assets/svg/email-colored.svg';
|
||||
@ -354,6 +355,15 @@ export const getGlobalSettingsMenuWithPermission = (
|
||||
key: `${GlobalSettingsMenuCategory.CUSTOM_PROPERTIES}.${GlobalSettingOptions.DASHBOARDS}`,
|
||||
icon: DashboardIcon,
|
||||
},
|
||||
{
|
||||
label: i18next.t('label.dashboard-data-model-plural'),
|
||||
description: i18next.t('message.define-custom-property-for-entity', {
|
||||
entity: i18next.t('label.dashboard-data-model-plural'),
|
||||
}),
|
||||
isProtected: Boolean(isAdminUser),
|
||||
key: `${GlobalSettingsMenuCategory.CUSTOM_PROPERTIES}.${GlobalSettingOptions.DASHBOARD_DATA_MODEL}`,
|
||||
icon: DashboardDataModelIcon,
|
||||
},
|
||||
{
|
||||
label: i18next.t('label.pipeline-plural'),
|
||||
description: i18next.t('message.define-custom-property-for-entity', {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user