From 75252a3d9f6a576904be5a0790d644b9ae2df6ac Mon Sep 17 00:00:00 2001 From: Ramendra761 <68153995+Ramendra761@users.noreply.github.com> Date: Fri, 8 Sep 2023 23:10:49 +0530 Subject: [PATCH] feat(UI): AccessManagement UI to access the role metadata for a dataset (#8541) Co-authored-by: Ramendra Srivastava --- .../graphql/featureflags/FeatureFlags.java | 1 + .../resolvers/config/AppConfigResolver.java | 1 + .../graphql/resolvers/search/SearchUtils.java | 4 +- .../src/main/resources/app.graphql | 4 + .../src/main/resources/entity.graphql | 10 +- datahub-web-react/src/App.tsx | 2 + .../src/app/entity/Access/RoleEntity.tsx | 88 ++++++ .../app/entity/Access/RoleEntityProfile.tsx | 75 +++++ .../src/app/entity/dataset/DatasetEntity.tsx | 12 + .../AccessManagement/AccessManagement.tsx | 115 ++++++++ .../AccessManagerDescription.tsx | 38 +++ .../__tests__/AccessManagement.test.ts | 267 ++++++++++++++++++ .../tabs/Dataset/AccessManagement/utils.tsx | 27 ++ datahub-web-react/src/appConfigContext.tsx | 1 + .../src/graphql/accessrole.graphql | 8 + datahub-web-react/src/graphql/app.graphql | 1 + datahub-web-react/src/graphql/dataset.graphql | 31 ++ datahub-web-react/src/graphql/search.graphql | 39 +++ .../src/main/resources/application.yml | 1 + 19 files changed, 721 insertions(+), 4 deletions(-) create mode 100644 datahub-web-react/src/app/entity/Access/RoleEntity.tsx create mode 100644 datahub-web-react/src/app/entity/Access/RoleEntityProfile.tsx create mode 100644 datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagement.tsx create mode 100644 datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/AccessManagerDescription.tsx create mode 100644 datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/__tests__/AccessManagement.test.ts create mode 100644 datahub-web-react/src/app/entity/shared/tabs/Dataset/AccessManagement/utils.tsx create mode 100644 datahub-web-react/src/graphql/accessrole.graphql 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 f813562945378..de3c217db01ec 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 90017f7b87997..09df985b19cf5 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 CompletableFuture get(final DataFetchingEnvironment environmen .setReadOnlyModeEnabled(_featureFlags.isReadOnlyModeEnabled()) .setShowBrowseV2(_featureFlags.isShowBrowseV2()) .setShowAcrylInfo(_featureFlags.isShowAcrylInfo()) + .setShowAccessManagement(_featureFlags.isShowAccessManagement()) .build(); appConfig.setFeatureFlags(featureFlagsConfig); diff --git a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java index fe5b79ba2ea3d..fb146ef72877d 100644 --- a/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java +++ b/datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/search/SearchUtils.java @@ -68,6 +68,7 @@ private SearchUtils() { EntityType.GLOSSARY_TERM, EntityType.GLOSSARY_NODE, EntityType.TAG, + EntityType.ROLE, EntityType.CORP_USER, EntityType.CORP_GROUP, EntityType.CONTAINER, @@ -94,6 +95,7 @@ private SearchUtils() { EntityType.TAG, EntityType.CORP_USER, EntityType.CORP_GROUP, + EntityType.ROLE, EntityType.NOTEBOOK, EntityType.DATA_PRODUCT); @@ -386,4 +388,4 @@ public static List getEntityNames(List inputTypes) { (inputTypes == null || inputTypes.isEmpty()) ? SEARCHABLE_ENTITY_TYPES : inputTypes; return entityTypes.stream().map(EntityTypeMapper::getName).collect(Collectors.toList()); } -} +} \ No newline at end of file diff --git a/datahub-graphql-core/src/main/resources/app.graphql b/datahub-graphql-core/src/main/resources/app.graphql index dbee24b4bf6f7..a5057bcf644da 100644 --- a/datahub-graphql-core/src/main/resources/app.graphql +++ b/datahub-graphql-core/src/main/resources/app.graphql @@ -441,6 +441,10 @@ type FeatureFlagsConfig { Whether we should show CTAs in the UI related to moving to Managed DataHub by Acryl. """ showAcrylInfo: Boolean! + """ + Whether we should show AccessManagement tab in the datahub UI. + """ + showAccessManagement: Boolean! } """ diff --git a/datahub-graphql-core/src/main/resources/entity.graphql b/datahub-graphql-core/src/main/resources/entity.graphql index b1f9d57300177..044c405942a3c 100644 --- a/datahub-graphql-core/src/main/resources/entity.graphql +++ b/datahub-graphql-core/src/main/resources/entity.graphql @@ -68,6 +68,10 @@ type Query { Fetch a Tag by primary key (urn) """ tag(urn: String!): Tag + """ + Fetch a Role by primary key (urn) + """ + role(urn: String!): Role """ Fetch a Glossary Term by primary key (urn) @@ -1451,12 +1455,12 @@ type Role implements Entity { """ Role properties to include Request Access Url """ - properties: RoleProperties! + properties: RoleProperties """ A standard Entity Type """ - actors: Actor! + actors: Actor } @@ -11164,4 +11168,4 @@ input UpdateOwnershipTypeInput { The description of the Custom Ownership Type """ description: String -} +} \ No newline at end of file diff --git a/datahub-web-react/src/App.tsx b/datahub-web-react/src/App.tsx index 68a4b93d71481..b6bc608dccbbb 100644 --- a/datahub-web-react/src/App.tsx +++ b/datahub-web-react/src/App.tsx @@ -35,6 +35,7 @@ import GlossaryNodeEntity from './app/entity/glossaryNode/GlossaryNodeEntity'; import { DataPlatformEntity } from './app/entity/dataPlatform/DataPlatformEntity'; import { DataProductEntity } from './app/entity/dataProduct/DataProductEntity'; import { DataPlatformInstanceEntity } from './app/entity/dataPlatformInstance/DataPlatformInstanceEntity'; +import { RoleEntity } from './app/entity/Access/RoleEntity'; /* Construct Apollo Client @@ -116,6 +117,7 @@ const App: React.VFC = () => { 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 0000000000000..e63db9d0bbb2a --- /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 0000000000000..d8a31700fb918 --- /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 535a3f569964c..7d40b97a66b3b 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 0000000000000..c812569367419 --- /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 0000000000000..c87a499e34ac0 --- /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 0000000000000..53c7b483d9428 --- /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 0000000000000..71e81e8d7de93 --- /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 807a17c4fd6a4..096c2fd6ef0e5 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 0000000000000..ccc7d3496ad6d --- /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 bf15e5f757f8f..228fa1c9430d0 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 c79c1a4d9d551..658ce2b47c567 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 7cd868d7cd2b2..94ff263c02039 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 f49498bfa2325..6fd7b9e6a295c 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: