Skip to content

Commit

Permalink
Visibility for shared tables and folder consumption (#289)
Browse files Browse the repository at this point in the history
### Feature or Bugfix
- Feature

### Detail
- Added "shared" db name and access point names in the UI
- Added "shared" db name and access point names as part of the
shareObject graphQL schema
The result is that users can view relevant information to consume data
that has been shared with them directly from the shares view. They can
copy to their clipboards a command to list the objects inside a shared
folders using the AWS CLI or they can copy an SQL statement to use in
Athena and query shared tables.


![image](https://user-images.githubusercontent.com/71252798/216063148-dabb0834-a1bd-45e6-8446-916540efc871.png)


### Relates
- #283 

By submitting this pull request, I confirm that my contribution is made
under the terms of the Apache 2.0 license.
  • Loading branch information
dlpzx committed Feb 3, 2023
1 parent 87ba593 commit 315dfcf
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 8 deletions.
19 changes: 19 additions & 0 deletions backend/dataall/api/Objects/ShareObject/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


from .... import db
from .... import utils
from ....api.constants import *
from ....api.context import Context
from ....aws.handlers.service_handlers import Worker
Expand Down Expand Up @@ -256,6 +257,8 @@ def resolve_dataset(context: Context, source: models.ShareObject, **kwargs):
'datasetName': ds.name if ds else 'NotFound',
'SamlAdminGroupName': ds.SamlAdminGroupName if ds else 'NotFound',
'environmentName': env.label if env else 'NotFound',
'AwsAccountId': env.AwsAccountId if env else 'NotFound',
'region': env.region if env else 'NotFound',
'exists': True if ds else False,
}

Expand Down Expand Up @@ -294,6 +297,22 @@ def resolve_group(context: Context, source: models.ShareObject, **kwargs):
return source.groupUri


def resolve_consumption_data(context: Context, source: models.ShareObject, **kwargs):
if not source:
return None
with context.engine.scoped_session() as session:
ds: models.Dataset = db.api.Dataset.get_dataset_by_uri(session, source.datasetUri)
if ds:
S3AccessPointName = utils.slugify(
source.datasetUri + '-' + source.principalId,
max_length=50, lowercase=True, regex_pattern='[^a-zA-Z0-9-]', separator='-'
)
return {
's3AccessPointName': S3AccessPointName,
'sharedGlueDatabase': (ds.GlueDatabaseName + '_shared_' + source.shareUri)[:254] if ds else 'Not created',
}


def resolve_share_object_statistics(context: Context, source: models.ShareObject, **kwargs):
if not source:
return None
Expand Down
12 changes: 12 additions & 0 deletions backend/dataall/api/Objects/ShareObject/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,20 @@
gql.Field(name='datasetName', type=gql.String),
gql.Field(name='SamlAdminGroupName', type=gql.String),
gql.Field(name='environmentName', type=gql.String),
gql.Field(name='AwsAccountId', type=gql.String),
gql.Field(name='region', type=gql.String),
gql.Field(name='exists', type=gql.Boolean),
],
)

ConsumptionData = gql.ObjectType(
name='ConsumptionData',
fields=[
gql.Field(name='s3AccessPointName', type=gql.String),
gql.Field(name='sharedGlueDatabase', type=gql.String),
],
)

