bulk writes and sidebar hiding

This commit is contained in:
Gabe Lyons 2025-06-10 08:24:39 -07:00
parent 1572dd7208
commit a7c39761b1
22 changed files with 118 additions and 1 deletions

View File

@ -6,6 +6,7 @@ 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.ApplicationConfig;
import com.linkedin.datahub.graphql.generated.AuthConfig;
import com.linkedin.datahub.graphql.generated.ChromeExtensionConfig;
import com.linkedin.datahub.graphql.generated.EntityProfileConfig;
@ -187,6 +188,12 @@ public class AppConfigResolver implements DataFetcher<CompletableFuture<AppConfi
}
visualConfig.setTheme(themeConfig);
}
if (_visualConfiguration != null && _visualConfiguration.getApplication() != null) {
ApplicationConfig applicationConfig = new ApplicationConfig();
applicationConfig.setShowSidebarSectionWhenEmpty(
_visualConfiguration.getApplication().isShowSidebarSectionWhenEmpty());
visualConfig.setApplication(applicationConfig);
}
appConfig.setVisualConfig(visualConfig);
final TelemetryConfig telemetryConfig = new TelemetryConfig();

View File

@ -357,6 +357,21 @@ type VisualConfig {
Configuration for custom theme-ing
"""
theme: ThemeConfig
"""
Configuration for the application sidebar section
"""
application: ApplicationConfig
}
"""
Configuration for the application sidebar section
"""
type ApplicationConfig {
"""
Whether to show the application sidebar section even when empty
"""
showSidebarSectionWhenEmpty: Boolean
}
"""

View File

@ -24,6 +24,10 @@ export enum EntityActionItem {
* Batch add a Data Product to a set of assets
*/
BATCH_ADD_DATA_PRODUCT,
/**
* Batch add an Application to a set of assets
*/
BATCH_ADD_APPLICATION,
}
interface Props {

View File

@ -96,6 +96,10 @@ export enum EntityCapabilityType {
* Lineage information of an entity
*/
LINEAGE,
/**
* Assigning the entity to an application
*/
APPLICATIONS,
}
export interface EntityMenuActions {

View File

@ -24,6 +24,7 @@ import { SidebarGlossaryTermsSection } from '@app/entityV2/shared/containers/pro
import { SidebarTagsSection } from '@app/entityV2/shared/containers/profile/sidebar/SidebarTagsSection';
import StatusSection from '@app/entityV2/shared/containers/profile/sidebar/shared/StatusSection';
import { getDataForEntityType } from '@app/entityV2/shared/containers/profile/utils';
import { EntityActionItem } from '@app/entityV2/shared/entity/EntityActions';
import SidebarNotesSection from '@app/entityV2/shared/sidebarSection/SidebarNotesSection';
import SidebarStructuredProperties from '@app/entityV2/shared/sidebarSection/SidebarStructuredProperties';
import { DocumentationTab } from '@app/entityV2/shared/tabs/Documentation/DocumentationTab';
@ -98,7 +99,7 @@ export class ApplicationEntity implements Entity<Application> {
useEntityQuery={useGetApplicationQuery}
useUpdateQuery={undefined}
getOverrideProperties={this.getOverridePropertiesFromEntity}
headerActionItems={new Set([])}
headerActionItems={new Set([EntityActionItem.BATCH_ADD_APPLICATION])}
headerDropdownItems={headerDropdownItems}
isNameEditable
tabs={[

View File

@ -385,6 +385,7 @@ export class ChartEntity implements Entity<Chart> {
EntityCapabilityType.TEST,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.HEALTH,
EntityCapabilityType.APPLICATIONS,
]);
};

View File

@ -392,6 +392,7 @@ export class DashboardEntity implements Entity<Dashboard> {
EntityCapabilityType.TEST,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.HEALTH,
EntityCapabilityType.APPLICATIONS,
]);
};

View File

@ -257,6 +257,7 @@ export class DataFlowEntity implements Entity<DataFlow> {
EntityCapabilityType.TEST,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.HEALTH,
EntityCapabilityType.APPLICATIONS,
]);
};
}

View File

@ -322,6 +322,7 @@ export class DataJobEntity implements Entity<DataJob> {
EntityCapabilityType.TEST,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.HEALTH,
EntityCapabilityType.APPLICATIONS,
]);
};
}

