diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/featureflags/FeatureFlags.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/featureflags/FeatureFlags.java index f813562945..de3c217db0 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/featureflags/FeatureFlags.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/featureflags/FeatureFlags.java @@ -15,4 +15,5 @@ public class FeatureFlags { private boolean showBrowseV2 = false; private PreProcessHooks preProcessHooks; private boolean showAcrylInfo = false; + private boolean showAccessManagement = false; } diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java index 90017f7b87..09df985b19 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/config/AppConfigResolver.java @@ -171,6 +171,7 @@ public class AppConfigResolver implements DataFetcher { register.register(new DomainEntity()); register.register(new ContainerEntity()); register.register(new GlossaryNodeEntity()); + register.register(new RoleEntity()); register.register(new DataPlatformEntity()); register.register(new DataProductEntity()); register.register(new DataPlatformInstanceEntity()); diff --git a/datahub-web-react/src/app/entity/Access/RoleEntity.tsx b/datahub-web-react/src/app/entity/Access/RoleEntity.tsx new file mode 100644 index 0000000000..e63db9d0bb --- /dev/null +++ b/datahub-web-react/src/app/entity/Access/RoleEntity.tsx @@ -0,0 +1,88 @@ +import { TagOutlined, TagFilled } from '@ant-design/icons'; +import * as React from 'react'; +import styled from 'styled-components'; +import { Role, EntityType, SearchResult } from '../../../types.generated'; +import DefaultPreviewCard from '../../preview/DefaultPreviewCard'; +import { Entity, EntityCapabilityType, IconStyleType, PreviewType } from '../Entity'; +import { getDataForEntityType } from '../shared/containers/profile/utils'; +import { urlEncodeUrn } from '../shared/utils'; +import RoleEntityProfile from './RoleEntityProfile'; + +const PreviewTagIcon = styled(TagOutlined)` + font-size: 20px; +`; +// /** +// * Definition of the DataHub Access Role entity. +// */ +export class RoleEntity implements Entity { + type: EntityType = EntityType.Role; + + icon = (fontSize: number, styleType: IconStyleType, color?: string) => { + if (styleType === IconStyleType.TAB_VIEW) { + return ; + } + + if (styleType === IconStyleType.HIGHLIGHT) { + return ; + } + + return ( + + ); + }; + + isSearchEnabled = () => true; + + isBrowseEnabled = () => false; + + isLineageEnabled = () => false; + + getAutoCompleteFieldName = () => 'name'; + + getPathName: () => string = () => 'role'; + + getCollectionName: () => string = () => 'Roles'; + + getEntityName: () => string = () => 'Role'; + + renderProfile: (urn: string) => JSX.Element = (_) => ; + + renderPreview = (_: PreviewType, data: Role) => ( + } + type="Role" + typeIcon={this.icon(14, IconStyleType.ACCENT)} + /> + ); + + renderSearch = (result: SearchResult) => { + return this.renderPreview(PreviewType.SEARCH, result.entity as Role); + }; + + displayName = (data: Role) => { + return data.properties?.name || data.urn; + }; + + getOverridePropertiesFromEntity = (data: Role) => { + return { + name: data.properties?.name, + }; + }; + + getGenericEntityProperties = (role: Role) => { + return getDataForEntityType({ data: role, entityType: this.type, getOverrideProperties: (data) => data }); + }; + + supportedCapabilities = () => { + return new Set([EntityCapabilityType.OWNERS]); + }; +} diff --git a/datahub-web-react/src/app/entity/Access/RoleEntityProfile.tsx b/datahub-web-react/src/app/entity/Access/RoleEntityProfile.tsx new file mode 100644 index 0000000000..d8a31700fb --- /dev/null +++ b/datahub-web-react/src/app/entity/Access/RoleEntityProfile.tsx @@ -0,0 +1,75 @@ +import React from 'react'; + +import { useParams } from 'react-router'; +import { Divider, Typography } from 'antd'; +import { grey } from '@ant-design/colors'; +import styled from 'styled-components'; + +import { Message } from '../../shared/Message'; +import { decodeUrn } from '../shared/utils'; +import { useGetExternalRoleQuery } from '../../../graphql/accessrole.generated'; + +const PageContainer = styled.div` + padding: 32px 100px; +`; + +const LoadingMessage = styled(Message)` + margin-top: 10%; +`; + +type RolePageParams = { + urn: string; +}; + +const TitleLabel = styled(Typography.Text)` + &&& { + color: ${grey[2]}; + font-size: 12px; + display: block; + line-height: 20px; + font-weight: 700; + } +`; + +const DescriptionLabel = styled(Typography.Text)` + &&& { + text-align: left; + font-weight: bold; + font-size: 14px; + line-height: 28px; + color: rgb(38, 38, 38); + } +`; + +const TitleText = styled(Typography.Text)` + &&& { + color: ${grey[10]}; + font-weight: 700; + font-size: 20px; + line-height: 28px; + display: inline-block; + margin: 0px 7px; + } +`; + +const { Paragraph } = Typography; + +export default function RoleEntityProfile() { + const { urn: encodedUrn } = useParams(); + const urn = decodeUrn(encodedUrn); + const { data, loading } = useGetExternalRoleQuery({ variables: { urn } }); + + return ( + + {loading && } + Role + {data?.role?.properties?.name} + + {/* Role Description */} + About + + {data?.role?.properties?.description} + + + ); +} diff --git a/datahub-web-react/src/app/entity/dataset/DatasetEntity.tsx b/datahub-web-react/src/app/entity/dataset/DatasetEntity.tsx index 535a3f5699..7d40b97a66 100644 --- a/datahub-web-react/src/app/entity/dataset/DatasetEntity.tsx +++ b/datahub-web-react/src/app/entity/dataset/DatasetEntity.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { DatabaseFilled, DatabaseOutlined } from '@ant-design/icons'; import { Dataset, DatasetProperties, EntityType, OwnershipType, SearchResult } from '../../../types.generated'; import { Entity, EntityCapabilityType, IconStyleType, PreviewType } from '../Entity'; +import { useAppConfig } from '../../useAppConfig'; import { Preview } from './preview/Preview'; import { EntityProfile } from '../shared/containers/profile/EntityProfile'; import { GetDatasetQuery, useGetDatasetQuery, useUpdateDatasetMutation } from '../../../graphql/dataset.generated'; @@ -30,6 +31,7 @@ import { EmbedTab } from '../shared/tabs/Embed/EmbedTab'; import EmbeddedProfile from '../shared/embed/EmbeddedProfile'; import DataProductSection from '../shared/containers/profile/sidebar/DataProduct/DataProductSection'; import { getDataProduct } from '../shared/utils'; +import AccessManagement from '../shared/tabs/Dataset/AccessManagement/AccessManagement'; import { matchedFieldPathsRenderer } from '../../search/matches/matchedFieldPathsRenderer'; const SUBTYPES = { @@ -69,6 +71,8 @@ export class DatasetEntity implements Entity { isSearchEnabled = () => true; + appconfig = useAppConfig; + isBrowseEnabled = () => true; isLineageEnabled = () => true; @@ -176,6 +180,14 @@ export class DatasetEntity implements Entity { }, }, }, + { + name: 'Access Management', + component: AccessManagement, + display: { + visible: (_, _1) => this.appconfig().config.featureFlags.showAccessManagement, + enabled: (_, _2) => true, + }, + }, ]} sidebarSections={[ { diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagement.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagement.tsx new file mode 100644 index 0000000000..c812569367 --- /dev/null +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagement.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import styled from 'styled-components'; +import { Button, Table } from 'antd'; +import { useBaseEntity } from '../../../EntityContext'; +import { GetDatasetQuery, useGetExternalRolesQuery } from '../../../../../../graphql/dataset.generated'; +import { useGetMeQuery } from '../../../../../../graphql/me.generated'; +import { handleAccessRoles } from './utils'; +import AccessManagerDescription from './AccessManagerDescription'; + +const StyledTable = styled(Table)` + overflow: inherit; + height: inherit; + + &&& .ant-table-cell { + background-color: #fff; + } + &&& .ant-table-thead .ant-table-cell { + font-weight: 600; + font-size: 12px; + color: '#898989'; + } + && + .ant-table-thead + > tr + > th:not(:last-child):not(.ant-table-selection-column):not(.ant-table-row-expand-icon-cell):not([colspan])::before { + border: 1px solid #f0f0f0; + } +` as typeof Table; + +const StyledSection = styled.section` + background-color: #fff; + color: black; + width: 83px; + text-align: center; + border-radius: 3px; + border: none; + font-weight: bold; +`; + +const AccessButton = styled(Button)` + background-color: #1890ff; + color: white; + width: 80px; + height: 30px; + border-radius: 3.5px; + border: none; + font-weight: bold; + &:hover { + background-color: #18baff; + color: white; + width: 80px; + height: 30px; + border-radius: 3.5px; + border: none; + font-weight: bold; + } +`; + +export default function AccessManagement() { + const { data: loggedInUser } = useGetMeQuery({ fetchPolicy: 'cache-first' }); + const baseEntity = useBaseEntity(); + const { data: externalRoles } = useGetExternalRolesQuery({ + variables: { urn: baseEntity?.dataset?.urn as string }, + skip: !baseEntity?.dataset?.urn, + }); + + const columns = [ + { + title: 'Role Name', + dataIndex: 'name', + key: 'name', + }, + { + title: 'Description', + dataIndex: 'description', + key: 'description', + render: (roleDescription) => { + return ; + }, + }, + { + title: 'Access Type', + dataIndex: 'accessType', + key: 'accessType', + }, + { + title: 'Access', + dataIndex: 'hasAccess', + key: 'hasAccess', + render: (hasAccess, record) => { + if (hasAccess) { + return Provisioned; + } + if (record?.url) { + return ( + { + e.preventDefault(); + window.open(record.url); + }} + > + Request + + ); + } + return ; + }, + hidden: true, + }, + ]; + + return ( + + ); +} diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagerDescription.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagerDescription.tsx new file mode 100644 index 0000000000..c87a499e34 --- /dev/null +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagerDescription.tsx @@ -0,0 +1,38 @@ +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { Typography } from 'antd'; + +export type Props = { + description: any; +}; + +const DescriptionContainer = styled.div` + position: relative; + display: flex; + flex-direction: column; + width: 500px; + height: 100%; + min-height: 22px; +`; + +export default function AccessManagerDescription({ description }: Props) { + const shouldTruncateDescription = description.length > 150; + const [expanded, setIsExpanded] = useState(!shouldTruncateDescription); + const finalDescription = expanded ? description : description.slice(0, 150); + const toggleExpanded = () => { + setIsExpanded(!expanded); + }; + + return ( + + {finalDescription} + { + toggleExpanded(); + }} + > + {(shouldTruncateDescription && (expanded ? ' Read Less' : '...Read More')) || undefined} + + + ); +} diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/__tests__/AccessManagement.test.ts b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/__tests__/AccessManagement.test.ts new file mode 100644 index 0000000000..53c7b483d9 --- /dev/null +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/__tests__/AccessManagement.test.ts @@ -0,0 +1,267 @@ +import { handleAccessRoles } from '../utils'; +import { GetExternalRolesQuery } from '../../../../../../../graphql/dataset.generated'; +import { GetMeQuery } from '../../../../../../../graphql/me.generated'; + +describe('handleAccessRoles', () => { + it('should properly map the externalroles and loggedin user', () => { + const externalRolesQuery: GetExternalRolesQuery = { + dataset: { + access: { + roles: [ + { + role: { + id: 'accessRole', + properties: { + name: 'accessRole', + description: + 'This role access is required by the developers to test and deploy the code also adding few more details to check the description length for the given data and hence check the condition of read more and read less ', + type: 'READ', + requestUrl: 'https://www.google.com/', + }, + urn: 'urn:li:role:accessRole', + actors: { + users: null, + }, + }, + }, + ], + }, + __typename: 'Dataset', + }, + }; + + const GetMeQueryUser: GetMeQuery = { + me: { + corpUser: { + urn: 'urn:li:corpuser:datahub', + username: 'datahub', + info: { + active: true, + displayName: 'DataHub', + title: 'DataHub Root User', + firstName: null, + lastName: null, + fullName: null, + email: null, + __typename: 'CorpUserInfo', + }, + editableProperties: { + displayName: null, + title: null, + pictureLink: + 'https://raw.githubusercontent.com/datahub-project/datahub/master/datahub-web-react/src/images/default_avatar.png', + teams: [], + skills: [], + __typename: 'CorpUserEditableProperties', + }, + settings: { + appearance: { + showSimplifiedHomepage: false, + __typename: 'CorpUserAppearanceSettings', + }, + views: null, + __typename: 'CorpUserSettings', + }, + __typename: 'CorpUser', + }, + platformPrivileges: { + viewAnalytics: true, + managePolicies: true, + manageIdentities: true, + generatePersonalAccessTokens: true, + manageIngestion: true, + manageSecrets: true, + manageDomains: true, + manageTests: true, + manageGlossaries: true, + manageUserCredentials: true, + manageTags: true, + createDomains: true, + createTags: true, + manageGlobalViews: true, + manageOwnershipTypes: true, + __typename: 'PlatformPrivileges', + }, + __typename: 'AuthenticatedUser', + }, + }; + const externalRole = handleAccessRoles(externalRolesQuery, GetMeQueryUser); + expect(externalRole).toMatchObject([ + { + name: 'accessRole', + description: + 'This role access is required by the developers to test and deploy the code also adding few more details to check the description length for the given data and hence check the condition of read more and read less ', + accessType: 'READ', + hasAccess: false, + url: 'https://www.google.com/', + }, + ]); + }); + it('should return empty array', () => { + const externalRolesQuery: GetExternalRolesQuery = { + dataset: { + access: null, + __typename: 'Dataset', + }, + }; + + const GetMeQueryUser: GetMeQuery = { + me: { + corpUser: { + urn: 'urn:li:corpuser:datahub', + username: 'datahub', + info: { + active: true, + displayName: 'DataHub', + title: 'DataHub Root User', + firstName: null, + lastName: null, + fullName: null, + email: null, + __typename: 'CorpUserInfo', + }, + editableProperties: { + displayName: null, + title: null, + pictureLink: + 'https://raw.githubusercontent.com/datahub-project/datahub/master/datahub-web-react/src/images/default_avatar.png', + teams: [], + skills: [], + __typename: 'CorpUserEditableProperties', + }, + settings: { + appearance: { + showSimplifiedHomepage: false, + __typename: 'CorpUserAppearanceSettings', + }, + views: null, + __typename: 'CorpUserSettings', + }, + __typename: 'CorpUser', + }, + platformPrivileges: { + viewAnalytics: true, + managePolicies: true, + manageIdentities: true, + generatePersonalAccessTokens: true, + manageIngestion: true, + manageSecrets: true, + manageDomains: true, + manageTests: true, + manageGlossaries: true, + manageUserCredentials: true, + manageTags: true, + createDomains: true, + createTags: true, + manageGlobalViews: true, + manageOwnershipTypes: true, + __typename: 'PlatformPrivileges', + }, + __typename: 'AuthenticatedUser', + }, + }; + const externalRole = handleAccessRoles(externalRolesQuery, GetMeQueryUser); + expect(externalRole).toMatchObject([]); + }); + it('should properly map the externalroles and loggedin user and access true', () => { + const externalRolesQuery: GetExternalRolesQuery = { + dataset: { + access: { + roles: [ + { + role: { + id: 'accessRole', + properties: { + name: 'accessRole', + description: + 'This role access is required by the developers to test and deploy the code also adding few more details to check the description length for the given data and hence check the condition of read more and read less ', + type: 'READ', + requestUrl: 'https://www.google.com/', + }, + urn: 'urn:li:role:accessRole', + actors: { + users: [ + { + user: { + urn: 'urn:li:corpuser:datahub', + }, + }, + ], + }, + }, + }, + ], + }, + __typename: 'Dataset', + }, + }; + + const GetMeQueryUser: GetMeQuery = { + me: { + corpUser: { + urn: 'urn:li:corpuser:datahub', + username: 'datahub', + info: { + active: true, + displayName: 'DataHub', + title: 'DataHub Root User', + firstName: null, + lastName: null, + fullName: null, + email: null, + __typename: 'CorpUserInfo', + }, + editableProperties: { + displayName: null, + title: null, + pictureLink: + 'https://raw.githubusercontent.com/datahub-project/datahub/master/datahub-web-react/src/images/default_avatar.png', + teams: [], + skills: [], + __typename: 'CorpUserEditableProperties', + }, + settings: { + appearance: { + showSimplifiedHomepage: false, + __typename: 'CorpUserAppearanceSettings', + }, + views: null, + __typename: 'CorpUserSettings', + }, + __typename: 'CorpUser', + }, + platformPrivileges: { + viewAnalytics: true, + managePolicies: true, + manageIdentities: true, + generatePersonalAccessTokens: true, + manageIngestion: true, + manageSecrets: true, + manageDomains: true, + manageTests: true, + manageGlossaries: true, + manageUserCredentials: true, + manageTags: true, + createDomains: true, + createTags: true, + manageGlobalViews: true, + manageOwnershipTypes: true, + __typename: 'PlatformPrivileges', + }, + __typename: 'AuthenticatedUser', + }, + }; + const externalRole = handleAccessRoles(externalRolesQuery, GetMeQueryUser); + + expect(externalRole).toMatchObject([ + { + name: 'accessRole', + description: + 'This role access is required by the developers to test and deploy the code also adding few more details to check the description length for the given data and hence check the condition of read more and read less ', + accessType: 'READ', + hasAccess: true, + url: 'https://www.google.com/', + }, + ]); + }); +}); diff --git a/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/utils.tsx b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/utils.tsx new file mode 100644 index 0000000000..71e81e8d7d --- /dev/null +++ b/datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/utils.tsx @@ -0,0 +1,27 @@ +export function handleAccessRoles(externalRoles, loggedInUser) { + const accessRoles = new Array(); + if ( + externalRoles?.dataset?.access && + externalRoles?.dataset?.access.roles && + externalRoles?.dataset?.access.roles.length > 0 + ) { + externalRoles?.dataset?.access?.roles?.forEach((userRoles) => { + const role = { + name: userRoles?.role?.properties?.name || ' ', + description: userRoles?.role?.properties?.description || ' ', + accessType: userRoles?.role?.properties?.type || ' ', + hasAccess: + (userRoles?.role?.actors?.users && + userRoles?.role?.actors?.users.length > 0 && + userRoles?.role?.actors?.users?.some( + (user) => user.user.urn === loggedInUser?.me?.corpUser.urn, + )) || + false, + url: userRoles?.role?.properties?.requestUrl || undefined, + }; + accessRoles.push(role); + }); + } + + return accessRoles; +} diff --git a/datahub-web-react/src/appConfigContext.tsx b/datahub-web-react/src/appConfigContext.tsx index 807a17c4fd..096c2fd6ef 100644 --- a/datahub-web-react/src/appConfigContext.tsx +++ b/datahub-web-react/src/appConfigContext.tsx @@ -48,6 +48,7 @@ export const DEFAULT_APP_CONFIG = { showSearchFiltersV2: true, showBrowseV2: true, showAcrylInfo: false, + showAccessManagement: false, }, }; diff --git a/datahub-web-react/src/graphql/accessrole.graphql b/datahub-web-react/src/graphql/accessrole.graphql new file mode 100644 index 0000000000..ccc7d3496a --- /dev/null +++ b/datahub-web-react/src/graphql/accessrole.graphql @@ -0,0 +1,8 @@ +query getExternalRole($urn: String!) { + role(urn: $urn) { + properties { + name + description + } + } +} \ No newline at end of file diff --git a/datahub-web-react/src/graphql/app.graphql b/datahub-web-react/src/graphql/app.graphql index bf15e5f757..228fa1c943 100644 --- a/datahub-web-react/src/graphql/app.graphql +++ b/datahub-web-react/src/graphql/app.graphql @@ -63,6 +63,7 @@ query appConfig { showSearchFiltersV2 showBrowseV2 showAcrylInfo + showAccessManagement } } } diff --git a/datahub-web-react/src/graphql/dataset.graphql b/datahub-web-react/src/graphql/dataset.graphql index c79c1a4d9d..658ce2b47c 100644 --- a/datahub-web-react/src/graphql/dataset.graphql +++ b/datahub-web-react/src/graphql/dataset.graphql @@ -311,3 +311,34 @@ query getDatasetSchema($urn: String!) { } } } + +query getExternalRoles($urn: String!) { + dataset(urn: $urn) { + access { + ...getRoles + } + __typename + } +} + +fragment getRoles on Access { + roles { + role { + id + properties { + name + description + type + requestUrl + } + urn + actors { + users { + user { + urn + } + } + } + } + } +} diff --git a/datahub-web-react/src/graphql/search.graphql b/datahub-web-react/src/graphql/search.graphql index 7cd868d7cd..94ff263c02 100644 --- a/datahub-web-react/src/graphql/search.graphql +++ b/datahub-web-react/src/graphql/search.graphql @@ -44,6 +44,16 @@ fragment autoCompleteFields on Entity { } } ...datasetStatsFields + access { + ...getAccess + } + } + ... on Role { + id + properties { + name + description + } } ... on CorpUser { username @@ -242,6 +252,25 @@ query getAutoCompleteMultipleResults($input: AutoCompleteMultipleInput!) { } } +fragment getAccess on Access { + roles { + role { + ...getRolesName + } + } +} + +fragment getRolesName on Role { + urn + type + id + properties { + name + description + type + } +} + fragment datasetStatsFields on Dataset { lastProfile: datasetProfiles(limit: 1) { rowCount @@ -288,6 +317,9 @@ fragment nonSiblingsDatasetSearchFields on Dataset { editableProperties { description } + access { + ...getAccess + } platformNativeType properties { name @@ -346,6 +378,13 @@ fragment searchResultFields on Entity { } } } + ... on Role { + id + properties { + name + description + } + } ... on CorpUser { username properties { diff --git a/metadata-service/configuration/src/main/resources/application.yml b/metadata-service/configuration/src/main/resources/application.yml index f49498bfa2..6fd7b9e6a2 100644 --- a/metadata-service/configuration/src/main/resources/application.yml +++ b/metadata-service/configuration/src/main/resources/application.yml @@ -294,6 +294,7 @@ featureFlags: alwaysEmitChangeLog: ${ALWAYS_EMIT_CHANGE_LOG:false} # Enables always emitting a MCL even when no changes are detected. Used for Time Based Lineage when no changes occur. searchServiceDiffModeEnabled: ${SEARCH_SERVICE_DIFF_MODE_ENABLED:true} # Enables diff mode for search document writes, reduces amount of writes to ElasticSearch documents for no-ops readOnlyModeEnabled: ${READ_ONLY_MODE_ENABLED:false} # Enables read only mode for an instance. Right now this only affects ability to edit user profile image URL but can be extended + showAccessManagement: ${SHOW_ACCESS_MANAGEMENT:false} #Whether we should show AccessManagement tab in the datahub UI. showSearchFiltersV2: ${SHOW_SEARCH_FILTERS_V2:true} # Enables showing the search filters V2 experience. showBrowseV2: ${SHOW_BROWSE_V2:true} # Enables showing the browse v2 sidebar experience. preProcessHooks: