fix(ui): ui breaking as url was not encoded. (#12643)

* fix ui breaking because of url having percent in it

* added support for % in about

* fix test cases

* checkstyle

* url fix

* fix storage url

* encoded topic, mlmodel and pipeline entity

---------

Co-authored-by: 07Himank <himank07mehta@gmail.com>
This commit is contained in:
Ashish Gupta 2023-08-01 14:12:47 +05:30 committed by GitHub
parent 819bb27ee0
commit de06d50e7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 111 additions and 53 deletions

View File

@ -234,7 +234,7 @@ public abstract class EntityResourceTest<T extends EntityInterface, K extends Cr
public static final String DATA_CONSUMER_ROLE_NAME = "DataConsumer";
public static final String ENTITY_LINK_MATCH_ERROR =
"[entityLink must match \"^(?U)<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#]+>$\"]";
"[entityLink must match \"^(?U)<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\"]";
// Random unicode string generator to test entity name accepts all the unicode characters
protected static final RandomStringGenerator RANDOM_STRING_GENERATOR =

View File

@ -199,7 +199,7 @@ public class FeedResourceTest extends OpenMetadataApplicationTest {
// Create thread without addressed to entity in the request
CreateThread create = create().withFrom(USER.getName()).withAbout("<>"); // Invalid EntityLink
String failureReason = "[about must match \"^(?U)<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#]+>$\"]";
String failureReason = "[about must match \"^(?U)<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$\"]";
assertResponseContains(() -> createThread(create, USER_AUTH_HEADERS), BAD_REQUEST, failureReason);
create.withAbout("<#E::>"); // Invalid EntityLink - missing entityType and entityId

View File

@ -93,7 +93,7 @@
"entityLink": {
"description": "Link to an entity or field within an entity using this format `<#E::{entities}::{entityType}::{field}::{arrayFieldName}::{arrayFieldValue}`.",
"type": "string",
"pattern": "^(?U)<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#]+>$"
"pattern": "^(?U)<#E::\\w+::[\\w'\\- .&/:+\"\\\\()$#%]+>$"
},
"entityName": {
"description": "Name that identifies a entity.",

View File

@ -38,6 +38,7 @@ import { useHistory, useParams } from 'react-router-dom';
import { getAllFeeds, getFeedCount } from 'rest/feedsAPI';
import { getCountBadge, getEntityDetailLink } from 'utils/CommonUtils';
import { ENTITY_LINK_SEPARATOR, getEntityFeedLink } from 'utils/EntityUtils';
import { getEncodedFqn } from 'utils/StringsUtils';
import '../../Widgets/FeedsWidget/feeds-widget.less';
import ActivityFeedEditor from '../ActivityFeedEditor/ActivityFeedEditor';
import ActivityFeedListV1 from '../ActivityFeedList/ActivityFeedListV1.component';
@ -108,7 +109,12 @@ export const ActivityFeedTab = ({
const handleTabChange = (subTab: string) => {
history.push(
getEntityDetailLink(entityType, fqn, EntityTabs.ACTIVITY_FEED, subTab)
getEntityDetailLink(
entityType,
EntityType.TABLE === entityType ? getEncodedFqn(fqn) : fqn,
EntityTabs.ACTIVITY_FEED,
subTab
)
);
setActiveThread();
};

View File

@ -25,6 +25,7 @@ import { capitalize, isEmpty, isUndefined } from 'lodash';
import { LoadingState } from 'Models';
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { getEncodedFqn } from 'utils/StringsUtils';
import { showErrorToast } from 'utils/ToastUtils';
import { getServiceDetailsPath } from '../../constants/constants';
import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants';
@ -171,7 +172,12 @@ const AddService = ({
// View new service
const handleViewServiceClick = () => {
if (!isUndefined(newServiceData)) {
history.push(getServiceDetailsPath(newServiceData.name, serviceCategory));
history.push(
getServiceDetailsPath(
getEncodedFqn(newServiceData.name),
serviceCategory
)
);
}
};

View File

@ -68,6 +68,7 @@ import { withActivityFeed } from 'components/router/withActivityFeed';
import TableDescription from 'components/TableDescription/TableDescription.component';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
const DashboardDetails = ({
charts,
@ -219,7 +220,9 @@ const DashboardDetails = ({
const handleTabChange = (activeKey: string) => {
if (activeKey !== activeTab) {
history.push(getDashboardDetailsPath(dashboardFQN, activeKey));
history.push(
getDashboardDetailsPath(getDecodedFqn(dashboardFQN), activeKey)
);
}
};

View File

@ -43,6 +43,7 @@ import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getFeedCounts, refreshPage } from 'utils/CommonUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import { getTagsWithoutTier } from 'utils/TableUtils';
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
import { DataModelDetailsProps } from './DataModelDetails.interface';
@ -148,7 +149,10 @@ const DataModelDetails = ({
const handleTabChange = (tabValue: EntityTabs) => {
if (tabValue !== activeTab) {
history.push({
pathname: getDataModelDetailsPath(dashboardDataModelFQN, tabValue),
pathname: getDataModelDetailsPath(
getDecodedFqn(dashboardDataModelFQN),
tabValue
),
});
}
};

View File

@ -132,7 +132,7 @@ export default function EntitySummaryPanel({
(entityDetails.details.fullyQualifiedName &&
entityDetails.details.entityType &&
getEntityLinkFromType(
getEncodedFqn(entityDetails.details.fullyQualifiedName),
entityDetails.details.fullyQualifiedName,
entityDetails.details.entityType as EntityType
)) ??
'',

View File

@ -31,7 +31,7 @@ import {
getEntityLinkFromType,
getEntityName,
} from 'utils/EntityUtils';
import { getEncodedFqn, stringToHTML } from 'utils/StringsUtils';
import { stringToHTML } from 'utils/StringsUtils';
import { getServiceIcon, getUsagePercentile } from 'utils/TableUtils';
import './explore-search-card.less';
import { ExploreSearchCardProps } from './ExploreSearchCard.interface';
@ -140,7 +140,7 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
to={
source.fullyQualifiedName && source.entityType
? getEntityLinkFromType(
getEncodedFqn(source.fullyQualifiedName),
source.fullyQualifiedName,
source.entityType as EntityType
)
: ''

View File

@ -22,6 +22,7 @@ import React, {
} from 'react';
import { useTranslation } from 'react-i18next';
import { getRunHistoryForPipeline } from 'rest/ingestionPipelineAPI';
import { getEncodedFqn } from 'utils/StringsUtils';
import {
IngestionPipeline,
PipelineStatus,
@ -54,7 +55,7 @@ export const IngestionRecentRuns: FunctionComponent<Props> = ({
setLoading(true);
try {
const response = await getRunHistoryForPipeline(
ingestion.fullyQualifiedName || '',
getEncodedFqn(ingestion.fullyQualifiedName || ''),
queryParams
);

View File

@ -21,6 +21,7 @@ import { useTranslation } from 'react-i18next';
import { Link, useHistory } from 'react-router-dom';
import { getLoadingStatus } from 'utils/CommonUtils';
import { getEditIngestionPath, getLogsViewerPath } from 'utils/RouterUtils';
import { getEncodedFqn } from 'utils/StringsUtils';
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
import { PipelineActionsProps } from './PipelineActions.interface';
@ -83,7 +84,9 @@ function PipelineActions({
getEditIngestionPath(
serviceCategory,
serviceName,
ingestion.fullyQualifiedName || `${serviceName}.${ingestion.name}`,
getEncodedFqn(
ingestion.fullyQualifiedName || `${serviceName}.${ingestion.name}`
),
ingestion.pipelineType
)
);
@ -214,7 +217,7 @@ function PipelineActions({
to={getLogsViewerPath(
serviceCategory,
record.service?.name || '',
record?.fullyQualifiedName || record?.name || ''
getEncodedFqn(record?.fullyQualifiedName || record?.name || '')
)}>
<Button
className="p-x-xss"

View File

@ -36,6 +36,7 @@ import { useHistory, useParams } from 'react-router-dom';
import { restoreMlmodel } from 'rest/mlModelAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import AppState from '../../AppState';
import { getMlModelDetailsPath } from '../../constants/constants';
import { EntityField } from '../../constants/Feeds.constants';
@ -155,7 +156,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
const handleTabChange = (activeKey: string) => {
if (activeKey !== activeTab) {
history.push(getMlModelDetailsPath(mlModelFqn, activeKey));
history.push(getMlModelDetailsPath(getDecodedFqn(mlModelFqn), activeKey));
}
};

View File

@ -44,6 +44,7 @@ import { Link, useHistory, useParams } from 'react-router-dom';
import { postThread } from 'rest/feedsAPI';
import { restorePipeline } from 'rest/pipelineAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import { ReactComponent as ExternalLinkIcon } from '../../assets/svg/external-links.svg';
import {
getPipelineDetailsPath,
@ -483,7 +484,7 @@ const PipelineDetails = ({
const handleTabChange = (tabValue: string) => {
if (tabValue !== tab) {
history.push({
pathname: getPipelineDetailsPath(pipelineFQN, tabValue),
pathname: getPipelineDetailsPath(getDecodedFqn(pipelineFQN), tabValue),
});
}
};
@ -590,7 +591,7 @@ const PipelineDetails = ({
<div
className="tw-mt-4 tw-ml-4 d-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8"
data-testid="no-tasks-data">
<span>{t('label.no-task-available')}</span>
<span>{t('server.no-task-available')}</span>
</div>
)}
</Col>

View File

@ -22,6 +22,7 @@ import { getTagDisplay, getTagTooltip } from 'utils/TagsUtils';
import { ReactComponent as IconTag } from 'assets/svg/classification.svg';
import { TAG_START_WITH } from 'constants/Tag.constants';
import { reduceColorOpacity } from 'utils/CommonUtils';
import { getEncodedFqn } from 'utils/StringsUtils';
import { ReactComponent as IconTerm } from '../../../assets/svg/book.svg';
import { ReactComponent as PlusIcon } from '../../../assets/svg/plus-primary.svg';
import { TagsV1Props } from './TagsV1.interface';
@ -78,8 +79,10 @@ const TagsV1 = ({
const redirectLink = useCallback(
() =>
tag.source === TagSource.Glossary
? history.push(`${ROUTES.GLOSSARY}/${tag.tagFQN}`)
: history.push(`${ROUTES.TAGS}/${tag.tagFQN.split('.')[0]}`),
? history.push(`${ROUTES.GLOSSARY}/${getEncodedFqn(tag.tagFQN)}`)
: history.push(
`${ROUTES.TAGS}/${getEncodedFqn(tag.tagFQN.split('.')[0])}`
),
[tag.source, tag.tagFQN]
);

View File

@ -38,6 +38,7 @@ import { useHistory, useParams } from 'react-router-dom';
import { restoreTopic } from 'rest/topicsAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import { EntityField } from '../../constants/Feeds.constants';
import { EntityTabs, EntityType } from '../../enums/entity.enum';
import { Topic } from '../../generated/entity/data/topic';
@ -169,7 +170,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
const handleTabChange = (activeKey: string) => {
if (activeKey !== activeTab) {
history.push(getTopicDetailsPath(topicFQN, activeKey));
history.push(getTopicDetailsPath(getDecodedFqn(topicFQN), activeKey));
}
};

View File

@ -31,10 +31,12 @@ import {
import { getGlossariesByName, getGlossaryTermByFQN } from 'rest/glossaryAPI';
import { getMlModelByFQN } from 'rest/mlModelAPI';
import { getPipelineByFqn } from 'rest/pipelineAPI';
import { getContainerByFQN } from 'rest/storageAPI';
import { getTableDetailsByFQN } from 'rest/tableAPI';
import { getTopicByFqn } from 'rest/topicsAPI';
import { getTableFQNFromColumnFQN } from 'utils/CommonUtils';
import { getEntityName } from 'utils/EntityUtils';
import { getEncodedFqn } from 'utils/StringsUtils';
import AppState from '../../../AppState';
import { EntityType } from '../../../enums/entity.enum';
import { Table } from '../../../generated/entity/data/table';
@ -109,6 +111,11 @@ const PopoverContent: React.FC<{
break;
case EntityType.CONTAINER:
promise = getContainerByFQN(entityFQN, 'owner', Include.All);
break;
default:
break;
}
@ -169,7 +176,12 @@ const EntityPopOverCard: FC<Props> = ({ children, entityType, entityFQN }) => {
return (
<Popover
align={{ targetOffset: [0, -10] }}
content={<PopoverContent entityFQN={entityFQN} entityType={entityType} />}
content={
<PopoverContent
entityFQN={getEncodedFqn(entityFQN)}
entityType={entityType}
/>
}
overlayClassName="entity-popover-card"
trigger="hover"
zIndex={9999}>

View File

@ -341,7 +341,7 @@ export const IN_PAGE_SEARCH_ROUTES: Record<string, Array<string>> = {
export const getTableDetailsPath = (tableFQN: string, columnName?: string) => {
let path = ROUTES.TABLE_DETAILS;
path = path.replace(PLACEHOLDER_ROUTE_TABLE_FQN, tableFQN);
path = path.replace(PLACEHOLDER_ROUTE_TABLE_FQN, getEncodedFqn(tableFQN));
return `${path}${columnName ? `.${columnName}` : ''}`;
};

View File

@ -72,6 +72,7 @@ import {
import { getEntityName, getEntityThreadLink } from 'utils/EntityUtils';
import { getEntityFieldThreadCounts } from 'utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils';
import { showErrorToast, showSuccessToast } from 'utils/ToastUtils';
@ -235,7 +236,10 @@ const ContainerPage = () => {
const handleTabChange = (tabValue: string) => {
if (tabValue !== tab) {
history.push({
pathname: getContainerDetailPath(containerName, tabValue),
pathname: getContainerDetailPath(
getDecodedFqn(containerName),
tabValue
),
});
}
};
@ -562,7 +566,7 @@ const ContainerPage = () => {
children: (
<ActivityFeedTab
entityType={EntityType.CONTAINER}
fqn={containerName}
fqn={getDecodedFqn(containerName)}
onFeedUpdate={getEntityFeedCount}
onUpdateEntityDetails={() => fetchContainerDetail(containerName)}
/>

View File

@ -32,6 +32,7 @@ import {
OperationPermission,
ResourceEntity,
} from 'components/PermissionProvider/PermissionProvider.interface';
import { withActivityFeed } from 'components/router/withActivityFeed';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
@ -88,6 +89,7 @@ import {
} from '../../utils/EntityUtils';
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
import { getDecodedFqn } from '../../utils/StringsUtils';
import {
getTagsWithoutTier,
getTierTags,
@ -286,7 +288,7 @@ const DatabaseDetails: FunctionComponent = () => {
const activeTabHandler = (key: string) => {
if (key !== activeTab) {
history.push({
pathname: getDatabaseDetailsPath(databaseFQN, key),
pathname: getDatabaseDetailsPath(getDecodedFqn(databaseFQN), key),
});
}
};
@ -421,12 +423,14 @@ const DatabaseDetails: FunctionComponent = () => {
title: t('label.owner'),
dataIndex: 'owner',
key: 'owner',
width: 120,
render: (text: EntityReference) => getEntityName(text) || '--',
},
{
title: t('label.usage'),
dataIndex: 'usageSummary',
key: 'usageSummary',
width: 120,
render: (text: UsageDetails) =>
getUsagePercentile(text?.weeklyStats?.percentileRank ?? 0),
},
@ -776,4 +780,4 @@ const DatabaseDetails: FunctionComponent = () => {
);
};
export default observer(DatabaseDetails);
export default observer(withActivityFeed(DatabaseDetails));

View File

@ -28,6 +28,7 @@ import {
OperationPermission,
ResourceEntity,
} from 'components/PermissionProvider/PermissionProvider.interface';
import { withActivityFeed } from 'components/router/withActivityFeed';
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
import { DisplayType } from 'components/Tag/TagsViewer/TagsViewer.interface';
@ -58,6 +59,7 @@ import { getFeedCount, postThread } from 'rest/feedsAPI';
import { getTableList, TableListParams } from 'rest/tableAPI';
import { handleDataAssetAfterDeleteAction } from 'utils/Assets/AssetsUtils';
import { getEntityMissingError } from 'utils/CommonUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import { default as appState } from '../../AppState';
import {
getDatabaseSchemaDetailsPath,
@ -206,7 +208,7 @@ const DatabaseSchemaPage: FunctionComponent = () => {
try {
const res = await getTableList({
...params,
databaseSchema: databaseSchemaFQN,
databaseSchema: getDecodedFqn(databaseSchemaFQN),
include: showDeletedTables ? Include.Deleted : Include.NonDeleted,
});
setTableData(res);
@ -274,7 +276,10 @@ const DatabaseSchemaPage: FunctionComponent = () => {
(activeKey: string) => {
if (activeKey !== activeTab) {
history.push({
pathname: getDatabaseSchemaDetailsPath(databaseSchemaFQN, activeKey),
pathname: getDatabaseSchemaDetailsPath(
getDecodedFqn(databaseSchemaFQN),
activeKey
),
});
}
},
@ -636,4 +641,4 @@ const DatabaseSchemaPage: FunctionComponent = () => {
);
};
export default observer(DatabaseSchemaPage);
export default observer(withActivityFeed(DatabaseSchemaPage));

View File

@ -33,6 +33,7 @@ import {
getIngestionPipelineByName,
getIngestionPipelineLogById,
} from 'rest/ingestionPipelineAPI';
import { getDecodedFqn } from 'utils/StringsUtils';
import { PipelineType } from '../../generated/api/services/ingestionPipelines/createIngestionPipeline';
import { IngestionPipeline } from '../../generated/entity/services/ingestionPipelines/ingestionPipeline';
import { Paging } from '../../generated/type/paging';
@ -232,7 +233,7 @@ const LogsViewer = () => {
<TitleBreadcrumb
titleLinks={getLogBreadCrumbs(
logEntityType,
ingestionName,
getDecodedFqn(ingestionName),
ingestionDetails
)}
/>

View File

@ -108,6 +108,7 @@ import {
getResourceEntityFromServiceCategory,
shouldTestConnection,
} from 'utils/ServiceUtils';
import { getDecodedFqn } from 'utils/StringsUtils';
import { getTagsWithoutTier } from 'utils/TableUtils';
import { showErrorToast } from 'utils/ToastUtils';
import ServiceMainTabContent from './ServiceMainTabContent';
@ -238,7 +239,7 @@ const ServiceDetailsPage: FunctionComponent = () => {
setIsLoading(true);
const response = await getIngestionPipelines(
['owner', 'pipelineStatuses'],
serviceFQN,
getDecodedFqn(serviceFQN),
paging
);
@ -377,7 +378,7 @@ const ServiceDetailsPage: FunctionComponent = () => {
const fetchDatabases = useCallback(
async (paging?: PagingWithoutTotal) => {
const { data, paging: resPaging } = await getDatabases(
decodeURIComponent(serviceFQN),
getDecodedFqn(serviceFQN),
'owner,tags,usageSummary',
paging,
include
@ -406,7 +407,7 @@ const ServiceDetailsPage: FunctionComponent = () => {
const fetchDashboards = useCallback(
async (paging?: PagingWithoutTotal) => {
const { data, paging: resPaging } = await getDashboards(
serviceFQN,
getDecodedFqn(serviceFQN),
'owner,usageSummary,tags',
paging,
include
@ -422,7 +423,7 @@ const ServiceDetailsPage: FunctionComponent = () => {
try {
setIsServiceLoading(true);
const { data, paging: resPaging } = await getDataModels({
service: serviceFQN,
service: getDecodedFqn(serviceFQN),
fields: 'owner,tags,followers',
include,
...params,
@ -443,7 +444,7 @@ const ServiceDetailsPage: FunctionComponent = () => {
const fetchPipeLines = useCallback(
async (paging?: PagingWithoutTotal) => {
const { data, paging: resPaging } = await getPipelines(
serviceFQN,
getDecodedFqn(serviceFQN),
'owner,tags',
paging,
include
@ -457,7 +458,7 @@ const ServiceDetailsPage: FunctionComponent = () => {
const fetchMlModal = useCallback(
async (paging?: PagingWithoutTotal) => {
const { data, paging: resPaging } = await getMlModels(
serviceFQN,
getDecodedFqn(serviceFQN),
'owner,tags',
paging,
include
@ -471,7 +472,7 @@ const ServiceDetailsPage: FunctionComponent = () => {
const fetchContainers = useCallback(
async (paging?: PagingWithoutTotal) => {
const response = await getContainers({
service: serviceFQN,
service: getDecodedFqn(serviceFQN),
fields: 'owner,tags',
paging,
root: true,

View File

@ -85,6 +85,7 @@ import { getEntityFieldThreadCounts } from './FeedUtils';
import Fqn from './Fqn';
import { getGlossaryPath, getSettingPath } from './RouterUtils';
import { getServiceRouteFromServiceType } from './ServiceUtils';
import { getEncodedFqn } from './StringsUtils';
import {
getDataTypeString,
getTierFromTableTags,
@ -926,9 +927,9 @@ export const getEntityLinkFromType = (
return getTableDetailsPath(fullyQualifiedName);
case EntityType.GLOSSARY:
case EntityType.GLOSSARY_TERM:
return getGlossaryTermDetailsPath(fullyQualifiedName);
return getGlossaryTermDetailsPath(getEncodedFqn(fullyQualifiedName));
case EntityType.TAG:
return getTagsDetailsPath(fullyQualifiedName);
return getTagsDetailsPath(getEncodedFqn(fullyQualifiedName));
case EntityType.TOPIC:
return getTopicDetailsPath(fullyQualifiedName);
case EntityType.DASHBOARD:
@ -957,7 +958,7 @@ export const getBreadcrumbForTable = (
name: getEntityName(service),
url: service?.name
? getServiceDetailsPath(
service?.name,
getEncodedFqn(service?.name),
ServiceCategory.DATABASE_SERVICES
)
: '',
@ -997,7 +998,7 @@ export const getBreadcrumbForEntitiesWithServiceOnly = (
name: getEntityName(service),
url: service?.name
? getServiceDetailsPath(
service?.name,
getEncodedFqn(service?.name),
ServiceCategoryPlural[
service?.type as keyof typeof ServiceCategoryPlural
]
@ -1031,7 +1032,7 @@ export const getBreadcrumbForContainer = (data: {
name: getEntityName(service),
url: service?.name
? getServiceDetailsPath(
service?.name,
getEncodedFqn(service?.name),
ServiceCategoryPlural[
service?.type as keyof typeof ServiceCategoryPlural
]
@ -1136,7 +1137,7 @@ export const getEntityBreadcrumbs = (
name: getEntityName((entity as DatabaseSchema).service),
url: (entity as DatabaseSchema).service?.name
? getServiceDetailsPath(
(entity as DatabaseSchema).service?.name ?? '',
getEncodedFqn((entity as DatabaseSchema).service?.name ?? ''),
ServiceCategoryPlural[
(entity as DatabaseSchema).service
?.type as keyof typeof ServiceCategoryPlural

View File

@ -116,6 +116,7 @@ import {
} from './CommonUtils';
import { getDashboardURL } from './DashboardServiceUtils';
import { getBrokers } from './MessagingServiceUtils';
import { getEncodedFqn } from './StringsUtils';
import { getEntityLink } from './TableUtils';
import { showErrorToast } from './ToastUtils';
@ -697,6 +698,6 @@ export const getLinkForFqn = (serviceCategory: ServiceTypes, fqn: string) => {
case ServiceCategory.DATABASE_SERVICES:
default:
return `/database/${fqn}`;
return `/database/${getEncodedFqn(fqn)}`;
}
};

View File

@ -70,7 +70,7 @@ import {
} from './CommonUtils';
import { getGlossaryPath, getSettingPath } from './RouterUtils';
import { serviceTypeLogo } from './ServiceUtils';
import { ordinalize } from './StringsUtils';
import { getDecodedFqn, ordinalize } from './StringsUtils';
import SVGIcons, { Icons } from './SvgUtils';
export const getUsagePercentile = (pctRank: number, isLiteral = false) => {
@ -180,26 +180,26 @@ export const getEntityLink = (
switch (indexType) {
case SearchIndex.TOPIC:
case EntityType.TOPIC:
return getTopicDetailsPath(fullyQualifiedName);
return getTopicDetailsPath(getDecodedFqn(fullyQualifiedName));
case SearchIndex.DASHBOARD:
case EntityType.DASHBOARD:
return getDashboardDetailsPath(fullyQualifiedName);
return getDashboardDetailsPath(getDecodedFqn(fullyQualifiedName));
case SearchIndex.PIPELINE:
case EntityType.PIPELINE:
return getPipelineDetailsPath(fullyQualifiedName);
return getPipelineDetailsPath(getDecodedFqn(fullyQualifiedName));
case EntityType.DATABASE:
return getDatabaseDetailsPath(fullyQualifiedName);
return getDatabaseDetailsPath(getDecodedFqn(fullyQualifiedName));
case EntityType.DATABASE_SCHEMA:
return getDatabaseSchemaDetailsPath(fullyQualifiedName);
return getDatabaseSchemaDetailsPath(getDecodedFqn(fullyQualifiedName));
case EntityType.GLOSSARY:
case EntityType.GLOSSARY_TERM:
case SearchIndex.GLOSSARY:
return getGlossaryPath(fullyQualifiedName);
return getGlossaryPath(getDecodedFqn(fullyQualifiedName));
case EntityType.DATABASE_SERVICE:
case EntityType.DASHBOARD_SERVICE:
@ -222,12 +222,12 @@ export const getEntityLink = (
case EntityType.CONTAINER:
case SearchIndex.CONTAINER:
return getContainerDetailPath(fullyQualifiedName);
return getContainerDetailPath(getDecodedFqn(fullyQualifiedName));
case SearchIndex.TAG:
return getTagsDetailsPath(fullyQualifiedName);
case EntityType.DASHBOARD_DATA_MODEL:
return getDataModelDetailsPath(fullyQualifiedName);
return getDataModelDetailsPath(getDecodedFqn(fullyQualifiedName));
case EntityType.TEST_CASE:
return `${getTableTabPath(
@ -238,7 +238,7 @@ export const getEntityLink = (
case SearchIndex.TABLE:
case EntityType.TABLE:
default:
return getTableDetailsPath(fullyQualifiedName);
return getTableDetailsPath(getDecodedFqn(fullyQualifiedName));
}
};