View File

@ -262,6 +262,7 @@ export class DataProductEntity implements Entity<DataProduct> {
EntityCapabilityType.GLOSSARY_TERMS,
EntityCapabilityType.TAGS,
EntityCapabilityType.DOMAINS,
EntityCapabilityType.APPLICATIONS,
]);
};

View File

@ -515,6 +515,7 @@ export class DatasetEntity implements Entity<Dataset> {
EntityCapabilityType.TEST,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.HEALTH,
EntityCapabilityType.APPLICATIONS,
]);
};

View File

@ -197,6 +197,7 @@ class GlossaryNodeEntity implements Entity<GlossaryNode> {
EntityCapabilityType.OWNERS,
EntityCapabilityType.DEPRECATION,
EntityCapabilityType.SOFT_DELETE,
EntityCapabilityType.APPLICATIONS,
]);
};

View File

@ -239,6 +239,7 @@ export class GlossaryTermEntity implements Entity<GlossaryTerm> {
EntityCapabilityType.OWNERS,
EntityCapabilityType.DEPRECATION,
EntityCapabilityType.SOFT_DELETE,
EntityCapabilityType.APPLICATIONS,
]);
};

View File

@ -268,6 +268,7 @@ export class MLFeatureEntity implements Entity<MlFeature> {
EntityCapabilityType.SOFT_DELETE,
EntityCapabilityType.DATA_PRODUCTS,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.APPLICATIONS,
]);
};
}

View File

@ -235,6 +235,7 @@ export class MLFeatureTableEntity implements Entity<MlFeatureTable> {
EntityCapabilityType.SOFT_DELETE,
EntityCapabilityType.DATA_PRODUCTS,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.APPLICATIONS,
]);
};
}

View File

@ -247,6 +247,7 @@ export class MLModelEntity implements Entity<MlModel> {
EntityCapabilityType.SOFT_DELETE,
EntityCapabilityType.DATA_PRODUCTS,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.APPLICATIONS,
]);
};
}

View File

@ -221,6 +221,7 @@ export class MLModelGroupEntity implements Entity<MlModelGroup> {
EntityCapabilityType.SOFT_DELETE,
EntityCapabilityType.DATA_PRODUCTS,
EntityCapabilityType.LINEAGE,
EntityCapabilityType.APPLICATIONS,
]);
};
}

View File

@ -11,6 +11,7 @@ import EmptySectionText from '@app/entityV2/shared/containers/profile/sidebar/Em
import SectionActionButton from '@app/entityV2/shared/containers/profile/sidebar/SectionActionButton';
import { SidebarSection } from '@app/entityV2/shared/containers/profile/sidebar/SidebarSection';
import { ApplicationLink } from '@app/shared/tags/ApplicationLink';
import { useAppConfig } from '@app/useAppConfig';
import { useBatchSetApplicationMutation } from '@graphql/application.generated';
@ -38,6 +39,9 @@ interface Props {
}
export const SidebarApplicationSection = ({ readOnly, properties }: Props) => {
const {
config: { visualConfig },
} = useAppConfig();
const updateOnly = properties?.updateOnly;
const { entityData } = useEntityData();
const refetch = useRefetch();
@ -47,6 +51,10 @@ export const SidebarApplicationSection = ({ readOnly, properties }: Props) => {
const application = entityData?.application?.application;
const canEditApplication = !!entityData?.privileges?.canEditProperties;
if (!application && !visualConfig.application?.showSidebarSectionWhenEmpty) {
return null;
}
const removeApplication = () => {
batchSetApplicationMutation({
variables: {

View File

@ -11,6 +11,7 @@ import { SearchSelectModal } from '@app/entityV2/shared/components/styled/search
import { handleBatchError } from '@app/entityV2/shared/utils';
import { useEntityRegistry } from '@app/useEntityRegistry';
import { useBatchSetApplicationMutation } from '@graphql/application.generated';
import { useBatchSetDataProductMutation } from '@graphql/dataProduct.generated';
import { useBatchAddTermsMutation, useBatchSetDomainMutation } from '@graphql/mutations.generated';
import { EntityType } from '@types';
@ -36,6 +37,10 @@ export enum EntityActionItem {
* Add a new Glossary Node as child
*/
ADD_CHILD_GLOSSARY_NODE,
/**
* Batch add an Application to a set of assets
*/
BATCH_ADD_APPLICATION,
}
const ButtonWrapper = styled.div`
@ -72,9 +77,11 @@ function EntityActions(props: Props) {
const [isBatchSetDataProductModalVisible, setIsBatchSetDataProductModalVisible] = useState(false);
const [isCreateTermModalVisible, setIsCreateTermModalVisible] = useState(false);
const [isCreateNodeModalVisible, setIsCreateNodeModalVisible] = useState(false);
const [isBatchSetApplicationModalVisible, setIsBatchSetApplicationModalVisible] = useState(false);
const [batchAddTermsMutation] = useBatchAddTermsMutation();
const [batchSetDomainMutation] = useBatchSetDomainMutation();
const [batchSetDataProductMutation] = useBatchSetDataProductMutation();
const [batchSetApplicationMutation] = useBatchSetApplicationMutation();
// eslint-disable-next-line
const batchAddGlossaryTerms = (entityUrns: Array<string>) => {
@ -186,6 +193,40 @@ function EntityActions(props: Props) {
});
};
const batchSetApplication = (entityUrns: Array<string>) => {
batchSetApplicationMutation({
variables: {
input: {
applicationUrn: urn,
resourceUrns: entityUrns,
},
},
})
.then(({ errors }) => {
if (!errors) {
setIsBatchSetApplicationModalVisible(false);
message.loading({ content: 'Updating...', duration: 3 });
setTimeout(() => {
message.success({
content: `Added assets to Application!`,
duration: 3,
});
refetchForEntity?.();
setShouldRefetchEmbeddedListSearch?.(true);
}, 3000);
}
})
.catch((e) => {
message.destroy();
message.error(
handleBatchError(entityUrns, e, {
content: `Failed to add assets to Application. An unknown error occurred.`,
duration: 3,
}),
);
});
};
const { entityData } = useEntityData();
const canCreateGlossaryEntity = !!entityData?.privileges?.canManageChildren;
@ -245,6 +286,13 @@ function EntityActions(props: Props) {
</Button>
</Tooltip>
)}
{actionItems.has(EntityActionItem.BATCH_ADD_APPLICATION) && (
<Tooltip title="Add Assets to Application" showArrow={false} placement="bottom">
<Button variant="outline" onClick={() => setIsBatchSetApplicationModalVisible(true)}>
<LinkOutlined /> Add to Assets
</Button>
</Tooltip>
)}
</ButtonWrapper>
{isBatchAddGlossaryTermModalVisible && (
<SearchSelectModal
@ -268,6 +316,17 @@ function EntityActions(props: Props) {
)}
/>
)}
{isBatchSetApplicationModalVisible && (
<SearchSelectModal
titleText="Add assets to Application"
continueText="Add"
onContinue={batchSetApplication}
onCancel={() => setIsBatchSetApplicationModalVisible(false)}
fixedEntityTypes={Array.from(
entityRegistry.getTypesWithSupportedCapabilities(EntityCapabilityType.APPLICATIONS),
)}
/>
)}
{isBatchSetDataProductModalVisible && (
<SearchSelectModal
titleText="Add assets to Data Product"

View File

@ -52,6 +52,9 @@ query appConfig {
theme {
themeId
}
application {
showSidebarSectionWhenEmpty
}
}
telemetryConfig {
enableThirdPartyLogging

View File

@ -31,4 +31,6 @@ public class VisualConfiguration {
/** Boolean flag enabled shows the full title of an entity in lineage view by default */
public boolean showFullTitleInLineage;
public ApplicationConfig application;
}

View File

@ -167,6 +167,8 @@ visualConfig:
entityProfile:
# we only support default tab for domains right now. In order to implement for other entities, update React code
domainDefaultTab: ${DOMAIN_DEFAULT_TAB:} # set to DOCUMENTATION_TAB to show documentation tab first
application:
showSidebarSectionWhenEmpty: ${APPLICATION_SHOW_SIDEBAR_SECTION_WHEN_EMPTY:false}
searchResult:
enableNameHighlight: ${SEARCH_RESULT_NAME_HIGHLIGHT_ENABLED:true} # Enables visual highlighting on search result names/descriptions.