From ce89dbc94be01ee8d46e79135ad0a2c0141ee349 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Tue, 11 Jul 2023 18:46:04 +0530 Subject: [PATCH] chore(#6741): use antlr parser for fqn and entityLink (#12324) * 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> --- .../src/main/resources/ui/package.json | 1 + .../resources/ui/src/@types/antlr-parser.d.ts | 13 ++ .../ui/src/antlr/EntityLinkSplitListener.js | 45 +++++++ .../ActivityFeedCard/ActivityFeedCard.tsx | 12 +- .../ui/src/components/nav-bar/NavBar.tsx | 4 +- .../resources/ui/src/utils/EntityLink.test.ts | 84 +++++++++++++ .../main/resources/ui/src/utils/EntityLink.ts | 115 ++++++++++++++++++ .../main/resources/ui/src/utils/FeedUtils.tsx | 35 +----- .../main/resources/ui/src/utils/TasksUtils.ts | 2 +- .../src/main/resources/ui/yarn.lock | 5 + 10 files changed, 275 insertions(+), 41 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/@types/antlr-parser.d.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/antlr/EntityLinkSplitListener.js create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.test.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts diff --git a/openmetadata-ui/src/main/resources/ui/package.json b/openmetadata-ui/src/main/resources/ui/package.json index 534a4e6b960..cebb86ddc4d 100644 --- a/openmetadata-ui/src/main/resources/ui/package.json +++ b/openmetadata-ui/src/main/resources/ui/package.json @@ -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", diff --git a/openmetadata-ui/src/main/resources/ui/src/@types/antlr-parser.d.ts b/openmetadata-ui/src/main/resources/ui/src/@types/antlr-parser.d.ts new file mode 100644 index 00000000000..83f5a2a9382 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/@types/antlr-parser.d.ts @@ -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'; diff --git a/openmetadata-ui/src/main/resources/ui/src/antlr/EntityLinkSplitListener.js b/openmetadata-ui/src/main/resources/ui/src/antlr/EntityLinkSplitListener.js new file mode 100644 index 00000000000..7c9ca74b640 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/antlr/EntityLinkSplitListener.js @@ -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; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx index 393b51592f3..90fb5124200 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityFeedCard/ActivityFeedCard.tsx @@ -57,9 +57,9 @@ const ActivityFeedCard: FC = ({ 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(null); @@ -211,9 +211,9 @@ const ActivityFeedCard: FC = ({ '; +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>' + ); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts new file mode 100644 index 00000000000..2929a3f5cb1 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLink.ts @@ -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}>`; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx index 77f66c32b99..334936c7135 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/FeedUtils.tsx @@ -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); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts index a1af1983158..96fa5dcf2cc 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts @@ -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 diff --git a/openmetadata-ui/src/main/resources/ui/yarn.lock b/openmetadata-ui/src/main/resources/ui/yarn.lock index 2aab14cfb93..2ef268d3440 100644 --- a/openmetadata-ui/src/main/resources/ui/yarn.lock +++ b/openmetadata-ui/src/main/resources/ui/yarn.lock @@ -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"