diff --git a/backend/dataall/modules/s3_datasets_shares/tasks/dataset_subscription_task.py b/backend/dataall/modules/s3_datasets_shares/tasks/dataset_subscription_task.py
index ac37d633d..e382b05ef 100644
--- a/backend/dataall/modules/s3_datasets_shares/tasks/dataset_subscription_task.py
+++ b/backend/dataall/modules/s3_datasets_shares/tasks/dataset_subscription_task.py
@@ -18,6 +18,9 @@
from dataall.modules.s3_datasets_shares.tasks.subscriptions import poll_queues
from dataall.modules.s3_datasets.db.dataset_repositories import DatasetRepository
from dataall.modules.s3_datasets.db.dataset_models import DatasetStorageLocation, DatasetTable, S3Dataset
+from dataall.modules.datasets_base.db.dataset_models import DatasetBase
+from dataall.modules.shares_base.db.share_object_models import ShareObject
+from dataall.modules.shares_base.services.share_notification_service import DataSharingNotificationType
root = logging.getLogger()
root.setLevel(logging.INFO)
@@ -130,15 +133,25 @@ def publish_sns_message(self, session, message, dataset, share_items, prefix, ta
response = sns_client.publish_dataset_message(message)
log.info(f'SNS update publish response {response}')
- notifications = ShareNotificationService(
- session=session, dataset=dataset, share=share_object
- ).notify_new_data_available_from_owners(s3_prefix=prefix)
+ notifications = self.notify_new_data_available_from_owners(
+ session=session, dataset=dataset, share=share_object, s3_prefix=prefix
+ )
log.info(f'Notifications for share owners {notifications}')
except ClientError as e:
log.error(f'Failed to deliver message {message} due to: {e}')
+ @staticmethod
+ def notify_new_data_available_from_owners(session, dataset: DatasetBase, share: ShareObject, s3_prefix: str):
+ msg = (
+ f'New data (at {s3_prefix}) is available from dataset {dataset.datasetUri} shared by owner {dataset.owner}'
+ )
+ notifications = ShareNotificationService(session=session, dataset=dataset, share=share).register_notifications(
+ notification_type=DataSharingNotificationType.DATASET_VERSION.value, msg=msg
+ )
+ return notifications
+
if __name__ == '__main__':
ENVNAME = os.environ.get('envname', 'local')
diff --git a/backend/dataall/modules/shares_base/db/share_object_repositories.py b/backend/dataall/modules/shares_base/db/share_object_repositories.py
index f77da2841..84b2f90d4 100644
--- a/backend/dataall/modules/shares_base/db/share_object_repositories.py
+++ b/backend/dataall/modules/shares_base/db/share_object_repositories.py
@@ -67,7 +67,7 @@ def get_share_item_by_uri(session, uri):
return share_item
@staticmethod
- def get_share_item_details(session, share_type_model, item_uri): # TODO CHECK THAT IT WORKS
+ def get_share_item_details(session, share_type_model, item_uri):
return session.query(share_type_model).get(item_uri)
@staticmethod
diff --git a/backend/dataall/modules/shares_base/db/share_state_machines_repositories.py b/backend/dataall/modules/shares_base/db/share_state_machines_repositories.py
index ddb5d9ae9..1b9905dcf 100644
--- a/backend/dataall/modules/shares_base/db/share_state_machines_repositories.py
+++ b/backend/dataall/modules/shares_base/db/share_state_machines_repositories.py
@@ -48,6 +48,17 @@ def get_share_items_states(session, share_uri, item_uris=None):
query = query.filter(ShareObjectItem.shareItemUri.in_(item_uris))
return [item.status for item in query.distinct(ShareObjectItem.status)]
+ @staticmethod
+ def get_share_items_health_states(session, share_uri, item_uris=None):
+ query = session.query(ShareObjectItem).filter(
+ and_(
+ ShareObjectItem.shareUri == share_uri,
+ )
+ )
+ if item_uris:
+ query = query.filter(ShareObjectItem.shareItemUri.in_(item_uris))
+ return [item.healthStatus for item in query.distinct(ShareObjectItem.healthStatus)]
+
@staticmethod
def update_share_object_status(session, share_uri: str, status: str) -> ShareObject:
share = ShareObjectRepository.get_share_by_uri(session, share_uri)
@@ -78,7 +89,7 @@ def update_share_item_status_batch(
and_(ShareObjectItem.shareUri == share_uri, ShareObjectItem.status == old_status)
)
if share_item_type:
- query = query.filter(ShareObjectItem.shareableType == share_item_type.value)
+ query = query.filter(ShareObjectItem.itemType == share_item_type.value)
query.update(
{
diff --git a/backend/dataall/modules/shares_base/services/share_item_service.py b/backend/dataall/modules/shares_base/services/share_item_service.py
index a4d231978..9f27b6121 100644
--- a/backend/dataall/modules/shares_base/services/share_item_service.py
+++ b/backend/dataall/modules/shares_base/services/share_item_service.py
@@ -81,6 +81,9 @@ def revoke_items_share_object(uri, revoked_uris):
share = ShareObjectRepository.get_share_by_uri(session, uri)
dataset = DatasetBaseRepository.get_dataset_by_uri(session, share.datasetUri)
revoked_items_states = ShareStatusRepository.get_share_items_states(session, uri, revoked_uris)
+ revoked_items_health_states = ShareStatusRepository.get_share_items_health_states(
+ session, uri, revoked_uris
+ )
revoked_items = [ShareObjectRepository.get_share_item_by_uri(session, uri) for uri in revoked_uris]
if not revoked_items_states:
@@ -89,6 +92,12 @@ def revoke_items_share_object(uri, revoked_uris):
message='Nothing to be revoked.',
)
+ if ShareItemHealthStatus.PendingReApply.value in revoked_items_health_states:
+ raise UnauthorizedOperation(
+ action='Revoke Items from Share Object',
+ message='Cannot revoke while reapply pending for one or more items.',
+ )
+
share_sm = ShareObjectSM(share.status)
new_share_state = share_sm.run_transition(ShareObjectActions.RevokeItems.value)
@@ -124,26 +133,16 @@ def add_shared_item(uri: str, data: dict = None):
item_type = data.get('itemType')
item_uri = data.get('itemUri')
share = ShareObjectRepository.get_share_by_uri(session, uri)
- target_environment = EnvironmentService.get_environment_by_uri(session, share.environmentUri)
share_sm = ShareObjectSM(share.status)
new_share_state = share_sm.run_transition(ShareItemActions.AddItem.value)
share_sm.update_state(session, share, new_share_state)
+
processor = ShareProcessorManager.get_processor_by_item_type(item_type)
item = ShareObjectRepository.get_share_item_details(session, processor.shareable_type, item_uri)
if not item:
raise ObjectNotFound('ShareObjectItem', item_uri)
- if (
- item_type == ShareableType.Table.value and item.region != target_environment.region
- ): # TODO Part10: remove from here (we might be able to remove get_share_item_details entirely
- raise UnauthorizedOperation(
- action=ADD_ITEM,
- message=f'Lake Formation cross region sharing is not supported. '
- f'Table {item.itemUri} is in {item.region} and target environment '
- f'{target_environment.name} is in {target_environment.region} ',
- )
-
share_item: ShareObjectItem = ShareObjectRepository.find_sharable_item(session, uri, item_uri)
if not share_item:
@@ -163,17 +162,6 @@ def add_shared_item(uri: str, data: dict = None):
def remove_shared_item(uri: str):
with get_context().db_engine.scoped_session() as session:
share_item = ShareObjectRepository.get_share_item_by_uri(session, uri)
- if (
- share_item.itemType == ShareableType.Table.value # TODO Part10 - REMOVE
- and share_item.status == ShareItemStatus.Share_Failed.value
- ):
- share = ShareObjectRepository.get_share_by_uri(session, share_item.shareUri)
- ResourcePolicyService.delete_resource_policy(
- session=session,
- group=share.groupUri,
- resource_uri=share_item.itemUri,
- )
-
item_sm = ShareItemSM(share_item.status)
item_sm.run_transition(ShareItemActions.RemoveItem.value)
ShareObjectRepository.remove_share_object_item(session, share_item)
@@ -184,9 +172,7 @@ def remove_shared_item(uri: str):
def resolve_shared_item(uri, item: ShareObjectItem):
with get_context().db_engine.scoped_session() as session:
processor = ShareProcessorManager.get_processor_by_item_type(item.itemType)
- return ShareObjectRepository.get_share_item_details(
- session, processor.shareable_type, item.itemUri
- ) # TODO - check it works
+ return ShareObjectRepository.get_share_item_details(session, processor.shareable_type, item.itemUri)
@staticmethod
def check_existing_shared_items(share):
diff --git a/backend/dataall/modules/shares_base/services/share_notification_service.py b/backend/dataall/modules/shares_base/services/share_notification_service.py
index 197b706f4..765138af9 100644
--- a/backend/dataall/modules/shares_base/services/share_notification_service.py
+++ b/backend/dataall/modules/shares_base/services/share_notification_service.py
@@ -49,7 +49,7 @@ def notify_share_object_submission(self, email_id: str):
subject = f'Data.all | Share Request Submitted for {self.dataset.label}'
email_notification_msg = msg + share_link_text
- notifications = self._register_notifications(
+ notifications = self.register_notifications(
notification_type=DataSharingNotificationType.SHARE_OBJECT_SUBMITTED.value, msg=msg
)
@@ -64,7 +64,7 @@ def notify_share_object_approval(self, email_id: str):
subject = f'Data.all | Share Request Approved for {self.dataset.label}'
email_notification_msg = msg + share_link_text
- notifications = self._register_notifications(
+ notifications = self.register_notifications(
notification_type=DataSharingNotificationType.SHARE_OBJECT_APPROVED.value, msg=msg
)
@@ -86,21 +86,13 @@ def notify_share_object_rejection(self, email_id: str):
subject = f'Data.all | Share Request Rejected / Revoked for {self.dataset.label}'
email_notification_msg = msg + share_link_text
- notifications = self._register_notifications(
+ notifications = self.register_notifications(
notification_type=DataSharingNotificationType.SHARE_OBJECT_REJECTED.value, msg=msg
)
self._create_notification_task(subject=subject, msg=email_notification_msg)
return notifications
- def notify_new_data_available_from_owners(self, s3_prefix): # TODO part10: remove, this is specific for S3
- msg = f'New data (at {s3_prefix}) is available from dataset {self.dataset.datasetUri} shared by owner {self.dataset.owner}'
-
- notifications = self._register_notifications(
- notification_type=DataSharingNotificationType.DATASET_VERSION.value, msg=msg
- )
- return notifications
-
def _get_share_object_targeted_users(self):
targeted_users = list()
targeted_users.append(self.dataset.SamlAdminGroupName)
@@ -109,7 +101,7 @@ def _get_share_object_targeted_users(self):
targeted_users.append(self.share.groupUri)
return targeted_users
- def _register_notifications(self, notification_type, msg):
+ def register_notifications(self, notification_type, msg):
"""
Notifications sent to:
- dataset.SamlAdminGroupName
diff --git a/deploy/custom_resources/custom_authorizer/requirements.txt b/deploy/custom_resources/custom_authorizer/requirements.txt
index 6eca03d80..930779231 100644
--- a/deploy/custom_resources/custom_authorizer/requirements.txt
+++ b/deploy/custom_resources/custom_authorizer/requirements.txt
@@ -7,4 +7,4 @@ python-jose==3.3.0
requests==2.32.2
rsa==4.9
six==1.16.0
-urllib3==1.26.18
\ No newline at end of file
+urllib3==1.26.19
\ No newline at end of file
diff --git a/frontend/src/design/components/ShareHealthStatus.js b/frontend/src/design/components/ShareHealthStatus.js
new file mode 100644
index 000000000..983536f67
--- /dev/null
+++ b/frontend/src/design/components/ShareHealthStatus.js
@@ -0,0 +1,72 @@
+import VerifiedUserOutlinedIcon from '@mui/icons-material/VerifiedUserOutlined';
+import GppBadOutlinedIcon from '@mui/icons-material/GppBadOutlined';
+import PendingOutlinedIcon from '@mui/icons-material/PendingOutlined';
+import DangerousOutlinedIcon from '@mui/icons-material/DangerousOutlined';
+import * as PropTypes from 'prop-types';
+import { Typography } from '@mui/material';
+import { Label } from './Label';
+
+export const ShareHealthStatus = (props) => {
+ const { status, healthStatus, lastVerificationTime } = props;
+
+ const isShared = ['Revoke_Failed', 'Share_Succeeded'].includes(status);
+ const isHealthPending = ['PendingReApply', 'PendingVerify', null].includes(
+ healthStatus
+ );
+ const setStatus = () => {
+ if (!healthStatus) return 'Undefined';
+ return healthStatus;
+ };
+
+ const setColor = () => {
+ if (!healthStatus) return 'info';
+ if (['Healthy'].includes(healthStatus)) return 'success';
+ if (['Unhealthy'].includes(healthStatus)) return 'error';
+ if (isHealthPending) return 'warning';
+ return 'info';
+ };
+
+ const setIcon = () => {
+ if (!healthStatus) return ;
+ if (['Healthy'].includes(healthStatus))
+ return ;
+ if (['Unhealthy'].includes(healthStatus))
+ return ;
+ if (['PendingReApply', 'PendingVerify'].includes(healthStatus))
+ return ;
+ return ;
+ };
+
+ if (!isShared) {
+ return (
+
+ {'Item is not Shared'}
+
+ );
+ }
+
+ return (
+
+ {setIcon()}
+
+ {!isHealthPending && (
+
+ {(lastVerificationTime &&
+ '(' +
+ lastVerificationTime.substring(
+ 0,
+ lastVerificationTime.indexOf('.')
+ ) +
+ ')') ||
+ ''}
+
+ )}
+
+ );
+};
+
+ShareHealthStatus.propTypes = {
+ status: PropTypes.string.isRequired,
+ healthStatus: PropTypes.string,
+ lastVerificationTime: PropTypes.string
+};
diff --git a/frontend/src/design/components/index.js b/frontend/src/design/components/index.js
index cd01380bb..8b5ce3bc0 100644
--- a/frontend/src/design/components/index.js
+++ b/frontend/src/design/components/index.js
@@ -19,6 +19,7 @@ export * from './Scrollbar';
export * from './SearchInput';
export * from './SettingsDrawer';
export * from './ShareStatus';
+export * from './ShareHealthStatus';
export * from './SplashScreen';
export * from './StackStatus';
export * from './TagsInput';
diff --git a/frontend/src/modules/Dashboards/views/DashboardImportForm.js b/frontend/src/modules/Dashboards/views/DashboardImportForm.js
index 50fbc3ac4..83afbc2c9 100644
--- a/frontend/src/modules/Dashboards/views/DashboardImportForm.js
+++ b/frontend/src/modules/Dashboards/views/DashboardImportForm.js
@@ -8,12 +8,10 @@ import {
CardContent,
CardHeader,
Chip,
- CircularProgress,
Container,
FormHelperText,
Grid,
Link,
- MenuItem,
TextField,
Typography
} from '@mui/material';
@@ -31,13 +29,9 @@ import {
useSettings
} from 'design';
import { SET_ERROR, useDispatch } from 'globalErrors';
-import {
- listEnvironmentGroups,
- listValidEnvironments,
- searchGlossary,
- useClient
-} from 'services';
+import { searchGlossary, useClient } from 'services';
import { importDashboard } from '../services';
+import { EnvironmentTeamDropdown } from 'modules/Shared';
const DashboardImportForm = (props) => {
const navigate = useNavigate();
@@ -45,31 +39,8 @@ const DashboardImportForm = (props) => {
const dispatch = useDispatch();
const client = useClient();
const { settings } = useSettings();
- const [loading, setLoading] = useState(true);
- const [groupOptions, setGroupOptions] = useState([]);
- const [environmentOptions, setEnvironmentOptions] = useState([]);
const [selectableTerms, setSelectableTerms] = useState([]);
- const fetchEnvironments = useCallback(async () => {
- setLoading(true);
- const response = await client.query(
- listValidEnvironments({
- filter: Defaults.selectListFilter
- })
- );
- if (!response.errors) {
- setEnvironmentOptions(
- response.data.listValidEnvironments.nodes.map((e) => ({
- ...e,
- value: e.environmentUri,
- label: e.label
- }))
- );
- } else {
- dispatch({ type: SET_ERROR, error: response.errors[0].message });
- }
- setLoading(false);
- }, [client, dispatch]);
const fetchTerms = useCallback(async () => {
const response = await client.query(
searchGlossary(Defaults.selectListFilter)
@@ -95,37 +66,11 @@ const DashboardImportForm = (props) => {
}, [client, dispatch]);
useEffect(() => {
if (client) {
- fetchEnvironments().catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
fetchTerms().catch((e) =>
dispatch({ type: SET_ERROR, error: e.message })
);
}
- }, [client, fetchTerms, fetchEnvironments, dispatch]);
-
- const fetchGroups = async (environmentUri) => {
- try {
- const response = await client.query(
- listEnvironmentGroups({
- filter: Defaults.selectListFilter,
- environmentUri
- })
- );
- if (!response.errors) {
- setGroupOptions(
- response.data.listEnvironmentGroups.nodes.map((g) => ({
- value: g.groupUri,
- label: g.groupUri
- }))
- );
- } else {
- dispatch({ type: SET_ERROR, error: response.errors[0].message });
- }
- } catch (e) {
- dispatch({ type: SET_ERROR, error: e.message });
- }
- };
+ }, [client, fetchTerms, dispatch]);
async function submit(values, setStatus, setSubmitting, setErrors) {
try {
@@ -136,7 +81,7 @@ const DashboardImportForm = (props) => {
dashboardId: values.dashboardId,
environmentUri: values.environment.environmentUri,
description: values.description,
- SamlGroupName: values.SamlGroupName,
+ SamlGroupName: values.SamlAdminGroupName,
tags: values.tags,
terms: values.terms.nodes
? values.terms.nodes.map((t) => t.nodeUri)
@@ -168,9 +113,6 @@ const DashboardImportForm = (props) => {
dispatch({ type: SET_ERROR, error: err.message });
}
}
- if (loading) {
- return ;
- }
return (
<>
@@ -239,7 +181,7 @@ const DashboardImportForm = (props) => {
label: '',
dashboardId: '',
description: '',
- SamlGroupName: '',
+ SamlAdminGroupName: '',
environment: '',
tags: [],
terms: []
@@ -252,7 +194,7 @@ const DashboardImportForm = (props) => {
.max(255)
.required('*QuickSight dashboard identifier is required'),
description: Yup.string().max(5000),
- SamlGroupName: Yup.string()
+ SamlAdminGroupName: Yup.string()
.max(255)
.required('*Team is required'),
environment: Yup.object().required('*Environment is required'),
@@ -392,94 +334,13 @@ const DashboardImportForm = (props) => {
-
-
-
- {
- setFieldValue('SamlGroupName', '');
- fetchGroups(
- event.target.value.environmentUri
- ).catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- setFieldValue('environment', event.target.value);
- }}
- select
- value={values.environment}
- variant="outlined"
- >
- {environmentOptions.map((environment) => (
-
- ))}
-
-
-
-
-
-
-
-
-
-
- {groupOptions.map((group) => (
-
- ))}
-
-
-
+
{errors.submit && (
{errors.submit}
diff --git a/frontend/src/modules/DatasetsBase/components/DatasetListItem.js b/frontend/src/modules/DatasetsBase/components/DatasetListItem.js
index 432883398..85726a435 100644
--- a/frontend/src/modules/DatasetsBase/components/DatasetListItem.js
+++ b/frontend/src/modules/DatasetsBase/components/DatasetListItem.js
@@ -41,7 +41,7 @@ export const DatasetListItem = (props) => {
variant="h6"
onClick={() => {
navigate(
- dataset.datasetType === 'DatasetType.S3'
+ dataset.datasetType === 'DatasetTypes.S3'
? `/console/s3-datasets/${dataset.datasetUri}`
: '-'
);
@@ -208,7 +208,7 @@ export const DatasetListItem = (props) => {
color="primary"
component={RouterLink}
to={
- dataset.datasetType === 'DatasetType.S3'
+ dataset.datasetType === 'DatasetTypes.S3'
? `/console/s3-datasets/${dataset.datasetUri}`
: '-'
}
diff --git a/frontend/src/modules/Environments/components/EnvironmentOwnedDatasets.js b/frontend/src/modules/Environments/components/EnvironmentOwnedDatasets.js
index 1ed875a3f..5ef920184 100644
--- a/frontend/src/modules/Environments/components/EnvironmentOwnedDatasets.js
+++ b/frontend/src/modules/Environments/components/EnvironmentOwnedDatasets.js
@@ -150,7 +150,7 @@ export const EnvironmentOwnedDatasets = ({ environment }) => {
{
navigate(
- dataset.datasetType === 'DatasetType.S3'
+ dataset.datasetType === 'DatasetTypes.S3'
? `/console/s3-datasets/${dataset.datasetUri}`
: '-'
);
diff --git a/frontend/src/modules/Environments/views/EnvironmentCreateForm.js b/frontend/src/modules/Environments/views/EnvironmentCreateForm.js
index 17de06d94..bbe55e522 100644
--- a/frontend/src/modules/Environments/views/EnvironmentCreateForm.js
+++ b/frontend/src/modules/Environments/views/EnvironmentCreateForm.js
@@ -17,7 +17,6 @@ import {
Grid,
IconButton,
Link,
- MenuItem,
Switch,
TextField,
Typography
@@ -73,9 +72,7 @@ const EnvironmentCreateForm = (props) => {
const [trustedAccount, setTrustedAccount] = useState(null);
const [pivotRoleName, setPivotRoleName] = useState(null);
const [loading, setLoading] = useState(true);
- const groupOptions = groups
- ? groups.map((g) => ({ value: g, label: g }))
- : [];
+
const fetchItem = useCallback(async () => {
setLoading(true);
const response = await client.query(getOrganization(params.uri));
@@ -175,7 +172,7 @@ const EnvironmentCreateForm = (props) => {
organizationUri: organization.organizationUri,
AwsAccountId: values.AwsAccountId,
label: values.label,
- SamlGroupName: values.SamlGroupName,
+ SamlGroupName: values.SamlAdminGroupName,
tags: values.tags,
description: values.description,
region: values.region,
@@ -506,7 +503,7 @@ const EnvironmentCreateForm = (props) => {
initialValues={{
label: '',
description: '',
- SamlGroupName: '',
+ SamlAdminGroupName: '',
AwsAccountId: '',
region: '',
tags: [],
@@ -525,7 +522,7 @@ const EnvironmentCreateForm = (props) => {
.max(255)
.required('*Environment name is required'),
description: Yup.string().max(5000),
- SamlGroupName: Yup.string()
+ SamlAdminGroupName: Yup.string()
.max(255)
.required('*Team is required'),
AwsAccountId: Yup.number(
@@ -873,27 +870,37 @@ const EnvironmentCreateForm = (props) => {
/>
- {
+ if (value) {
+ setFieldValue('SamlAdminGroupName', value);
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
)}
- helperText={
- touched.SamlGroupName && errors.SamlGroupName
- }
- onChange={handleChange}
- select
- value={values.SamlGroupName}
- variant="outlined"
- >
- {groupOptions.map((group) => (
-
- ))}
-
+ />
{
const client = useClient();
const { settings } = useSettings();
const groups = useGroups();
- const groupOptions = groups
- ? groups.map((g) => ({ value: g, label: g }))
- : [];
async function submit(values, setStatus, setSubmitting, setErrors) {
try {
@@ -42,7 +39,7 @@ const GlossaryCreateForm = (props) => {
createGlossary({
label: values.label,
readme: values.readme,
- admin: values.admin
+ admin: values.SamlAdminGroupName
})
);
@@ -212,21 +209,32 @@ const GlossaryCreateForm = (props) => {
option.value)}
+ id="SamlAdminGroupName"
+ disablePortal
+ options={groups}
onChange={(event, value) => {
- setFieldValue('admin', value);
+ if (value) {
+ setFieldValue('SamlAdminGroupName', value);
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
}}
+ inputValue={values.SamlAdminGroupName}
renderInput={(params) => (
)}
diff --git a/frontend/src/modules/MLStudio/views/MLStudioCreateForm.js b/frontend/src/modules/MLStudio/views/MLStudioCreateForm.js
index 0f4c13e34..74206dc88 100644
--- a/frontend/src/modules/MLStudio/views/MLStudioCreateForm.js
+++ b/frontend/src/modules/MLStudio/views/MLStudioCreateForm.js
@@ -6,18 +6,15 @@ import {
Card,
CardContent,
CardHeader,
- CircularProgress,
Container,
FormHelperText,
Grid,
Link,
- MenuItem,
TextField,
Typography
} from '@mui/material';
import { Formik } from 'formik';
import { useSnackbar } from 'notistack';
-import { useCallback, useEffect, useState } from 'react';
import { Helmet } from 'react-helmet-async';
import { Link as RouterLink, useNavigate } from 'react-router-dom';
import * as Yup from 'yup';
@@ -25,17 +22,13 @@ import {
ArrowLeftIcon,
ChevronRightIcon,
ChipInput,
- Defaults,
useSettings
} from 'design';
import { SET_ERROR, useDispatch } from 'globalErrors';
-import {
- listEnvironmentGroups,
- listValidEnvironments,
- useClient
-} from 'services';
+import { useClient } from 'services';
import { createSagemakerStudioUser } from '../services';
+import { EnvironmentTeamDropdown } from 'modules/Shared';
const MLStudioCreateForm = (props) => {
const navigate = useNavigate();
@@ -43,57 +36,6 @@ const MLStudioCreateForm = (props) => {
const dispatch = useDispatch();
const client = useClient();
const { settings } = useSettings();
- const [loading, setLoading] = useState(true);
- const [groupOptions, setGroupOptions] = useState([]);
- const [environmentOptions, setEnvironmentOptions] = useState([]);
- const fetchEnvironments = useCallback(async () => {
- setLoading(true);
- const response = await client.query(
- listValidEnvironments({ filter: Defaults.selectListFilter })
- );
- if (!response.errors) {
- setEnvironmentOptions(
- response.data.listValidEnvironments.nodes.map((e) => ({
- ...e,
- value: e.environmentUri,
- label: e.label
- }))
- );
- } else {
- dispatch({ type: SET_ERROR, error: response.errors[0].message });
- }
- setLoading(false);
- }, [client, dispatch]);
- const fetchGroups = async (environmentUri) => {
- try {
- const response = await client.query(
- listEnvironmentGroups({
- filter: Defaults.selectListFilter,
- environmentUri
- })
- );
- if (!response.errors) {
- setGroupOptions(
- response.data.listEnvironmentGroups.nodes.map((g) => ({
- value: g.groupUri,
- label: g.groupUri
- }))
- );
- } else {
- dispatch({ type: SET_ERROR, error: response.errors[0].message });
- }
- } catch (e) {
- dispatch({ type: SET_ERROR, error: e.message });
- }
- };
- useEffect(() => {
- if (client) {
- fetchEnvironments().catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- }
- }, [client, dispatch, fetchEnvironments]);
-
async function submit(values, setStatus, setSubmitting, setErrors) {
try {
const response = await client.mutate(
@@ -130,10 +72,6 @@ const MLStudioCreateForm = (props) => {
setSubmitting(false);
}
}
- if (loading) {
- return ;
- }
-
return (
<>
@@ -282,31 +220,6 @@ const MLStudioCreateForm = (props) => {
-
-
- {groupOptions.map((group) => (
-
- ))}
-
-
{
-
-
-
- {
- setFieldValue('SamlGroupName', '');
- fetchGroups(
- event.target.value.environmentUri
- ).catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- setFieldValue('environment', event.target.value);
- }}
- select
- value={values.environment}
- variant="outlined"
- >
- {environmentOptions.map((environment) => (
-
- ))}
-
-
-
-
-
-
-
-
-
+
{errors.submit && (
{errors.submit}
diff --git a/frontend/src/modules/Notebooks/views/NotebookCreateForm.js b/frontend/src/modules/Notebooks/views/NotebookCreateForm.js
index 2d256fc2d..b433b5fe2 100644
--- a/frontend/src/modules/Notebooks/views/NotebookCreateForm.js
+++ b/frontend/src/modules/Notebooks/views/NotebookCreateForm.js
@@ -394,45 +394,52 @@ const NotebookCreateForm = (props) => {
- {
+ option)}
+ onChange={(event, value) => {
setFieldValue('SamlAdminGroupName', '');
- fetchGroups(
- event.target.value.environmentUri
- ).catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- setFieldValue('environment', event.target.value);
- setVpcOptions(
- event.target.value.networks.map((v) => ({
- ...v,
- value: v,
- label: v.VpcId
- }))
- );
+ if (value && value.environmentUri) {
+ setFieldValue('environment', value);
+ fetchGroups(value.environmentUri).catch((e) =>
+ dispatch({
+ type: SET_ERROR,
+ error: e.message
+ })
+ );
+ setVpcOptions(
+ value.networks.map((v) => ({
+ ...v,
+ value: v,
+ label: v.VpcId
+ }))
+ );
+ } else {
+ setFieldValue('environment', '');
+ setVpcOptions([]);
+ setGroupOptions([]);
+ }
}}
- select
- value={values.environment}
- variant="outlined"
- >
- {environmentOptions.map((environment) => (
-
- ))}
-
+ renderInput={(params) => (
+
+ )}
+ />
{
label="Region"
name="region"
value={
- values.environment
+ values.environment && values.environment.region
? values.environment.region
: ''
}
@@ -455,7 +462,8 @@ const NotebookCreateForm = (props) => {
label="Organization"
name="organization"
value={
- values.environment
+ values.environment &&
+ values.environment.organization
? values.environment.organization.label
: ''
}
@@ -463,29 +471,59 @@ const NotebookCreateForm = (props) => {
/>
- option)}
+ onChange={(event, value) => {
+ if (value && value.value) {
+ setFieldValue(
+ 'SamlAdminGroupName',
+ value.value
+ );
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
+ {groupOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
)}
- helperText={
- touched.SamlAdminGroupName &&
- errors.SamlAdminGroupName
- }
- label="Team"
- name="SamlAdminGroupName"
- onChange={handleChange}
- select
- value={values.SamlAdminGroupName}
- variant="outlined"
- >
- {groupOptions.map((group) => (
-
- ))}
-
+ />
@@ -558,7 +596,6 @@ const NotebookCreateForm = (props) => {
-
{errors.submit && (
{errors.submit}
diff --git a/frontend/src/modules/Omics/views/OmicsRunCreateForm.js b/frontend/src/modules/Omics/views/OmicsRunCreateForm.js
index cdf5dc4e3..fbf62acad 100644
--- a/frontend/src/modules/Omics/views/OmicsRunCreateForm.js
+++ b/frontend/src/modules/Omics/views/OmicsRunCreateForm.js
@@ -14,22 +14,17 @@ import {
FormHelperText,
Grid,
Link,
- MenuItem,
TextField,
Typography
} from '@mui/material';
import { Helmet } from 'react-helmet-async';
import { LoadingButton } from '@mui/lab';
import React, { useCallback, useEffect, useState } from 'react';
-import {
- useClient,
- listEnvironmentGroups,
- listValidEnvironments,
- listS3DatasetsOwnedByEnvGroup
-} from 'services';
+import { useClient } from 'services';
import { getOmicsWorkflow, createOmicsRun } from '../services';
-import { ArrowLeftIcon, ChevronRightIcon, Defaults, useSettings } from 'design';
+import { ArrowLeftIcon, ChevronRightIcon, useSettings } from 'design';
import { SET_ERROR, useDispatch } from 'globalErrors';
+import { EnvironmentTeamDatasetsDropdown } from 'modules/Shared';
const OmicsRunCreateForm = (props) => {
const params = useParams();
@@ -54,88 +49,6 @@ const OmicsRunCreateForm = (props) => {
setLoading(false);
}, [client, dispatch, params.uri]);
- const [groupOptions, setGroupOptions] = useState([]);
- const [environmentOptions, setEnvironmentOptions] = useState([]);
- const [currentEnv, setCurrentEnv] = useState('');
- const [datasetOptions, setDatasetOptions] = useState([]);
- const fetchEnvironments = useCallback(async () => {
- setLoading(true);
- const response = await client.query(
- listValidEnvironments({ filter: Defaults.SelectListFilter })
- );
- if (!response.errors) {
- setEnvironmentOptions(
- response.data.listValidEnvironments.nodes.map((e) => ({
- ...e,
- value: e.environmentUri,
- label: e.label
- }))
- );
- } else {
- dispatch({ type: SET_ERROR, error: response.errors[0].message });
- }
- setLoading(false);
- }, [client, dispatch]);
-
- const fetchGroups = async (environmentUri) => {
- setCurrentEnv(environmentUri);
- try {
- const response = await client.query(
- listEnvironmentGroups({
- filter: Defaults.SelectListFilter,
- environmentUri
- })
- );
- if (!response.errors) {
- setGroupOptions(
- response.data.listEnvironmentGroups.nodes.map((g) => ({
- value: g.groupUri,
- label: g.groupUri
- }))
- );
- } else {
- dispatch({ type: SET_ERROR, error: response.errors[0].message });
- }
- } catch (e) {
- dispatch({ type: SET_ERROR, error: e.message });
- }
- };
-
- const fetchDatasets = async (groupUri) => {
- let ownedDatasets = [];
- try {
- const response = await client.query(
- listS3DatasetsOwnedByEnvGroup({
- filter: Defaults.SelectListFilter,
- environmentUri: currentEnv,
- groupUri: groupUri
- })
- );
- if (!response.errors) {
- ownedDatasets = response.data.listS3DatasetsOwnedByEnvGroup.nodes?.map(
- (dataset) => ({
- value: dataset.datasetUri,
- label: dataset.label
- })
- );
- } else {
- dispatch({ type: SET_ERROR, error: response.errors[0].message });
- }
- } catch (e) {
- dispatch({ type: SET_ERROR, error: e.message });
- }
- setDatasetOptions(ownedDatasets);
- };
-
- useEffect(() => {
- if (client) {
- fetchEnvironments().catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- fetchItem().catch((e) => dispatch({ type: SET_ERROR, error: e.message }));
- }
- }, [client, dispatch, fetchEnvironments, fetchItem]);
-
useEffect(() => {
if (client) {
fetchItem().catch((e) => dispatch({ type: SET_ERROR, error: e.message }));
@@ -151,7 +64,7 @@ const OmicsRunCreateForm = (props) => {
workflowUri: omicsWorkflow.workflowUri,
parameterTemplate: values.parameterTemplate,
SamlAdminGroupName: values.SamlAdminGroupName,
- destination: values.destination
+ destination: values.dataset
})
);
setStatus({ success: true });
@@ -252,7 +165,7 @@ const OmicsRunCreateForm = (props) => {
label: '',
SamlAdminGroupName: '',
environment: '',
- destination: '',
+ dataset: '',
parameterTemplate: omicsWorkflow.parameterTemplate
}}
validationSchema={Yup.object().shape({
@@ -267,9 +180,7 @@ const OmicsRunCreateForm = (props) => {
.max(255)
.required('*Team is required'),
environment: Yup.object().required('*Environment is required'),
- destination: Yup.string()
- .max(255)
- .required('*Destination is required')
+ dataset: Yup.string().max(255).required('*Dataset is required')
})}
onSubmit={async (
values,
@@ -316,114 +227,13 @@ const OmicsRunCreateForm = (props) => {
variant="outlined"
/>
-
- {
- setFieldValue('SamlAdminGroupName', '');
- fetchGroups(
- event.target.value.environmentUri
- ).catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- setFieldValue('environment', event.target.value);
- }}
- select
- value={values.environment}
- variant="outlined"
- >
- {environmentOptions.map((environment) => (
-
- ))}
-
-
-
-
-
-
- {
- setFieldValue('destination', '');
- fetchDatasets(event.target.value).catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- setFieldValue(
- 'SamlAdminGroupName',
- event.target.value
- );
- }}
- select
- value={values.SamlAdminGroupName}
- variant="outlined"
- >
- {groupOptions.map((group) => (
-
- ))}
-
-
-
-
- {datasetOptions.map((dataset) => (
-
- ))}
-
-
+
diff --git a/frontend/src/modules/Organizations/views/OrganizationCreateForm.js b/frontend/src/modules/Organizations/views/OrganizationCreateForm.js
index 6936e1948..ca5f8e62f 100644
--- a/frontend/src/modules/Organizations/views/OrganizationCreateForm.js
+++ b/frontend/src/modules/Organizations/views/OrganizationCreateForm.js
@@ -1,5 +1,6 @@
import { LoadingButton } from '@mui/lab';
import {
+ Autocomplete,
Box,
Breadcrumbs,
Button,
@@ -10,7 +11,6 @@ import {
FormHelperText,
Grid,
Link,
- MenuItem,
TextField,
Typography
} from '@mui/material';
@@ -36,9 +36,6 @@ const OrganizationCreateForm = (props) => {
const client = useClient();
const groups = useGroups();
const { settings } = useSettings();
- const groupOptions = groups
- ? groups.map((g) => ({ value: g, label: g }))
- : [];
async function submit(values, setStatus, setSubmitting, setErrors) {
try {
@@ -46,7 +43,7 @@ const OrganizationCreateForm = (props) => {
createOrganization({
label: values.label,
description: values.description,
- SamlGroupName: values.SamlGroupName,
+ SamlGroupName: values.SamlAdminGroupName,
tags: values.tags
})
);
@@ -147,7 +144,7 @@ const OrganizationCreateForm = (props) => {
initialValues={{
label: '',
description: '',
- SamlGroupName: '',
+ SamlAdminGroupName: '',
tags: []
}}
validationSchema={Yup.object().shape({
@@ -155,7 +152,7 @@ const OrganizationCreateForm = (props) => {
.max(255)
.required('*Organization name is required'),
description: Yup.string().max(5000),
- SamlGroupName: Yup.string()
+ SamlAdminGroupName: Yup.string()
.max(255)
.required('*Team is required'),
tags: Yup.array().nullable()
@@ -230,27 +227,37 @@ const OrganizationCreateForm = (props) => {
- {
+ if (value) {
+ setFieldValue('SamlAdminGroupName', value);
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
)}
- helperText={
- touched.SamlGroupName && errors.SamlGroupName
- }
- fullWidth
- label="Team"
- name="SamlGroupName"
- onChange={handleChange}
- select
- value={values.SamlGroupName}
- variant="outlined"
- >
- {groupOptions.map((group) => (
-
- ))}
-
+ />
diff --git a/frontend/src/modules/S3_Datasets/views/DatasetCreateForm.js b/frontend/src/modules/S3_Datasets/views/DatasetCreateForm.js
index cc88bf175..565afe50c 100644
--- a/frontend/src/modules/S3_Datasets/views/DatasetCreateForm.js
+++ b/frontend/src/modules/S3_Datasets/views/DatasetCreateForm.js
@@ -120,7 +120,7 @@ const DatasetCreateForm = (props) => {
owner: '',
stewards: values.stewards,
label: values.label,
- SamlAdminGroupName: values.SamlGroupName,
+ SamlAdminGroupName: values.SamlAdminGroupName,
tags: values.tags,
description: values.description,
topics: values.topics ? values.topics.map((t) => t.value) : [],
@@ -220,7 +220,7 @@ const DatasetCreateForm = (props) => {
environment: '',
stewards: '',
confidentiality: '',
- SamlGroupName: '',
+ SamlAdminGroupName: '',
tags: [],
topics: [],
autoApprovalEnabled: false
@@ -230,7 +230,7 @@ const DatasetCreateForm = (props) => {
.max(255)
.required('*Dataset name is required'),
description: Yup.string().max(5000),
- SamlGroupName: Yup.string()
+ SamlAdminGroupName: Yup.string()
.max(255)
.required('*Owners team is required'),
topics: isFeatureEnabled('datasets_base', 'topics_dropdown')
@@ -429,38 +429,45 @@ const DatasetCreateForm = (props) => {
- {
- setFieldValue('SamlGroupName', '');
- fetchGroups(
- event.target.value.environmentUri
- ).catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- setFieldValue('environment', event.target.value);
+ option)}
+ onChange={(event, value) => {
+ setFieldValue('SamlAdminGroupName', '');
+ setFieldValue('stewards', '');
+ if (value && value.environmentUri) {
+ setFieldValue('environment', value);
+ fetchGroups(value.environmentUri).catch((e) =>
+ dispatch({
+ type: SET_ERROR,
+ error: e.message
+ })
+ );
+ } else {
+ setFieldValue('environment', '');
+ setGroupOptions([]);
+ }
}}
- select
- value={values.environment}
- variant="outlined"
- >
- {environmentOptions.map((environment) => (
-
- ))}
-
+ renderInput={(params) => (
+
+ )}
+ />
{
label="Region"
name="region"
value={
- values.environment
+ values.environment && values.environment.region
? values.environment.region
: ''
}
@@ -483,7 +490,8 @@ const DatasetCreateForm = (props) => {
label="Organization"
name="organization"
value={
- values.environment
+ values.environment &&
+ values.environment.organization
? values.environment.organization.label
: ''
}
@@ -494,45 +502,105 @@ const DatasetCreateForm = (props) => {
- option)}
+ onChange={(event, value) => {
+ if (value && value.value) {
+ setFieldValue(
+ 'SamlAdminGroupName',
+ value.value
+ );
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
+ {groupOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
)}
- helperText={
- touched.SamlGroupName && errors.SamlGroupName
- }
- label="Owners"
- name="SamlGroupName"
- onChange={handleChange}
- select
- value={values.SamlGroupName}
- variant="outlined"
- >
- {groupOptions.map((group) => (
-
- ))}
-
+ />
option.value)}
+ disablePortal
+ options={groupOptions.map((option) => option)}
onChange={(event, value) => {
- setFieldValue('stewards', value);
+ if (value && value.value) {
+ setFieldValue('stewards', value.value);
+ } else {
+ setFieldValue('stewards', '');
+ }
}}
- renderInput={(renderParams) => (
-
+ inputValue={values.stewards}
+ renderInput={(params) => (
+
+ {groupOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
)}
/>
diff --git a/frontend/src/modules/S3_Datasets/views/DatasetImportForm.js b/frontend/src/modules/S3_Datasets/views/DatasetImportForm.js
index c73a7cfbc..21454003f 100644
--- a/frontend/src/modules/S3_Datasets/views/DatasetImportForm.js
+++ b/frontend/src/modules/S3_Datasets/views/DatasetImportForm.js
@@ -119,7 +119,7 @@ const DatasetImportForm = (props) => {
environmentUri: values.environment.environmentUri,
owner: '',
label: values.label,
- SamlAdminGroupName: values.SamlGroupName,
+ SamlAdminGroupName: values.SamlAdminGroupName,
tags: values.tags,
description: values.description,
topics: values.topics ? values.topics.map((t) => t.value) : [],
@@ -223,7 +223,7 @@ const DatasetImportForm = (props) => {
environment: '',
businessOwnerEmail: '',
businessOwnerDelegationEmails: [],
- SamlGroupName: '',
+ SamlAdminGroupName: '',
stewards: '',
tags: [],
topics: [],
@@ -238,7 +238,7 @@ const DatasetImportForm = (props) => {
.max(255)
.required('*Dataset name is required'),
description: Yup.string().max(5000),
- SamlGroupName: Yup.string()
+ SamlAdminGroupName: Yup.string()
.max(255)
.required('*Team is required'),
topics: isFeatureEnabled('datasets_base', 'topics_dropdown')
@@ -442,38 +442,45 @@ const DatasetImportForm = (props) => {
- {
- setFieldValue('SamlGroupName', '');
- fetchGroups(
- event.target.value.environmentUri
- ).catch((e) =>
- dispatch({ type: SET_ERROR, error: e.message })
- );
- setFieldValue('environment', event.target.value);
+ option)}
+ onChange={(event, value) => {
+ setFieldValue('SamlAdminGroupName', '');
+ setFieldValue('stewards', '');
+ if (value && value.environmentUri) {
+ setFieldValue('environment', value);
+ fetchGroups(value.environmentUri).catch((e) =>
+ dispatch({
+ type: SET_ERROR,
+ error: e.message
+ })
+ );
+ } else {
+ setFieldValue('environment', '');
+ setGroupOptions([]);
+ }
}}
- select
- value={values.environment}
- variant="outlined"
- >
- {environmentOptions.map((environment) => (
-
- ))}
-
+ renderInput={(params) => (
+
+ )}
+ />
{
label="Region"
name="region"
value={
- values.environment
+ values.environment && values.environment.region
? values.environment.region
: ''
}
@@ -496,7 +503,8 @@ const DatasetImportForm = (props) => {
label="Organization"
name="organization"
value={
- values.environment
+ values.environment &&
+ values.environment.organization
? values.environment.organization.label
: ''
}
@@ -558,45 +566,105 @@ const DatasetImportForm = (props) => {
- option)}
+ onChange={(event, value) => {
+ if (value && value.value) {
+ setFieldValue(
+ 'SamlAdminGroupName',
+ value.value
+ );
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
+ {groupOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
)}
- helperText={
- touched.SamlGroupName && errors.SamlGroupName
- }
- label="Team"
- name="SamlGroupName"
- onChange={handleChange}
- select
- value={values.SamlGroupName}
- variant="outlined"
- >
- {groupOptions.map((group) => (
-
- ))}
-
+ />
option.value)}
+ disablePortal
+ options={groupOptions.map((option) => option)}
onChange={(event, value) => {
- setFieldValue('stewards', value);
+ if (value && value.value) {
+ setFieldValue('stewards', value.value);
+ } else {
+ setFieldValue('stewards', '');
+ }
}}
- renderInput={(renderParams) => (
-
+ inputValue={values.stewards}
+ renderInput={(params) => (
+
+ {groupOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
)}
/>
diff --git a/frontend/src/modules/Shared/EnvironmentGroupSelect/EnvironmentTeamDatasetsDropdown.js b/frontend/src/modules/Shared/EnvironmentGroupSelect/EnvironmentTeamDatasetsDropdown.js
new file mode 100644
index 000000000..515182243
--- /dev/null
+++ b/frontend/src/modules/Shared/EnvironmentGroupSelect/EnvironmentTeamDatasetsDropdown.js
@@ -0,0 +1,282 @@
+import {
+ Autocomplete,
+ Box,
+ Card,
+ CardContent,
+ CardHeader,
+ CircularProgress,
+ TextField
+} from '@mui/material';
+import React, { useCallback, useEffect, useState } from 'react';
+import { Defaults } from 'design';
+import { SET_ERROR, useDispatch } from 'globalErrors';
+import {
+ listEnvironmentGroups,
+ listValidEnvironments,
+ listS3DatasetsOwnedByEnvGroup,
+ useClient
+} from 'services';
+
+import PropTypes from 'prop-types';
+
+export const EnvironmentTeamDatasetsDropdown = (props) => {
+ const { setFieldValue, handleChange, values, touched, errors } = props;
+ const dispatch = useDispatch();
+ const client = useClient();
+ const [loading, setLoading] = useState(true);
+ const [groupOptions, setGroupOptions] = useState([]);
+ const [environmentOptions, setEnvironmentOptions] = useState([]);
+ const [currentEnv, setCurrentEnv] = useState('');
+ const [datasetOptions, setDatasetOptions] = useState([]);
+ const fetchEnvironments = useCallback(async () => {
+ setLoading(true);
+ const response = await client.query(
+ listValidEnvironments({ filter: Defaults.SelectListFilter })
+ );
+ if (!response.errors) {
+ setEnvironmentOptions(
+ response.data.listValidEnvironments.nodes.map((e) => ({
+ ...e,
+ value: e.environmentUri,
+ label: e.label
+ }))
+ );
+ } else {
+ dispatch({ type: SET_ERROR, error: response.errors[0].message });
+ }
+ setLoading(false);
+ }, [client, dispatch]);
+ const fetchGroups = async (environmentUri) => {
+ setCurrentEnv(environmentUri);
+ try {
+ const response = await client.query(
+ listEnvironmentGroups({
+ filter: Defaults.SelectListFilter,
+ environmentUri
+ })
+ );
+ if (!response.errors) {
+ setGroupOptions(
+ response.data.listEnvironmentGroups.nodes.map((g) => ({
+ value: g.groupUri,
+ label: g.groupUri
+ }))
+ );
+ } else {
+ dispatch({ type: SET_ERROR, error: response.errors[0].message });
+ }
+ } catch (e) {
+ dispatch({ type: SET_ERROR, error: e.message });
+ }
+ };
+ const fetchDatasets = async (groupUri) => {
+ let ownedDatasets = [];
+ try {
+ const response = await client.query(
+ listS3DatasetsOwnedByEnvGroup({
+ filter: Defaults.SelectListFilter,
+ environmentUri: currentEnv,
+ groupUri: groupUri
+ })
+ );
+ if (!response.errors) {
+ ownedDatasets = response.data.listS3DatasetsOwnedByEnvGroup.nodes?.map(
+ (dataset) => ({
+ value: dataset.datasetUri,
+ label: dataset.label
+ })
+ );
+ } else {
+ dispatch({ type: SET_ERROR, error: response.errors[0].message });
+ }
+ } catch (e) {
+ dispatch({ type: SET_ERROR, error: e.message });
+ }
+ setDatasetOptions(ownedDatasets);
+ };
+
+ useEffect(() => {
+ if (client) {
+ fetchEnvironments().catch((e) =>
+ dispatch({ type: SET_ERROR, error: e.message })
+ );
+ }
+ }, [client, dispatch, fetchEnvironments]);
+
+ if (loading) {
+ return ;
+ }
+
+ return (
+
+
+
+
+ option)}
+ onChange={(event, value) => {
+ setFieldValue('SamlAdminGroupName', '');
+ setFieldValue('dataset', '');
+ if (value && value.environmentUri) {
+ setFieldValue('environment', value);
+ fetchGroups(value.environmentUri).catch((e) =>
+ dispatch({
+ type: SET_ERROR,
+ error: e.message
+ })
+ );
+ } else {
+ setFieldValue('environment', '');
+ setGroupOptions([]);
+ setDatasetOptions([]);
+ }
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+ option)}
+ onChange={(event, value) => {
+ setFieldValue('dataset', '');
+ if (value && value.value) {
+ setFieldValue('SamlAdminGroupName', value.value);
+ fetchDatasets(value).catch((e) =>
+ dispatch({ type: SET_ERROR, error: e.message })
+ );
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ setDatasetOptions([]);
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
+ {groupOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
+ )}
+ />
+
+
+ option)}
+ onChange={(event, value) => {
+ if (value && value.value) {
+ setFieldValue('dataset', value.value);
+ } else {
+ setFieldValue('dataset', '');
+ }
+ }}
+ inputValue={values.dataset}
+ renderInput={(params) => (
+
+ {datasetOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
+ )}
+ />
+
+
+
+ );
+};
+
+EnvironmentTeamDatasetsDropdown.propTypes = {
+ setFieldValue: PropTypes.func.isRequired,
+ handleChange: PropTypes.func.isRequired,
+ values: PropTypes.object.isRequired,
+ touched: PropTypes.object.isRequired,
+ errors: PropTypes.object.isRequired
+};
diff --git a/frontend/src/modules/Shared/EnvironmentGroupSelect/EnvironmentTeamDropdown.js b/frontend/src/modules/Shared/EnvironmentGroupSelect/EnvironmentTeamDropdown.js
new file mode 100644
index 000000000..6862c8baf
--- /dev/null
+++ b/frontend/src/modules/Shared/EnvironmentGroupSelect/EnvironmentTeamDropdown.js
@@ -0,0 +1,205 @@
+import {
+ Autocomplete,
+ Box,
+ Card,
+ CardContent,
+ CardHeader,
+ CircularProgress,
+ TextField
+} from '@mui/material';
+import React, { useCallback, useEffect, useState } from 'react';
+import { Defaults } from 'design';
+import { SET_ERROR, useDispatch } from 'globalErrors';
+import {
+ listEnvironmentGroups,
+ listValidEnvironments,
+ useClient
+} from 'services';
+
+import PropTypes from 'prop-types';
+
+export const EnvironmentTeamDropdown = (props) => {
+ const { setFieldValue, handleChange, values, touched, errors } = props;
+ const dispatch = useDispatch();
+ const client = useClient();
+ const [loading, setLoading] = useState(true);
+ const [groupOptions, setGroupOptions] = useState([]);
+ const [environmentOptions, setEnvironmentOptions] = useState([]);
+ const fetchEnvironments = useCallback(async () => {
+ setLoading(true);
+ const response = await client.query(
+ listValidEnvironments({ filter: Defaults.selectListFilter })
+ );
+ if (!response.errors) {
+ setEnvironmentOptions(
+ response.data.listValidEnvironments.nodes.map((e) => ({
+ ...e,
+ value: e.environmentUri,
+ label: e.label
+ }))
+ );
+ } else {
+ dispatch({ type: SET_ERROR, error: response.errors[0].message });
+ }
+ setLoading(false);
+ }, [client, dispatch]);
+ const fetchGroups = async (environmentUri) => {
+ try {
+ const response = await client.query(
+ listEnvironmentGroups({
+ filter: Defaults.selectListFilter,
+ environmentUri
+ })
+ );
+ if (!response.errors) {
+ setGroupOptions(
+ response.data.listEnvironmentGroups.nodes.map((g) => ({
+ value: g.groupUri,
+ label: g.groupUri
+ }))
+ );
+ } else {
+ dispatch({ type: SET_ERROR, error: response.errors[0].message });
+ }
+ } catch (e) {
+ dispatch({ type: SET_ERROR, error: e.message });
+ }
+ };
+ useEffect(() => {
+ if (client) {
+ fetchEnvironments().catch((e) =>
+ dispatch({ type: SET_ERROR, error: e.message })
+ );
+ }
+ }, [client, dispatch, fetchEnvironments]);
+
+ if (loading) {
+ return ;
+ }
+
+ return (
+
+
+
+
+ option)}
+ onChange={(event, value) => {
+ setFieldValue('SamlAdminGroupName', '');
+ if (value && value.environmentUri) {
+ setFieldValue('environment', value);
+ fetchGroups(value.environmentUri).catch((e) =>
+ dispatch({
+ type: SET_ERROR,
+ error: e.message
+ })
+ );
+ } else {
+ setFieldValue('environment', '');
+ setGroupOptions([]);
+ }
+ }}
+ renderInput={(params) => (
+
+ )}
+ />
+
+
+
+
+
+
+
+
+ option)}
+ onChange={(event, value) => {
+ if (value && value.value) {
+ setFieldValue('SamlAdminGroupName', value.value);
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
+ {groupOptions.length > 0 ? (
+
+ ) : (
+
+ )}
+
+ )}
+ />
+
+
+
+ );
+};
+
+EnvironmentTeamDropdown.propTypes = {
+ setFieldValue: PropTypes.func.isRequired,
+ handleChange: PropTypes.func.isRequired,
+ values: PropTypes.object.isRequired,
+ touched: PropTypes.object.isRequired,
+ errors: PropTypes.object.isRequired
+};
diff --git a/frontend/src/modules/Shared/EnvironmentGroupSelect/index.js b/frontend/src/modules/Shared/EnvironmentGroupSelect/index.js
new file mode 100644
index 000000000..c89c2caf3
--- /dev/null
+++ b/frontend/src/modules/Shared/EnvironmentGroupSelect/index.js
@@ -0,0 +1,2 @@
+export * from './EnvironmentTeamDatasetsDropdown';
+export * from './EnvironmentTeamDropdown';
diff --git a/frontend/src/modules/Shared/Shares/ShareEditForm.js b/frontend/src/modules/Shared/Shares/ShareEditForm.js
index e357ece47..5ad742a53 100644
--- a/frontend/src/modules/Shared/Shares/ShareEditForm.js
+++ b/frontend/src/modules/Shared/Shares/ShareEditForm.js
@@ -12,7 +12,7 @@ import {
TextField,
Typography
} from '@mui/material';
-import { Defaults, Pager, ShareStatus } from '../../../design';
+import { Defaults, Pager, ShareHealthStatus, ShareStatus } from 'design';
import SendIcon from '@mui/icons-material/Send';
import React, { useCallback, useEffect, useState } from 'react';
import {
@@ -50,7 +50,10 @@ const ItemRow = (props) => {
item.status === 'Share_Failed'
)
return 'Delete';
- if (item.status === 'Share_Succeeded' || item.status === 'Revoke_Failed')
+ if (
+ (item.status === 'Share_Succeeded' || item.status === 'Revoke_Failed') &&
+ item.healthStatus !== 'PendingReApply'
+ )
return 'Revoke';
return 'Nothing';
};
@@ -135,6 +138,17 @@ const ItemRow = (props) => {
{item.status ? : 'Not requested'}
+
+ {item.status ? (
+
+ ) : (
+ 'Not requested'
+ )}
+
{(shareStatus === 'Draft' ||
shareStatus === 'Processed' ||
shareStatus === 'Rejected' ||
@@ -173,7 +187,7 @@ const ItemRow = (props) => {
)}
{possibleAction === 'Nothing' && (
- Wait until this item is processed
+ Wait until this item is processed and/or re-apply task is complete
)}
@@ -376,6 +390,7 @@ export const ShareEditForm = (props) => {
Type
Name
Status
+ Health Status
{(shareStatus === 'Draft' ||
shareStatus === 'Processed' ||
shareStatus === 'Rejected' ||
diff --git a/frontend/src/modules/Shared/index.js b/frontend/src/modules/Shared/index.js
index ccffdd8d7..04647f029 100644
--- a/frontend/src/modules/Shared/index.js
+++ b/frontend/src/modules/Shared/index.js
@@ -1,3 +1,4 @@
export * from './Comments';
+export * from './EnvironmentGroupSelect';
export * from './KeyValueTags';
export * from './Stack';
diff --git a/frontend/src/modules/Shares/components/NavigateShareViewModal.js b/frontend/src/modules/Shares/components/NavigateShareViewModal.js
new file mode 100644
index 000000000..7f3effd2b
--- /dev/null
+++ b/frontend/src/modules/Shares/components/NavigateShareViewModal.js
@@ -0,0 +1,60 @@
+import { Box, Dialog, Divider, Typography, Button } from '@mui/material';
+import PropTypes from 'prop-types';
+import { Link as RouterLink } from 'react-router-dom';
+
+export const NavigateShareViewModal = (props) => {
+ const { dataset, onApply, onClose, open, ...other } = props;
+
+ return (
+
+ );
+};
+
+NavigateShareViewModal.propTypes = {
+ shares: PropTypes.array.isRequired,
+ dataset: PropTypes.object.isRequired,
+ onApply: PropTypes.func,
+ onClose: PropTypes.func,
+ open: PropTypes.bool.isRequired
+};
diff --git a/frontend/src/modules/Shares/components/ShareBoxList.js b/frontend/src/modules/Shares/components/ShareBoxList.js
index d795141ba..063a7524d 100644
--- a/frontend/src/modules/Shares/components/ShareBoxList.js
+++ b/frontend/src/modules/Shares/components/ShareBoxList.js
@@ -28,6 +28,7 @@ import { getShareRequestsFromMe, listOwnedDatasets } from '../services';
import { ShareBoxListItem } from './ShareBoxListItem';
import { ShareObjectSelectorModal } from './ShareObjectSelectorModal';
+import { NavigateShareViewModal } from './NavigateShareViewModal';
import { ShareStatusList } from '../constants';
import { RefreshRounded } from '@mui/icons-material';
import { reApplyShareObjectItemsOnDataset } from '../services/reApplyShareObjectItemsOnDataset';
@@ -54,6 +55,8 @@ export const ShareBoxList = (props) => {
const [datasets, setDatasets] = useState([]);
const [isVerifyObjectItemsModalOpen, setIsVerifyObjectItemsModalOpen] =
useState(false);
+ const [isNavigateShareViewModalOpen, setIsNavigateShareViewModalOpen] =
+ useState(false);
const statusOptions = ShareStatusList;
const { enqueueSnackbar } = useSnackbar();
@@ -62,6 +65,13 @@ export const ShareBoxList = (props) => {
};
const handleVerifyObjectItemsModalClose = () => {
setIsVerifyObjectItemsModalOpen(false);
+ if (dataset) {
+ setIsNavigateShareViewModalOpen(true);
+ }
+ };
+
+ const handleNavigateShareViewModalClose = () => {
+ setIsNavigateShareViewModalOpen(false);
};
const handlePageChange = async (event, value) => {
@@ -571,6 +581,14 @@ export const ShareBoxList = (props) => {
open={isVerifyObjectItemsModalOpen}
/>
)}
+ {isNavigateShareViewModalOpen && (
+
+ )}
>
);
};
diff --git a/frontend/src/modules/Shares/components/index.js b/frontend/src/modules/Shares/components/index.js
index e00ca86a8..86373e838 100644
--- a/frontend/src/modules/Shares/components/index.js
+++ b/frontend/src/modules/Shares/components/index.js
@@ -6,3 +6,4 @@ export * from './ShareUpdateReject';
export * from './ShareUpdateRequest';
export * from './ShareItemsSelectorModal';
export * from './ShareObjectSelectorModal';
+export * from './NavigateShareViewModal';
diff --git a/frontend/src/modules/Shares/views/ShareView.js b/frontend/src/modules/Shares/views/ShareView.js
index 16a5c311f..7bb05feda 100644
--- a/frontend/src/modules/Shares/views/ShareView.js
+++ b/frontend/src/modules/Shares/views/ShareView.js
@@ -6,10 +6,7 @@ import {
DeleteOutlined,
RefreshRounded
} from '@mui/icons-material';
-import VerifiedUserIcon from '@mui/icons-material/VerifiedUser';
-import GppBadIcon from '@mui/icons-material/GppBad';
import SecurityIcon from '@mui/icons-material/Security';
-import PendingIcon from '@mui/icons-material/Pending';
import { LoadingButton } from '@mui/lab';
import {
Box,
@@ -49,6 +46,7 @@ import {
PencilAltIcon,
Scrollbar,
ShareStatus,
+ ShareHealthStatus,
TextAvatar,
useSettings
} from 'design';
@@ -489,33 +487,11 @@ export function SharedItem(props) {
)}
-
- {item.healthStatus === 'Unhealthy' ? (
-
{item.healthStatus}}>
-
-
- ) : item.healthStatus === 'Healthy' ? (
-
{item.healthStatus}}>
-
-
- ) : (
-
{item.healthStatus || 'Undefined'}
- }
- >
-
-
- )}
-
- {(item.lastVerificationTime &&
- item.lastVerificationTime.substring(
- 0,
- item.lastVerificationTime.indexOf('.')
- )) ||
- ''}
-
-
+
{item.healthMessage ? (
diff --git a/frontend/src/modules/Worksheets/views/WorksheetCreateForm.js b/frontend/src/modules/Worksheets/views/WorksheetCreateForm.js
index 0a307559f..fab25da6f 100644
--- a/frontend/src/modules/Worksheets/views/WorksheetCreateForm.js
+++ b/frontend/src/modules/Worksheets/views/WorksheetCreateForm.js
@@ -1,5 +1,6 @@
import { LoadingButton } from '@mui/lab';
import {
+ Autocomplete,
Box,
Breadcrumbs,
Button,
@@ -10,7 +11,6 @@ import {
FormHelperText,
Grid,
Link,
- MenuItem,
TextField,
Typography
} from '@mui/material';
@@ -36,9 +36,6 @@ const WorksheetCreateForm = (props) => {
const client = useClient();
const groups = useGroups();
const { settings } = useSettings();
- const groupOptions = groups
- ? groups.map((g) => ({ value: g, label: g }))
- : [];
async function submit(values, setStatus, setSubmitting, setErrors) {
try {
@@ -46,7 +43,7 @@ const WorksheetCreateForm = (props) => {
createWorksheet({
label: values.label,
description: values.description,
- SamlAdminGroupName: values.SamlGroupName,
+ SamlAdminGroupName: values.SamlAdminGroupName,
tags: values.tags
})
);
@@ -141,7 +138,7 @@ const WorksheetCreateForm = (props) => {
initialValues={{
label: '',
description: '',
- SamlGroupName: '',
+ SamlAdminGroupName: '',
tags: []
}}
validationSchema={Yup.object().shape({
@@ -149,7 +146,7 @@ const WorksheetCreateForm = (props) => {
.max(255)
.required('*Worksheet name is required'),
description: Yup.string().max(5000),
- SamlGroupName: Yup.string()
+ SamlAdminGroupName: Yup.string()
.max(255)
.required('* Team is required'),
tags: Yup.array().nullable()
@@ -224,27 +221,37 @@ const WorksheetCreateForm = (props) => {
- {
+ if (value) {
+ setFieldValue('SamlAdminGroupName', value);
+ } else {
+ setFieldValue('SamlAdminGroupName', '');
+ }
+ }}
+ inputValue={values.SamlAdminGroupName}
+ renderInput={(params) => (
+
)}
- helperText={
- touched.SamlGroupName && errors.SamlGroupName
- }
- label="Team"
- name="SamlGroupName"
- onChange={handleChange}
- select
- value={values.SamlGroupName}
- variant="outlined"
- >
- {groupOptions.map((group) => (
-
- ))}
-
+ />
diff --git a/tests/modules/s3_datasets_shares/test_share.py b/tests/modules/s3_datasets_shares/test_share.py
index 22ca395ee..988b14f5e 100644
--- a/tests/modules/s3_datasets_shares/test_share.py
+++ b/tests/modules/s3_datasets_shares/test_share.py
@@ -317,6 +317,15 @@ def share3_processed(
if delete_share_object_response.data.deleteShareObject == True:
return
+ # Revert healthStatus back to healthy
+ with db.scoped_session() as session:
+ ShareStatusRepository.update_share_item_health_status_batch(
+ session=session,
+ share_uri=share3.shareUri,
+ old_status=ShareItemHealthStatus.PendingReApply.value,
+ new_status=ShareItemHealthStatus.Healthy.value,
+ )
+
# Given share item in shared states
get_share_object_response = get_share_object(
client=client,
@@ -1522,7 +1531,7 @@ def test_reapply_items_share_request(db, client, user, group, share3_processed,
client=client, user=user, group=group, shareUri=share3_processed.shareUri, reapply_items_uris=reapply_items_uris
)
- # Then share item health Status changes to PendingVerify
+ # Then share item health Status changes to PendingReApply
get_share_object_response = get_share_object(
client=client,
user=user,