ShareObject = gql.ObjectType(
name='ShareObject',
fields=[
Expand All @@ -105,6 +116,7 @@
gql.Field(name='updated', type=gql.String),
gql.Field(name='datasetUri', type=gql.String),
gql.Field(name='dataset', type=DatasetLink, resolver=resolve_dataset),
gql.Field(name='consumptionData', type=gql.Ref('ConsumptionData'), resolver=resolve_consumption_data),
gql.Field(name='existingSharedItems', type=gql.Boolean, resolver=resolve_existing_shared_items),
gql.Field(
name='statistics',
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/api/ShareObject/getShareObject.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ const getShareObject = ({ shareUri, filter }) => ({
owner
status
userRoleForShareObject
consumptionData {
s3AccessPointName
sharedGlueDatabase
}
principal {
principalId
principalType
Expand Down Expand Up @@ -46,6 +50,8 @@ const getShareObject = ({ shareUri, filter }) => ({
datasetName
SamlAdminGroupName
environmentName
AwsAccountId
region
exists
}
}
Expand Down
92 changes: 84 additions & 8 deletions frontend/src/views/Shares/ShareView.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,15 @@ import CircularProgress from '@mui/material/CircularProgress';
import {
BlockOutlined,
CheckCircleOutlined,
CopyAllOutlined,
DeleteOutlined,
RemoveCircleOutlineOutlined,
LockRounded,
RefreshRounded
} from '@mui/icons-material';
import { LoadingButton } from '@mui/lab';
import { CopyToClipboard } from 'react-copy-to-clipboard/lib/Component';
import { useTheme } from '@mui/styles';
import * as PropTypes from 'prop-types';
import { useSnackbar } from 'notistack';
import { useNavigate } from 'react-router';
Expand Down Expand Up @@ -383,6 +386,7 @@ const ShareView = () => {
const dispatch = useDispatch();
const params = useParams();
const client = useClient();
const theme = useTheme();
const [loading, setLoading] = useState(true);
const [loadingShareItems, setLoadingShareItems] = useState(false);
const [isAddItemModalOpen, setIsAddItemModalOpen] = useState(false);
Expand All @@ -391,6 +395,21 @@ const ShareView = () => {
const handleAddItemModalClose = () => {setIsAddItemModalOpen(false);};
const handleRevokeItemModalOpen = () => {setIsRevokeItemsModalOpen(true);};
const handleRevokeItemModalClose = () => {setIsRevokeItemsModalOpen(false);};
const handlePageChange = async (event, value) => {
if (value <= sharedItems.pages && value !== sharedItems.page) {
await setFilter({ ...filter, isShared: true, page: value });
}
};
const copyNotification = () => {
enqueueSnackbar('Copied to clipboard', {
anchorOrigin: {
horizontal: 'right',
vertical: 'top'
},
variant: 'success'
});
};

const fetchItem = useCallback(async () => {
setLoading(true);
const response = await client.query(
Expand All @@ -403,7 +422,6 @@ const ShareView = () => {
}
setLoading(false);
}, [client, dispatch, params.uri]);

const fetchShareItems = useCallback(
async (isAddingItem = false) => {
setLoadingShareItems(true);
Expand All @@ -428,13 +446,7 @@ const ShareView = () => {
},
[client, dispatch, filter, fetchItem, params.uri]
);

const handlePageChange = async (event, value) => {
if (value <= sharedItems.pages && value !== sharedItems.page) {
await setFilter({ ...filter, isShared: true, page: value });
}
};


useEffect(() => {
if (client) {
fetchItem().catch((e) => dispatch({ type: SET_ERROR, error: e.message }));
Expand Down Expand Up @@ -674,6 +686,70 @@ const ShareView = () => {
</Card>
</Grid>
</Grid>
<Box sx={{ mb: 3 }}>
<Card {...share}>
<Box>
<CardHeader title="Data Consumption details" />
<Divider />
</Box>
<CardContent>
<Box>
<Box>
<Typography display="inline" color="textSecondary" variant="subtitle2">
S3 Access Point name (Folder sharing):
</Typography>
<Typography display="inline" color="textPrimary" variant="subtitle2">
{` ${share.consumptionData.s3AccessPointName || '-'}`}
</Typography>
<Typography color="textPrimary" variant="subtitle2">
<CopyToClipboard
onCopy={() => copyNotification()}
text={`aws s3 ls arn:aws:s3:${share.dataset.region}:${share.dataset.AwsAccountId}:accesspoint/${share.consumptionData.s3AccessPointName}/SHARED_FOLDER/`}
>
<IconButton>
<CopyAllOutlined
sx={{
color:
theme.palette.mode === 'dark'
? theme.palette.primary.contrastText
: theme.palette.primary.main
}}
/>
</IconButton>
</CopyToClipboard>
{`aws s3 ls arn:aws:s3:${share.dataset.region}:${share.dataset.AwsAccountId}:accesspoint/${share.consumptionData.s3AccessPointName}/SHARED_FOLDER/`}
</Typography>
</Box>
<Box sx={{ mt: 3 }}>
<Typography display="inline" color="textSecondary" variant="subtitle2">
Glue database name (Table sharing):
</Typography>
<Typography display="inline" color="textPrimary" variant="subtitle2">
{` ${share.consumptionData.sharedGlueDatabase || '-'}`}
</Typography>
<Typography color="textPrimary" variant="subtitle2">
<CopyToClipboard
onCopy={() => copyNotification()}
text={`SELECT * FROM ${share.consumptionData.sharedGlueDatabase}.TABLENAME`}
>
<IconButton>
<CopyAllOutlined
sx={{
color:
theme.palette.mode === 'dark'
? theme.palette.primary.contrastText
: theme.palette.primary.main
}}
/>
</IconButton>
</CopyToClipboard>
{`SELECT * FROM ${share.consumptionData.sharedGlueDatabase}.TABLENAME`}
</Typography>
</Box>
</Box>
</CardContent>
</Card>
</Box>
<Card>
<CardHeader
title="Shared Items"
Expand Down

0 comments on commit 315dfcf

Please sign in to comment.