feat(customSearchCards): add descriptions (#14472)

Co-authored-by: Chris Collins <chriscollins3456@gmail.com>
This commit is contained in:
v-tarasevich-blitz-brain 2025-08-27 20:28:36 +03:00 committed by GitHub
parent b1d96ab3bb
commit 3fe39bf149
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 531 additions and 57 deletions

View File

@ -314,15 +314,7 @@ import com.linkedin.datahub.graphql.types.view.DataHubViewType;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.entity.client.SystemEntityClient;
import com.linkedin.metadata.client.UsageStatsJavaClient;
import com.linkedin.metadata.config.ChromeExtensionConfiguration;
import com.linkedin.metadata.config.DataHubConfiguration;
import com.linkedin.metadata.config.GraphQLConfiguration;
import com.linkedin.metadata.config.HomePageConfiguration;
import com.linkedin.metadata.config.IngestionConfiguration;
import com.linkedin.metadata.config.SearchBarConfiguration;
import com.linkedin.metadata.config.TestsConfiguration;
import com.linkedin.metadata.config.ViewsConfiguration;
import com.linkedin.metadata.config.VisualConfiguration;
import com.linkedin.metadata.config.*;
import com.linkedin.metadata.config.telemetry.TelemetryConfiguration;
import com.linkedin.metadata.connection.ConnectionService;
import com.linkedin.metadata.entity.EntityService;
@ -433,6 +425,7 @@ public class GmsGraphQLEngine {
private final DataHubConfiguration datahubConfiguration;
private final ViewsConfiguration viewsConfiguration;
private final SearchBarConfiguration searchBarConfiguration;
private final SearchCardConfiguration searchCardConfiguration;
private final HomePageConfiguration homePageConfiguration;
private final ChromeExtensionConfiguration chromeExtensionConfiguration;
@ -567,6 +560,7 @@ public class GmsGraphQLEngine {
this.datahubConfiguration = args.datahubConfiguration;
this.viewsConfiguration = args.viewsConfiguration;
this.searchBarConfiguration = args.searchBarConfiguration;
this.searchCardConfiguration = args.searchCardConfiguration;
this.homePageConfiguration = args.homePageConfiguration;
this.featureFlags = args.featureFlags;
this.chromeExtensionConfiguration = args.chromeExtensionConfiguration;
@ -972,6 +966,7 @@ public class GmsGraphQLEngine {
this.datahubConfiguration,
this.viewsConfiguration,
this.searchBarConfiguration,
this.searchCardConfiguration,
this.homePageConfiguration,
this.featureFlags,
this.chromeExtensionConfiguration,

View File

@ -13,15 +13,7 @@ import com.linkedin.datahub.graphql.featureflags.FeatureFlags;
import com.linkedin.entity.client.EntityClient;
import com.linkedin.entity.client.SystemEntityClient;
import com.linkedin.metadata.client.UsageStatsJavaClient;
import com.linkedin.metadata.config.ChromeExtensionConfiguration;
import com.linkedin.metadata.config.DataHubConfiguration;
import com.linkedin.metadata.config.GraphQLConfiguration;
import com.linkedin.metadata.config.HomePageConfiguration;
import com.linkedin.metadata.config.IngestionConfiguration;
import com.linkedin.metadata.config.SearchBarConfiguration;
import com.linkedin.metadata.config.TestsConfiguration;
import com.linkedin.metadata.config.ViewsConfiguration;
import com.linkedin.metadata.config.VisualConfiguration;
import com.linkedin.metadata.config.*;
import com.linkedin.metadata.config.telemetry.TelemetryConfiguration;
import com.linkedin.metadata.connection.ConnectionService;
import com.linkedin.metadata.entity.EntityService;
@ -77,6 +69,7 @@ public class GmsGraphQLEngineArgs {
DataHubConfiguration datahubConfiguration;
ViewsConfiguration viewsConfiguration;
SearchBarConfiguration searchBarConfiguration;
SearchCardConfiguration searchCardConfiguration;
HomePageConfiguration homePageConfiguration;
SiblingGraphService siblingGraphService;
GroupService groupService;

View File

@ -4,40 +4,11 @@ import com.datahub.authentication.AuthenticationConfiguration;
import com.datahub.authorization.AuthorizationConfiguration;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.featureflags.FeatureFlags;
import com.linkedin.datahub.graphql.generated.AnalyticsConfig;
import com.linkedin.datahub.graphql.generated.AppConfig;
import com.linkedin.datahub.graphql.generated.*;
import com.linkedin.datahub.graphql.generated.ApplicationConfig;
import com.linkedin.datahub.graphql.generated.AuthConfig;
import com.linkedin.datahub.graphql.generated.ChromeExtensionConfig;
import com.linkedin.datahub.graphql.generated.EntityProfileConfig;
import com.linkedin.datahub.graphql.generated.EntityProfilesConfig;
import com.linkedin.datahub.graphql.generated.EntityType;
import com.linkedin.datahub.graphql.generated.FeatureFlagsConfig;
import com.linkedin.datahub.graphql.generated.HomePageConfig;
import com.linkedin.datahub.graphql.generated.IdentityManagementConfig;
import com.linkedin.datahub.graphql.generated.LineageConfig;
import com.linkedin.datahub.graphql.generated.ManagedIngestionConfig;
import com.linkedin.datahub.graphql.generated.PersonalSidebarSection;
import com.linkedin.datahub.graphql.generated.PoliciesConfig;
import com.linkedin.datahub.graphql.generated.Privilege;
import com.linkedin.datahub.graphql.generated.QueriesTabConfig;
import com.linkedin.datahub.graphql.generated.ResourcePrivileges;
import com.linkedin.datahub.graphql.generated.SearchBarAPI;
import com.linkedin.datahub.graphql.generated.SearchBarConfig;
import com.linkedin.datahub.graphql.generated.SearchResultsVisualConfig;
import com.linkedin.datahub.graphql.generated.TelemetryConfig;
import com.linkedin.datahub.graphql.generated.TestsConfig;
import com.linkedin.datahub.graphql.generated.ThemeConfig;
import com.linkedin.datahub.graphql.generated.ViewsConfig;
import com.linkedin.datahub.graphql.generated.VisualConfig;
import com.linkedin.metadata.config.ChromeExtensionConfiguration;
import com.linkedin.metadata.config.DataHubConfiguration;
import com.linkedin.metadata.config.HomePageConfiguration;
import com.linkedin.metadata.config.IngestionConfiguration;
import com.linkedin.metadata.config.SearchBarConfiguration;
import com.linkedin.metadata.config.TestsConfiguration;
import com.linkedin.metadata.config.ViewsConfiguration;
import com.linkedin.metadata.config.VisualConfiguration;
import com.linkedin.metadata.config.*;
import com.linkedin.metadata.config.telemetry.TelemetryConfiguration;
import com.linkedin.metadata.service.SettingsService;
import com.linkedin.metadata.version.GitVersion;
@ -64,6 +35,7 @@ public class AppConfigResolver implements DataFetcher<CompletableFuture<AppConfi
private final DataHubConfiguration _datahubConfiguration;
private final ViewsConfiguration _viewsConfiguration;
private final SearchBarConfiguration _searchBarConfig;
private final SearchCardConfiguration _searchCardConfig;
private final HomePageConfiguration _homePageConfig;
private final FeatureFlags _featureFlags;
private final ChromeExtensionConfiguration _chromeExtensionConfiguration;
@ -82,6 +54,7 @@ public class AppConfigResolver implements DataFetcher<CompletableFuture<AppConfi
final DataHubConfiguration datahubConfiguration,
final ViewsConfiguration viewsConfiguration,
final SearchBarConfiguration searchBarConfig,
final SearchCardConfiguration searchCardConfig,
final HomePageConfiguration homePageConfig,
final FeatureFlags featureFlags,
final ChromeExtensionConfiguration chromeExtensionConfiguration,
@ -98,6 +71,7 @@ public class AppConfigResolver implements DataFetcher<CompletableFuture<AppConfi
_datahubConfiguration = datahubConfiguration;
_viewsConfiguration = viewsConfiguration;
_searchBarConfig = searchBarConfig;
_searchCardConfig = searchCardConfig;
_homePageConfig = homePageConfig;
_featureFlags = featureFlags;
_chromeExtensionConfiguration = chromeExtensionConfiguration;
@ -233,6 +207,10 @@ public class AppConfigResolver implements DataFetcher<CompletableFuture<AppConfi
}
appConfig.setSearchBarConfig(searchBarConfig);
final SearchCardConfig searchCardConfig = new SearchCardConfig();
searchCardConfig.setShowDescription(_searchCardConfig.getShowDescription());
appConfig.setSearchCardConfig(searchCardConfig);
final HomePageConfig homePageConfig = new HomePageConfig();
try {
homePageConfig.setFirstInPersonalSidebar(

View File

@ -217,6 +217,16 @@ type SearchBarConfig {
apiVariant: SearchBarAPI!
}
"""
Configurations related to the Search card
"""
type SearchCardConfig {
"""
Whether the search card should show description
"""
showDescription: Boolean!
}
"""
Variants of APIs used in the Search bar to get data
"""
@ -318,6 +328,11 @@ type AppConfig {
"""
searchBarConfig: SearchBarConfig!
"""
Configurations related to the Search card
"""
searchCardConfig: SearchCardConfig!
"""
Feature flags telling the UI whether a feature is enabled or not
"""

View File

@ -0,0 +1,411 @@
package com.linkedin.datahub.graphql.resolvers.config;
import static com.linkedin.datahub.graphql.TestUtils.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.testng.Assert.*;
import com.datahub.authentication.AuthenticationConfiguration;
import com.datahub.authorization.AuthorizationConfiguration;
import com.datahub.authorization.DefaultAuthorizerConfiguration;
import com.linkedin.datahub.graphql.QueryContext;
import com.linkedin.datahub.graphql.featureflags.FeatureFlags;
import com.linkedin.datahub.graphql.generated.AppConfig;
import com.linkedin.datahub.graphql.generated.PersonalSidebarSection;
import com.linkedin.datahub.graphql.generated.SearchBarAPI;
import com.linkedin.metadata.config.*;
import com.linkedin.metadata.config.telemetry.TelemetryConfiguration;
import com.linkedin.metadata.service.SettingsService;
import com.linkedin.metadata.version.GitVersion;
import com.linkedin.settings.global.ApplicationsSettings;
import com.linkedin.settings.global.GlobalSettingsInfo;
import graphql.schema.DataFetchingEnvironment;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class AppConfigResolverTest {
@Mock private GitVersion mockGitVersion;
@Mock private IngestionConfiguration mockIngestionConfiguration;
@Mock private AuthenticationConfiguration mockAuthenticationConfiguration;
@Mock private AuthorizationConfiguration mockAuthorizationConfiguration;
@Mock private DefaultAuthorizerConfiguration mockDefaultAuthorizer;
@Mock private VisualConfiguration mockVisualConfiguration;
@Mock private TelemetryConfiguration mockTelemetryConfiguration;
@Mock private TestsConfiguration mockTestsConfiguration;
@Mock private DataHubConfiguration mockDatahubConfiguration;
@Mock private ViewsConfiguration mockViewsConfiguration;
@Mock private SearchBarConfiguration mockSearchBarConfiguration;
@Mock private SearchCardConfiguration mockSearchCardConfiguration;
@Mock private HomePageConfiguration mockHomePageConfiguration;
@Mock private FeatureFlags mockFeatureFlags;
@Mock private ChromeExtensionConfiguration mockChromeExtensionConfiguration;
@Mock private SettingsService mockSettingsService;
@Mock private DataFetchingEnvironment mockDataFetchingEnvironment;
@Mock private GlobalSettingsInfo mockGlobalSettingsInfo;
private AppConfigResolver resolver;
private QueryContext mockContext;
@BeforeMethod
public void setupTest() {
MockitoAnnotations.openMocks(this);
mockContext = getMockAllowContext();
when(mockDataFetchingEnvironment.getContext()).thenReturn(mockContext);
// Setup basic mock responses
when(mockGitVersion.getVersion()).thenReturn("1.0.0");
when(mockIngestionConfiguration.isEnabled()).thenReturn(true);
when(mockAuthenticationConfiguration.isEnabled()).thenReturn(true);
when(mockAuthorizationConfiguration.getDefaultAuthorizer()).thenReturn(mockDefaultAuthorizer);
when(mockDefaultAuthorizer.isEnabled()).thenReturn(true);
when(mockTelemetryConfiguration.isEnableThirdPartyLogging()).thenReturn(false);
when(mockTestsConfiguration.isEnabled()).thenReturn(true);
when(mockViewsConfiguration.isEnabled()).thenReturn(true);
when(mockSearchBarConfiguration.getApiVariant()).thenReturn("AUTOCOMPLETE_FOR_MULTIPLE");
when(mockSearchCardConfiguration.getShowDescription()).thenReturn(true);
when(mockHomePageConfiguration.getFirstInPersonalSidebar()).thenReturn("YOUR_ASSETS");
when(mockChromeExtensionConfiguration.isEnabled()).thenReturn(false);
when(mockChromeExtensionConfiguration.isLineageEnabled()).thenReturn(false);
// Setup feature flags
setupFeatureFlags();
resolver =
new AppConfigResolver(
mockGitVersion,
true, // isAnalyticsEnabled
mockIngestionConfiguration,
mockAuthenticationConfiguration,
mockAuthorizationConfiguration,
true, // supportsImpactAnalysis
mockVisualConfiguration,
mockTelemetryConfiguration,
mockTestsConfiguration,
mockDatahubConfiguration,
mockViewsConfiguration,
mockSearchBarConfiguration,
mockSearchCardConfiguration,
mockHomePageConfiguration,
mockFeatureFlags,
mockChromeExtensionConfiguration,
mockSettingsService);
}
private void setupFeatureFlags() {
when(mockFeatureFlags.isShowSearchFiltersV2()).thenReturn(false);
when(mockFeatureFlags.isBusinessAttributeEntityEnabled()).thenReturn(false);
when(mockFeatureFlags.isReadOnlyModeEnabled()).thenReturn(false);
when(mockFeatureFlags.isShowBrowseV2()).thenReturn(false);
when(mockFeatureFlags.isShowAcrylInfo()).thenReturn(false);
when(mockFeatureFlags.isErModelRelationshipFeatureEnabled()).thenReturn(false);
when(mockFeatureFlags.isShowAccessManagement()).thenReturn(false);
when(mockFeatureFlags.isNestedDomainsEnabled()).thenReturn(false);
when(mockFeatureFlags.isPlatformBrowseV2()).thenReturn(false);
when(mockFeatureFlags.isDataContractsEnabled()).thenReturn(false);
when(mockFeatureFlags.isEditableDatasetNameEnabled()).thenReturn(false);
when(mockFeatureFlags.isThemeV2Enabled()).thenReturn(false);
when(mockFeatureFlags.isThemeV2Default()).thenReturn(false);
when(mockFeatureFlags.isThemeV2Toggleable()).thenReturn(false);
when(mockFeatureFlags.isLineageGraphV2()).thenReturn(false);
when(mockFeatureFlags.isShowSeparateSiblings()).thenReturn(false);
when(mockFeatureFlags.isShowManageStructuredProperties()).thenReturn(false);
when(mockFeatureFlags.isSchemaFieldCLLEnabled()).thenReturn(false);
when(mockFeatureFlags.isHideDbtSourceInLineage()).thenReturn(false);
when(mockFeatureFlags.isSchemaFieldLineageIgnoreStatus()).thenReturn(false);
when(mockFeatureFlags.isShowNavBarRedesign()).thenReturn(false);
when(mockFeatureFlags.isShowAutoCompleteResults()).thenReturn(false);
when(mockFeatureFlags.isEntityVersioning()).thenReturn(false);
when(mockFeatureFlags.isShowHasSiblingsFilter()).thenReturn(false);
when(mockFeatureFlags.isShowSearchBarAutocompleteRedesign()).thenReturn(false);
when(mockFeatureFlags.isShowManageTags()).thenReturn(false);
when(mockFeatureFlags.isShowIntroducePage()).thenReturn(false);
when(mockFeatureFlags.isShowIngestionPageRedesign()).thenReturn(false);
when(mockFeatureFlags.isShowLineageExpandMore()).thenReturn(false);
when(mockFeatureFlags.isShowStatsTabRedesign()).thenReturn(false);
when(mockFeatureFlags.isShowHomePageRedesign()).thenReturn(false);
when(mockFeatureFlags.isShowProductUpdates()).thenReturn(false);
when(mockFeatureFlags.isLineageGraphV3()).thenReturn(false);
when(mockFeatureFlags.isLogicalModelsEnabled()).thenReturn(false);
when(mockFeatureFlags.isShowHomepageUserRole()).thenReturn(false);
when(mockFeatureFlags.isAssetSummaryPageV1()).thenReturn(false);
}
@Test
public void testGetBasicConfig() throws Exception {
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result);
assertEquals(result.getAppVersion(), "1.0.0");
assertNotNull(result.getLineageConfig());
assertTrue(result.getLineageConfig().getSupportsImpactAnalysis());
assertNotNull(result.getAnalyticsConfig());
assertTrue(result.getAnalyticsConfig().getEnabled());
assertNotNull(result.getAuthConfig());
assertTrue(result.getAuthConfig().getTokenAuthEnabled());
assertNotNull(result.getPoliciesConfig());
assertTrue(result.getPoliciesConfig().getEnabled());
assertNotNull(result.getIdentityManagementConfig());
assertTrue(result.getIdentityManagementConfig().getEnabled());
assertNotNull(result.getManagedIngestionConfig());
assertTrue(result.getManagedIngestionConfig().getEnabled());
assertNotNull(result.getTelemetryConfig());
assertFalse(result.getTelemetryConfig().getEnableThirdPartyLogging());
assertNotNull(result.getTestsConfig());
assertTrue(result.getTestsConfig().getEnabled());
assertNotNull(result.getViewsConfig());
assertTrue(result.getViewsConfig().getEnabled());
assertNotNull(result.getSearchBarConfig());
assertEquals(
result.getSearchBarConfig().getApiVariant(), SearchBarAPI.AUTOCOMPLETE_FOR_MULTIPLE);
assertNotNull(result.getSearchCardConfig());
assertTrue(result.getSearchCardConfig().getShowDescription());
assertNotNull(result.getHomePageConfig());
assertEquals(
result.getHomePageConfig().getFirstInPersonalSidebar(), PersonalSidebarSection.YOUR_ASSETS);
assertNotNull(result.getFeatureFlags());
assertNotNull(result.getChromeExtensionConfig());
assertFalse(result.getChromeExtensionConfig().getEnabled());
}
@Test
public void testGetConfigWithAnalyticsDisabled() throws Exception {
resolver =
new AppConfigResolver(
mockGitVersion,
false, // isAnalyticsEnabled
mockIngestionConfiguration,
mockAuthenticationConfiguration,
mockAuthorizationConfiguration,
true,
mockVisualConfiguration,
mockTelemetryConfiguration,
mockTestsConfiguration,
mockDatahubConfiguration,
mockViewsConfiguration,
mockSearchBarConfiguration,
mockSearchCardConfiguration,
mockHomePageConfiguration,
mockFeatureFlags,
mockChromeExtensionConfiguration,
mockSettingsService);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getAnalyticsConfig());
assertFalse(result.getAnalyticsConfig().getEnabled());
}
@Test
public void testGetConfigWithVisualConfiguration() throws Exception {
AssetsConfiguration mockAssets = mock(AssetsConfiguration.class);
QueriesTabConfig mockQueriesTab = mock(QueriesTabConfig.class);
EntityProfileConfig mockEntityProfile = mock(EntityProfileConfig.class);
SearchResultVisualConfig mockSearchResult = mock(SearchResultVisualConfig.class);
ThemeConfiguration mockTheme = mock(ThemeConfiguration.class);
when(mockVisualConfiguration.getAssets()).thenReturn(mockAssets);
when(mockAssets.getLogoUrl()).thenReturn("https://example.com/logo.png");
when(mockAssets.getFaviconUrl()).thenReturn("https://example.com/favicon.ico");
when(mockVisualConfiguration.getAppTitle()).thenReturn("Custom DataHub");
when(mockVisualConfiguration.isHideGlossary()).thenReturn(true);
when(mockVisualConfiguration.isShowFullTitleInLineage()).thenReturn(true);
when(mockVisualConfiguration.getQueriesTab()).thenReturn(mockQueriesTab);
when(mockQueriesTab.getQueriesTabResultSize()).thenReturn(20);
when(mockVisualConfiguration.getEntityProfile()).thenReturn(mockEntityProfile);
when(mockEntityProfile.getDomainDefaultTab()).thenReturn("DOCUMENTATION");
when(mockVisualConfiguration.getSearchResult()).thenReturn(mockSearchResult);
when(mockSearchResult.getEnableNameHighlight()).thenReturn(true);
when(mockVisualConfiguration.getTheme()).thenReturn(mockTheme);
when(mockTheme.getThemeId()).thenReturn("dark");
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getVisualConfig());
assertEquals(result.getVisualConfig().getLogoUrl(), "https://example.com/logo.png");
assertEquals(result.getVisualConfig().getFaviconUrl(), "https://example.com/favicon.ico");
assertEquals(result.getVisualConfig().getAppTitle(), "Custom DataHub");
assertTrue(result.getVisualConfig().getHideGlossary());
assertTrue(result.getVisualConfig().getShowFullTitleInLineage());
assertNotNull(result.getVisualConfig().getQueriesTab());
assertEquals(
result.getVisualConfig().getQueriesTab().getQueriesTabResultSize(), Integer.valueOf(20));
assertNotNull(result.getVisualConfig().getEntityProfiles());
assertNotNull(result.getVisualConfig().getEntityProfiles().getDomain());
assertEquals(
result.getVisualConfig().getEntityProfiles().getDomain().getDefaultTab(), "DOCUMENTATION");
assertNotNull(result.getVisualConfig().getSearchResult());
assertTrue(result.getVisualConfig().getSearchResult().getEnableNameHighlight());
assertNotNull(result.getVisualConfig().getTheme());
assertEquals(result.getVisualConfig().getTheme().getThemeId(), "dark");
}
@Test
public void testGetConfigWithApplicationsEnabled() throws Exception {
ApplicationsSettings mockApplications = mock(ApplicationsSettings.class);
when(mockGlobalSettingsInfo.hasApplications()).thenReturn(true);
when(mockGlobalSettingsInfo.getApplications()).thenReturn(mockApplications);
when(mockApplications.hasEnabled()).thenReturn(true);
when(mockApplications.isEnabled()).thenReturn(true);
when(mockSettingsService.getGlobalSettings(any())).thenReturn(mockGlobalSettingsInfo);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getVisualConfig());
assertNotNull(result.getVisualConfig().getApplication());
assertTrue(result.getVisualConfig().getApplication().getShowApplicationInNavigation());
assertTrue(result.getVisualConfig().getApplication().getShowSidebarSectionWhenEmpty());
}
@Test
public void testGetConfigWithApplicationsDisabled() throws Exception {
ApplicationsSettings mockApplications = mock(ApplicationsSettings.class);
when(mockGlobalSettingsInfo.hasApplications()).thenReturn(true);
when(mockGlobalSettingsInfo.getApplications()).thenReturn(mockApplications);
when(mockApplications.hasEnabled()).thenReturn(true);
when(mockApplications.isEnabled()).thenReturn(false);
when(mockSettingsService.getGlobalSettings(any())).thenReturn(mockGlobalSettingsInfo);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getVisualConfig());
assertNotNull(result.getVisualConfig().getApplication());
assertFalse(result.getVisualConfig().getApplication().getShowApplicationInNavigation());
assertFalse(result.getVisualConfig().getApplication().getShowSidebarSectionWhenEmpty());
}
@Test
public void testGetConfigWithNullGlobalSettings() throws Exception {
when(mockSettingsService.getGlobalSettings(any())).thenReturn(null);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getVisualConfig());
assertNotNull(result.getVisualConfig().getApplication());
assertFalse(result.getVisualConfig().getApplication().getShowApplicationInNavigation());
assertFalse(result.getVisualConfig().getApplication().getShowSidebarSectionWhenEmpty());
}
@Test
public void testGetConfigWithSearchCardDescriptionDisabled() throws Exception {
when(mockSearchCardConfiguration.getShowDescription()).thenReturn(false);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getSearchCardConfig());
assertFalse(result.getSearchCardConfig().getShowDescription());
}
@Test
public void testGetConfigWithFeatureFlagsEnabled() throws Exception {
// Enable some feature flags
when(mockFeatureFlags.isShowSearchFiltersV2()).thenReturn(true);
when(mockFeatureFlags.isBusinessAttributeEntityEnabled()).thenReturn(true);
when(mockFeatureFlags.isReadOnlyModeEnabled()).thenReturn(true);
when(mockFeatureFlags.isThemeV2Enabled()).thenReturn(true);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getFeatureFlags());
assertTrue(result.getFeatureFlags().getShowSearchFiltersV2());
assertTrue(result.getFeatureFlags().getBusinessAttributeEntityEnabled());
assertTrue(result.getFeatureFlags().getReadOnlyModeEnabled());
assertTrue(result.getFeatureFlags().getThemeV2Enabled());
}
@Test
public void testGetConfigWithInvalidSearchBarAPI() throws Exception {
when(mockSearchBarConfiguration.getApiVariant()).thenReturn("INVALID_API");
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getSearchBarConfig());
assertEquals(
result.getSearchBarConfig().getApiVariant(), SearchBarAPI.AUTOCOMPLETE_FOR_MULTIPLE);
}
@Test
public void testGetConfigWithInvalidPersonalSidebarSection() throws Exception {
when(mockHomePageConfiguration.getFirstInPersonalSidebar()).thenReturn("INVALID_SECTION");
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getHomePageConfig());
assertEquals(
result.getHomePageConfig().getFirstInPersonalSidebar(), PersonalSidebarSection.YOUR_ASSETS);
}
@Test
public void testGetConfigWithNullSettingsService() throws Exception {
resolver =
new AppConfigResolver(
mockGitVersion,
true,
mockIngestionConfiguration,
mockAuthenticationConfiguration,
mockAuthorizationConfiguration,
true,
mockVisualConfiguration,
mockTelemetryConfiguration,
mockTestsConfiguration,
mockDatahubConfiguration,
mockViewsConfiguration,
mockSearchBarConfiguration,
mockSearchCardConfiguration,
mockHomePageConfiguration,
mockFeatureFlags,
mockChromeExtensionConfiguration,
null // null settings service
);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result);
assertNotNull(result.getVisualConfig());
// Should not crash and should handle null settings service gracefully
}
@Test
public void testGetConfigWithNullVisualConfiguration() throws Exception {
resolver =
new AppConfigResolver(
mockGitVersion,
true,
mockIngestionConfiguration,
mockAuthenticationConfiguration,
mockAuthorizationConfiguration,
true,
null, // null visual configuration
mockTelemetryConfiguration,
mockTestsConfiguration,
mockDatahubConfiguration,
mockViewsConfiguration,
mockSearchBarConfiguration,
mockSearchCardConfiguration,
mockHomePageConfiguration,
mockFeatureFlags,
mockChromeExtensionConfiguration,
mockSettingsService);
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result);
assertNotNull(result.getVisualConfig());
// Should handle null visual configuration gracefully
}
@Test
public void testPoliciesConfigMapping() throws Exception {
AppConfig result = resolver.get(mockDataFetchingEnvironment).get();
assertNotNull(result.getPoliciesConfig());
assertNotNull(result.getPoliciesConfig().getPlatformPrivileges());
assertNotNull(result.getPoliciesConfig().getResourcePrivileges());
// Verify that the privileges lists are populated (they come from static config)
assertFalse(result.getPoliciesConfig().getPlatformPrivileges().isEmpty());
assertFalse(result.getPoliciesConfig().getResourcePrivileges().isEmpty());
}
}

View File

@ -0,0 +1,35 @@
import { Text } from '@components';
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { removeMarkdown } from '@app/entity/shared/components/styled/StripMarkdownText';
const StyledText = styled(Text)`
text-wrap: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
`;
export type Props = {
content: string;
clearMarkdown?: boolean;
};
export default function ShortMarkdownViewer({ content, clearMarkdown }: Props) {
const processedContent = useMemo(() => {
let processingContent = content;
if (clearMarkdown) {
processingContent = removeMarkdown(processingContent);
}
return processingContent;
}, [content, clearMarkdown]);
return (
<StyledText color="gray" colorLevel={1700} type="div">
{processedContent}
</StyledText>
);
}

View File

@ -4,6 +4,7 @@ import React, { ReactNode } from 'react';
import styled from 'styled-components';
import { useEntityContext, useEntityData } from '@app/entity/shared/EntityContext';
import { removeMarkdown } from '@app/entity/shared/components/styled/StripMarkdownText';
import { GenericEntityProperties } from '@app/entity/shared/types';
import { EntityMenuActions, PreviewType } from '@app/entityV2/Entity';
import { EntityMenuItems } from '@app/entityV2/shared/EntityDropdown/EntityMenuActions';
@ -15,6 +16,7 @@ import { GlossaryPreviewCardDecoration } from '@app/entityV2/shared/containers/p
import { PopularityTier } from '@app/entityV2/shared/containers/profile/sidebar/shared/utils';
import ViewInPlatform from '@app/entityV2/shared/externalUrl/ViewInPlatform';
import CompactMarkdownViewer from '@app/entityV2/shared/tabs/Documentation/components/CompactMarkdownViewer';
import ShortMarkdownViewer from '@app/entityV2/shared/tabs/Documentation/components/ShortMarkdownViewer';
import { DashboardLastUpdatedMs, DatasetLastUpdatedMs } from '@app/entityV2/shared/utils';
import ColoredBackgroundPlatformIconGroup from '@app/previewV2/ColoredBackgroundPlatformIconGroup';
import { CompactView } from '@app/previewV2/CompactView';
@ -24,6 +26,7 @@ import EntityHeader from '@app/previewV2/EntityHeader';
import { ActionsAndStatusSection } from '@app/previewV2/shared';
import { useRemoveDataProductAssets, useRemoveDomainAssets, useRemoveGlossaryTermAssets } from '@app/previewV2/utils';
import { useSearchContext } from '@app/search/context/SearchContext';
import { useAppConfig } from '@app/useAppConfig';
import { useEntityRegistryV2 } from '@app/useEntityRegistry';
import DataProcessInstanceInfo from '@src/app/preview/DataProcessInstanceInfo';
@ -105,8 +108,15 @@ const InsightIconContainer = styled.span`
margin-right: 4px;
`;
const Documentation = styled.div`
const DocumentationTopMarginWrapper = styled.div`
margin-top: 8px;
`;
const ShortDocumentation = styled(DocumentationTopMarginWrapper)`
width: 100%;
`;
const Documentation = styled(DocumentationTopMarginWrapper)`
max-height: 300px;
overflow-y: auto;
`;
@ -218,6 +228,15 @@ export default function DefaultPreviewCard({
}: Props) {
const entityRegistry = useEntityRegistryV2();
const supportedCapabilities = entityRegistry.getSupportedEntityCapabilities(entityType);
const { config } = useAppConfig();
const shouldShowRedesignedDescription = config.searchCardConfig.showDescription;
const shouldShowDescriptionsForSearch =
previewType === PreviewType.SEARCH && config.searchCardConfig.showDescription;
const shouldShowDescription =
previewType === PreviewType.HOVER_CARD ||
ENTITY_TYPES_WITH_DESCRIPTION_PREVIEW.has(entityType) ||
shouldShowDescriptionsForSearch;
// sometimes these lists will be rendered inside an entity container (for example, in the case of impact analysis)
// in those cases, we may want to enrich the preview w/ context about the container entity
@ -311,13 +330,17 @@ export default function DefaultPreviewCard({
entityTitleWidth={previewType === PreviewType.HOVER_CARD ? 150 : 200}
/>
</RowContainer>
{(previewType === PreviewType.HOVER_CARD ||
ENTITY_TYPES_WITH_DESCRIPTION_PREVIEW.has(entityType)) &&
description ? (
<Documentation>
<CompactMarkdownViewer content={description} />
</Documentation>
) : null}
{shouldShowDescription &&
!!description &&
(shouldShowRedesignedDescription ? (
<ShortDocumentation>
<ShortMarkdownViewer content={description} clearMarkdown />
</ShortDocumentation>
) : (
<Documentation>
<CompactMarkdownViewer content={removeMarkdown(description)} />
</Documentation>
))}
{shouldShowDPIinfo && (
<RowContainer style={{ marginTop: 8, justifyContent: 'flex-end' }}>
<DataProcessInstanceInfo {...lastRunEvent} />

View File

@ -47,6 +47,9 @@ export const DEFAULT_APP_CONFIG = {
searchBarConfig: {
apiVariant: SearchBarApi.AutocompleteForMultiple,
},
searchCardConfig: {
showDescription: false,
},
homePageConfig: {
firstInPersonalSidebar: PersonalSidebarSection.YourAssets,
},

View File

@ -69,6 +69,9 @@ query appConfig {
searchBarConfig {
apiVariant
}
searchCardConfig {
showDescription
}
homePageConfig {
firstInPersonalSidebar
}

View File

@ -751,6 +751,7 @@ public class PropertiesCollectorConfigurationTest extends AbstractTestNGSpringCo
"platformAnalytics.usageExport.usageEventTypes",
"platformAnalytics.usageExport.userFilters",
"searchBar.apiVariant",
"searchCard.showDescription",
"searchService.cache.hazelcast.serviceName",
"searchService.cache.hazelcast.service-dns-timeout",
"searchService.cache.hazelcast.kubernetes-api-retries",

View File

@ -37,6 +37,9 @@ public class DataHubAppConfiguration {
/** Search bar related configs */
private SearchBarConfiguration searchBar;
/** Search card related configs */
private SearchCardConfiguration searchCard;
/** Home page related configs */
private HomePageConfiguration homePage;

View File

@ -0,0 +1,10 @@
package com.linkedin.metadata.config;
import lombok.Data;
/** POJO representing the "searchCard" configuration block in application.yaml.on.yml */
@Data
public class SearchCardConfiguration {
/** If turned on, show the description in search card */
public Boolean showDescription;
}

View File

@ -744,6 +744,9 @@ views:
searchBar:
apiVariant: ${SEARCH_BAR_API_VARIANT:AUTOCOMPLETE_FOR_MULTIPLE}
searchCard:
showDescription: ${SEARCH_CARD_SHOW_DESCRIPTION:false}
homePage:
firstInPersonalSidebar: ${FIRST_IN_PERSONAL_SIDEBAR:YOUR_ASSETS}

View File

@ -257,6 +257,7 @@ public class GraphQLEngineFactory {
args.setDatahubConfiguration(configProvider.getDatahub());
args.setViewsConfiguration(configProvider.getViews());
args.setSearchBarConfiguration(configProvider.getSearchBar());
args.setSearchCardConfiguration(configProvider.getSearchCard());
args.setHomePageConfiguration(configProvider.getHomePage());
args.setSiblingGraphService(siblingGraphService);
args.setGroupService(groupService);