mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-25 08:50:18 +00:00
* chore(#6741): use antlr parser for fqn and entityLink * chore: use this for class context * chore: address comments * chore: convert js to ts * fix: typescript errors * fix: entity link split method issue * fix: split listener --------- Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com>
This commit is contained in:
parent
8635fd7a28
commit
ce89dbc94b
@ -151,6 +151,7 @@
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/react-hooks": "^5.0.3",
|
||||
"@testing-library/user-event": "^7.1.2",
|
||||
"@types/antlr4": "^4.11.2",
|
||||
"@types/classnames": "^2.3.1",
|
||||
"@types/codemirror": "^0.0.104",
|
||||
"@types/dagre": "^0.7.47",
|
||||
|
13
openmetadata-ui/src/main/resources/ui/src/@types/antlr-parser.d.ts
vendored
Normal file
13
openmetadata-ui/src/main/resources/ui/src/@types/antlr-parser.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
declare module 'antlr4/src/antlr4/tree';
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import EntityLinkListener from '../generated/antlr/EntityLinkListener';
|
||||
|
||||
export default class EntityLinkSplitListener extends EntityLinkListener {
|
||||
constructor() {
|
||||
super();
|
||||
this.entityLinkParts = [];
|
||||
}
|
||||
|
||||
// Enter a parse tree produced by EntityLinkParser#entityType.
|
||||
enterEntityType(ctx) {
|
||||
this.entityLinkParts.push(ctx.getText());
|
||||
}
|
||||
|
||||
// Enter a parse tree produced by EntityLinkParser#entityAttribute.
|
||||
enterEntityAttribute(ctx) {
|
||||
this.entityLinkParts.push(ctx.getText());
|
||||
}
|
||||
|
||||
// Enter a parse tree produced by EntityLinkParser#entityFqn.
|
||||
enterEntityFqn(ctx) {
|
||||
this.entityLinkParts.push(ctx.getText());
|
||||
}
|
||||
|
||||
// Enter a parse tree produced by EntityLinkParser#entityField.
|
||||
enterEntityField(ctx) {
|
||||
this.entityLinkParts.push(ctx.getText());
|
||||
}
|
||||
|
||||
split() {
|
||||
return this.entityLinkParts;
|
||||
}
|
||||
}
|
@ -57,9 +57,9 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
|
||||
editAnnouncementPermission,
|
||||
showUserAvatar = true,
|
||||
}) => {
|
||||
const entityType = getEntityType(entityLink as string);
|
||||
const entityFQN = getEntityFQN(entityLink as string);
|
||||
const entityField = getEntityField(entityLink as string);
|
||||
const entityType = getEntityType(entityLink ?? '');
|
||||
const entityFQN = getEntityFQN(entityLink ?? '');
|
||||
const entityField = getEntityField(entityLink ?? '');
|
||||
|
||||
const currentUser = AppState.getCurrentUserDetails();
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@ -211,9 +211,9 @@ const ActivityFeedCard: FC<ActivityFeedCardProp> = ({
|
||||
<FeedCardHeader
|
||||
className="tw-pl-2"
|
||||
createdBy={feedDetail.from}
|
||||
entityFQN={entityFQN as string}
|
||||
entityField={entityField as string}
|
||||
entityType={entityType as string}
|
||||
entityFQN={entityFQN}
|
||||
entityField={entityField ?? ''}
|
||||
entityType={entityType}
|
||||
feedType={feedType}
|
||||
isEntityFeed={isEntityFeed}
|
||||
task={task}
|
||||
|
@ -30,7 +30,7 @@ import BrandImage from 'components/common/BrandImage/BrandImage';
|
||||
import { useGlobalSearchProvider } from 'components/GlobalSearchProvider/GlobalSearchProvider';
|
||||
import WhatsNewAlert from 'components/Modals/WhatsNewModal/WhatsNewAlert/WhatsNewAlert.component';
|
||||
import { CookieStorage } from 'cookie-storage';
|
||||
import { EntityTabs } from 'enums/entity.enum';
|
||||
import { EntityTabs, EntityType } from 'enums/entity.enum';
|
||||
import i18next from 'i18next';
|
||||
import { debounce, upperCase } from 'lodash';
|
||||
import React, {
|
||||
@ -218,7 +218,7 @@ const NavBar = ({
|
||||
});
|
||||
|
||||
path = getEntityDetailLink(
|
||||
entityType,
|
||||
entityType as EntityType,
|
||||
entityFQN,
|
||||
EntityTabs.ACTIVITY_FEED,
|
||||
ActivityFeedTabs.TASKS
|
||||
|
@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import EntityLink from './EntityLink';
|
||||
|
||||
const entityLink =
|
||||
'<#E::table::sample_data.ecommerce_db.shopify.dim_address::description>';
|
||||
const entityLinkWithColumn =
|
||||
'<#E::table::sample_data.ecommerce_db.shopify.dim_address::columns::address_id::tags>';
|
||||
|
||||
describe('Test EntityLink', () => {
|
||||
it('Should split the entityLink into parts', () => {
|
||||
const entityLinkPartsWithColumn = EntityLink.split(entityLinkWithColumn);
|
||||
const entityLinkParts = EntityLink.split(entityLink);
|
||||
|
||||
expect(entityLinkParts).toStrictEqual([
|
||||
'table',
|
||||
'sample_data.ecommerce_db.shopify.dim_address',
|
||||
'description',
|
||||
]);
|
||||
|
||||
expect(entityLinkPartsWithColumn).toStrictEqual([
|
||||
'table',
|
||||
'sample_data.ecommerce_db.shopify.dim_address',
|
||||
'columns',
|
||||
'address_id',
|
||||
'tags',
|
||||
]);
|
||||
});
|
||||
|
||||
it('Should return the entityType from entityLink', () => {
|
||||
expect(EntityLink.getEntityType(entityLink)).toStrictEqual('table');
|
||||
});
|
||||
|
||||
it('Should return the entityFqn from entityLink', () => {
|
||||
expect(EntityLink.getEntityFqn(entityLink)).toStrictEqual(
|
||||
'sample_data.ecommerce_db.shopify.dim_address'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should return the entityField from entityLink', () => {
|
||||
expect(EntityLink.getEntityField(entityLink)).toStrictEqual('description');
|
||||
});
|
||||
|
||||
it('Should return the columnName from entityLink', () => {
|
||||
expect(EntityLink.getTableColumnName(entityLinkWithColumn)).toStrictEqual(
|
||||
'address_id'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should return the column field from entityLink', () => {
|
||||
expect(EntityLink.getTableColumnField(entityLinkWithColumn)).toStrictEqual(
|
||||
'tags'
|
||||
);
|
||||
});
|
||||
|
||||
it('Should return the undefined if columnName if not present in entityLink', () => {
|
||||
expect(EntityLink.getTableColumnName(entityLink)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should return the undefined if columnField if not present in entityLink', () => {
|
||||
expect(EntityLink.getTableColumnField(entityLink)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('Should build the entityLink', () => {
|
||||
expect(
|
||||
EntityLink.getEntityLink(
|
||||
'table',
|
||||
'sample_data.ecommerce_db.shopify.dim_address'
|
||||
)
|
||||
).toStrictEqual(
|
||||
'<#E::table::sample_data.ecommerce_db.shopify.dim_address>'
|
||||
);
|
||||
});
|
||||
});
|
115
openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts
Normal file
115
openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts
Normal file
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2023 Collate.
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import antlr4 from 'antlr4';
|
||||
import { ParseTreeWalker } from 'antlr4/src/antlr4/tree';
|
||||
import EntityLinkSplitListener from '../antlr/EntityLinkSplitListener';
|
||||
import EntityLinkLexer from '../generated/antlr/EntityLinkLexer';
|
||||
import EntityLinkParser from '../generated/antlr/EntityLinkParser';
|
||||
import { ENTITY_LINK_SEPARATOR } from './EntityUtils';
|
||||
|
||||
export default class EntityLink {
|
||||
/**
|
||||
*
|
||||
* @param string entityLink
|
||||
* @returns list of entity link parts
|
||||
*/
|
||||
static split(entityLink: string) {
|
||||
if (entityLink) {
|
||||
const chars = new antlr4.InputStream(entityLink);
|
||||
const lexer = new EntityLinkLexer(chars);
|
||||
const tokens = new antlr4.CommonTokenStream(lexer);
|
||||
const parser = new EntityLinkParser(tokens);
|
||||
const tree = parser.entitylink();
|
||||
const splitter = new EntityLinkSplitListener();
|
||||
ParseTreeWalker.DEFAULT.walk(splitter, tree);
|
||||
|
||||
return splitter.split();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string entityLink
|
||||
* @returns entity type
|
||||
*/
|
||||
static getEntityType(entityLink: string) {
|
||||
return this.split(entityLink)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string entityLink
|
||||
* @returns entity fqn
|
||||
*/
|
||||
static getEntityFqn(entityLink: string) {
|
||||
return this.split(entityLink)[1];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string entityLink
|
||||
* @returns entity field
|
||||
*/
|
||||
static getEntityField(entityLink: string) {
|
||||
const entityType = this.getEntityType(entityLink);
|
||||
if (entityType === 'table') {
|
||||
return this.split(entityLink)[2];
|
||||
}
|
||||
|
||||
return this.split(entityLink)[-1];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string entityLink
|
||||
* @returns column name for table entity
|
||||
*/
|
||||
static getTableColumnName(entityLink: string) {
|
||||
return this.split(entityLink)[3];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string entityLink
|
||||
* @returns column field for table entity
|
||||
*/
|
||||
static getTableColumnField(entityLink: string) {
|
||||
return this.split(entityLink)[4];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string tableFqn
|
||||
* @param string | undefined columnName
|
||||
* @returns entity link for table
|
||||
*/
|
||||
static getTableEntityLink(tableFqn: string, columnName: string) {
|
||||
if (columnName) {
|
||||
return `<#E${ENTITY_LINK_SEPARATOR}table${ENTITY_LINK_SEPARATOR}${tableFqn}${ENTITY_LINK_SEPARATOR}columns${ENTITY_LINK_SEPARATOR}${columnName}>`;
|
||||
} else {
|
||||
return `<#E${ENTITY_LINK_SEPARATOR}table${ENTITY_LINK_SEPARATOR}${tableFqn}>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param string entityType
|
||||
* @param string entityFqn
|
||||
* @returns entityLink
|
||||
*/
|
||||
static getEntityLink(entityType: string, entityFqn: string) {
|
||||
return `<#E${ENTITY_LINK_SEPARATOR}${entityType}${ENTITY_LINK_SEPARATOR}${entityFqn}>`;
|
||||
}
|
||||
}
|
@ -59,6 +59,7 @@ import {
|
||||
getPartialNameFromFQN,
|
||||
getPartialNameFromTableFQN,
|
||||
} from './CommonUtils';
|
||||
import EntityLink from './EntityLink';
|
||||
import { ENTITY_LINK_SEPARATOR } from './EntityUtils';
|
||||
import { getEncodedFqn } from './StringsUtils';
|
||||
import { getEntityLink } from './TableUtils';
|
||||
@ -66,14 +67,10 @@ import { getRelativeDateByTimeStamp } from './TimeUtils';
|
||||
import { showErrorToast } from './ToastUtils';
|
||||
|
||||
export const getEntityType = (entityLink: string) => {
|
||||
const match = EntityRegEx.exec(entityLink);
|
||||
|
||||
return match?.[1] as EntityType;
|
||||
return EntityLink.getEntityType(entityLink);
|
||||
};
|
||||
export const getEntityFQN = (entityLink: string) => {
|
||||
const match = EntityRegEx.exec(entityLink);
|
||||
|
||||
return match?.[2];
|
||||
return EntityLink.getEntityFqn(entityLink);
|
||||
};
|
||||
export const getEntityField = (entityLink: string) => {
|
||||
const match = EntityRegEx.exec(entityLink);
|
||||
@ -155,23 +152,6 @@ export const getThreadField = (
|
||||
return value.split(separator).slice(-2);
|
||||
};
|
||||
|
||||
export const getThreadValue = (
|
||||
columnName: string,
|
||||
columnField: string,
|
||||
entityFieldThreads: EntityFieldThreads[]
|
||||
) => {
|
||||
let threadValue;
|
||||
|
||||
entityFieldThreads?.forEach((thread) => {
|
||||
const threadField = getThreadField(thread.entityField);
|
||||
if (threadField[0] === columnName && threadField[1] === columnField) {
|
||||
threadValue = thread;
|
||||
}
|
||||
});
|
||||
|
||||
return threadValue;
|
||||
};
|
||||
|
||||
export const buildMentionLink = (entityType: string, entityFqn: string) => {
|
||||
return `${document.location.protocol}//${document.location.host}/${entityType}/${entityFqn}`;
|
||||
};
|
||||
@ -484,21 +464,12 @@ export const updateThreadData = (
|
||||
}
|
||||
};
|
||||
|
||||
export const getFeedAction = (type: ThreadType) => {
|
||||
if (type === ThreadType.Task) {
|
||||
return i18next.t('label.created-a-task-lowercase');
|
||||
}
|
||||
|
||||
return i18next.t('label.posted-on-lowercase');
|
||||
};
|
||||
|
||||
export const prepareFeedLink = (entityType: string, entityFQN: string) => {
|
||||
const withoutFeedEntities = [
|
||||
EntityType.WEBHOOK,
|
||||
EntityType.GLOSSARY,
|
||||
EntityType.GLOSSARY_TERM,
|
||||
EntityType.TYPE,
|
||||
EntityType.MLMODEL,
|
||||
];
|
||||
|
||||
const entityLink = getEntityLink(entityType, entityFQN);
|
||||
|
@ -146,7 +146,7 @@ export const getTaskDetailPath = (task: Thread) => {
|
||||
const entityType = getEntityType(task.about) ?? '';
|
||||
|
||||
return getEntityDetailLink(
|
||||
entityType,
|
||||
entityType as EntityType,
|
||||
entityFQN,
|
||||
EntityTabs.ACTIVITY_FEED,
|
||||
ActivityFeedTabs.TASKS
|
||||
|
@ -3167,6 +3167,11 @@
|
||||
mkdirp "^1.0.4"
|
||||
path-browserify "^1.0.1"
|
||||
|
||||
"@types/antlr4@^4.11.2":
|
||||
version "4.11.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/antlr4/-/antlr4-4.11.2.tgz#46b70713b38eecb2d1a8e2ffd14544367067b41e"
|
||||
integrity sha512-WVDiUppozGAKAL76KbXX63A4U4a0HfHM/5X7+GWzen7OaS/7eJEMdd61B+Hhl52Kedcmr80/jNeeWrM3Z/icig==
|
||||
|
||||
"@types/asn1js@^2.0.2":
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/asn1js/-/asn1js-2.0.2.tgz#bb1992291381b5f06e22a829f2ae009267cdf8c5"
|
||||
|
Loading…
x
Reference in New Issue
Block a user