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>
This commit is contained in:
Sachin Chaurasiya 2023-07-11 18:46:04 +05:30 committed by GitHub
parent 8635fd7a28
commit ce89dbc94b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 275 additions and 41 deletions

View File

@ -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",

View 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';

View File

@ -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;
}
}

View File

@ -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}

View File

@ -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

View File

@ -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>'
);
});
});

View 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}>`;
}
}

View File

@ -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);

View File

@ -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

View File

@ -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"