mirror of
https://github.com/datahub-project/datahub.git
synced 2025-10-10 16:33:48 +00:00
feat(UI): AccessManagement UI to access the role metadata for a dataset (#8541)
Co-authored-by: Ramendra Srivastava <ramsrivastava@paypal.com>
This commit is contained in:
parent
68ae3bfc26
commit
75252a3d9f
@ -15,4 +15,5 @@ public class FeatureFlags {
|
||||
private boolean showBrowseV2 = false;
|
||||
private PreProcessHooks preProcessHooks;
|
||||
private boolean showAcrylInfo = false;
|
||||
private boolean showAccessManagement = false;
|
||||
}
|
||||
|
@ -171,6 +171,7 @@ public class AppConfigResolver implements DataFetcher<CompletableFuture<AppConfi
|
||||
.setReadOnlyModeEnabled(_featureFlags.isReadOnlyModeEnabled())
|
||||
.setShowBrowseV2(_featureFlags.isShowBrowseV2())
|
||||
.setShowAcrylInfo(_featureFlags.isShowAcrylInfo())
|
||||
.setShowAccessManagement(_featureFlags.isShowAccessManagement())
|
||||
.build();
|
||||
|
||||
appConfig.setFeatureFlags(featureFlagsConfig);
|
||||
|
@ -68,6 +68,7 @@ public class SearchUtils {
|
||||
EntityType.GLOSSARY_TERM,
|
||||
EntityType.GLOSSARY_NODE,
|
||||
EntityType.TAG,
|
||||
EntityType.ROLE,
|
||||
EntityType.CORP_USER,
|
||||
EntityType.CORP_GROUP,
|
||||
EntityType.CONTAINER,
|
||||
@ -94,6 +95,7 @@ public class SearchUtils {
|
||||
EntityType.TAG,
|
||||
EntityType.CORP_USER,
|
||||
EntityType.CORP_GROUP,
|
||||
EntityType.ROLE,
|
||||
EntityType.NOTEBOOK,
|
||||
EntityType.DATA_PRODUCT);
|
||||
|
||||
@ -386,4 +388,4 @@ public class SearchUtils {
|
||||
(inputTypes == null || inputTypes.isEmpty()) ? SEARCHABLE_ENTITY_TYPES : inputTypes;
|
||||
return entityTypes.stream().map(EntityTypeMapper::getName).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
@ -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!
|
||||
}
|
||||
|
||||
"""
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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());
|
||||
|
88
datahub-web-react/src/app/entity/Access/RoleEntity.tsx
Normal file
88
datahub-web-react/src/app/entity/Access/RoleEntity.tsx
Normal file
@ -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<Role> {
|
||||
type: EntityType = EntityType.Role;
|
||||
|
||||
icon = (fontSize: number, styleType: IconStyleType, color?: string) => {
|
||||
if (styleType === IconStyleType.TAB_VIEW) {
|
||||
return <TagFilled style={{ fontSize, color }} />;
|
||||
}
|
||||
|
||||
if (styleType === IconStyleType.HIGHLIGHT) {
|
||||
return <TagFilled style={{ fontSize, color: color || '#B37FEB' }} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<TagOutlined
|
||||
style={{
|
||||
fontSize,
|
||||
color: color || '#BFBFBF',
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
isSearchEnabled = () => true;
|
||||
|
||||
isBrowseEnabled = () => false;
|
||||
|
||||
isLineageEnabled = () => false;
|
||||
|
||||
getAutoCompleteFieldName = () => 'name';
|
||||
|
||||
getPathName: () => string = () => 'role';
|
||||
|
||||
getCollectionName: () => string = () => 'Roles';
|
||||
|
||||
getEntityName: () => string = () => 'Role';
|
||||
|
||||
renderProfile: (urn: string) => JSX.Element = (_) => <RoleEntityProfile />;
|
||||
|
||||
renderPreview = (_: PreviewType, data: Role) => (
|
||||
<DefaultPreviewCard
|
||||
description={data?.properties?.description || ''}
|
||||
name={this.displayName(data)}
|
||||
urn={data.urn}
|
||||
url={`/${this.getPathName()}/${urlEncodeUrn(data.urn)}`}
|
||||
logoComponent={<PreviewTagIcon />}
|
||||
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]);
|
||||
};
|
||||
}
|
@ -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<RolePageParams>();
|
||||
const urn = decodeUrn(encodedUrn);
|
||||
const { data, loading } = useGetExternalRoleQuery({ variables: { urn } });
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
{loading && <LoadingMessage type="loading" content="Loading..." />}
|
||||
<TitleLabel>Role</TitleLabel>
|
||||
<TitleText>{data?.role?.properties?.name}</TitleText>
|
||||
<Divider />
|
||||
{/* Role Description */}
|
||||
<DescriptionLabel>About</DescriptionLabel>
|
||||
<Paragraph style={{ fontSize: '12px', lineHeight: '15px', padding: '5px 0px' }}>
|
||||
{data?.role?.properties?.description}
|
||||
</Paragraph>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
@ -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<Dataset> {
|
||||
|
||||
isSearchEnabled = () => true;
|
||||
|
||||
appconfig = useAppConfig;
|
||||
|
||||
isBrowseEnabled = () => true;
|
||||
|
||||
isLineageEnabled = () => true;
|
||||
@ -176,6 +180,14 @@ export class DatasetEntity implements Entity<Dataset> {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Access Management',
|
||||
component: AccessManagement,
|
||||
display: {
|
||||
visible: (_, _1) => this.appconfig().config.featureFlags.showAccessManagement,
|
||||
enabled: (_, _2) => true,
|
||||
},
|
||||
},
|
||||
]}
|
||||
sidebarSections={[
|
||||
{
|
||||
|
@ -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<GetDatasetQuery>();
|
||||
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 <AccessManagerDescription description={roleDescription} />;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: 'Access Type',
|
||||
dataIndex: 'accessType',
|
||||
key: 'accessType',
|
||||
},
|
||||
{
|
||||
title: 'Access',
|
||||
dataIndex: 'hasAccess',
|
||||
key: 'hasAccess',
|
||||
render: (hasAccess, record) => {
|
||||
if (hasAccess) {
|
||||
return <StyledSection>Provisioned</StyledSection>;
|
||||
}
|
||||
if (record?.url) {
|
||||
return (
|
||||
<AccessButton
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
window.open(record.url);
|
||||
}}
|
||||
>
|
||||
Request
|
||||
</AccessButton>
|
||||
);
|
||||
}
|
||||
return <StyledSection />;
|
||||
},
|
||||
hidden: true,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<StyledTable dataSource={handleAccessRoles(externalRoles, loggedInUser)} columns={columns} pagination={false} />
|
||||
);
|
||||
}
|
@ -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 (
|
||||
<DescriptionContainer>
|
||||
{finalDescription}
|
||||
<Typography.Link
|
||||
onClick={() => {
|
||||
toggleExpanded();
|
||||
}}
|
||||
>
|
||||
{(shouldTruncateDescription && (expanded ? ' Read Less' : '...Read More')) || undefined}
|
||||
</Typography.Link>
|
||||
</DescriptionContainer>
|
||||
);
|
||||
}
|
@ -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/',
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
@ -0,0 +1,27 @@
|
||||
export function handleAccessRoles(externalRoles, loggedInUser) {
|
||||
const accessRoles = new Array<any>();
|
||||
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;
|
||||
}
|
@ -48,6 +48,7 @@ export const DEFAULT_APP_CONFIG = {
|
||||
showSearchFiltersV2: true,
|
||||
showBrowseV2: true,
|
||||
showAcrylInfo: false,
|
||||
showAccessManagement: false,
|
||||
},
|
||||
};
|
||||
|
||||
|
8
datahub-web-react/src/graphql/accessrole.graphql
Normal file
8
datahub-web-react/src/graphql/accessrole.graphql
Normal file
@ -0,0 +1,8 @@
|
||||
query getExternalRole($urn: String!) {
|
||||
role(urn: $urn) {
|
||||
properties {
|
||||
name
|
||||
description
|
||||
}
|
||||
}
|
||||
}
|
@ -63,6 +63,7 @@ query appConfig {
|
||||
showSearchFiltersV2
|
||||
showBrowseV2
|
||||
showAcrylInfo
|
||||
showAccessManagement
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user