diff --git a/datahub-web-react/src/app/entity/shared/EntityDropdown/UpdateDeprecationModal.tsx b/datahub-web-react/src/app/entity/shared/EntityDropdown/UpdateDeprecationModal.tsx index db91ec888f292..512735e60b2c3 100644 --- a/datahub-web-react/src/app/entity/shared/EntityDropdown/UpdateDeprecationModal.tsx +++ b/datahub-web-react/src/app/entity/shared/EntityDropdown/UpdateDeprecationModal.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Button, DatePicker, Form, Input, message, Modal } from 'antd'; import { useBatchUpdateDeprecationMutation } from '../../../../graphql/mutations.generated'; +import { handleBatchError } from '../utils'; type Props = { urns: string[]; @@ -35,7 +36,12 @@ export const UpdateDeprecationModal = ({ urns, onClose, refetch }: Props) => { } catch (e: unknown) { message.destroy(); if (e instanceof Error) { - message.error({ content: `Failed to update Deprecation: \n ${e.message || ''}`, duration: 2 }); + message.error( + handleBatchError(urns, e, { + content: `Failed to update Deprecation: \n ${e.message || ''}`, + duration: 2, + }), + ); } } refetch?.(); diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeleteDropdown.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeleteDropdown.tsx index 5a13c84037313..c79d43a21e45f 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeleteDropdown.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeleteDropdown.tsx @@ -2,6 +2,7 @@ import { message, Modal } from 'antd'; import React from 'react'; import { useBatchUpdateSoftDeletedMutation } from '../../../../../../../graphql/mutations.generated'; import ActionDropdown from './ActionDropdown'; +import { handleBatchError } from '../../../../utils'; type Props = { urns: Array; @@ -30,7 +31,12 @@ export default function DeleteDropdown({ urns, disabled = false, refetch }: Prop }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to delete assets: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { + content: `Failed to delete assets: \n ${e.message || ''}`, + duration: 3, + }), + ); }); }; diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeprecationDropdown.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeprecationDropdown.tsx index 43e44eee42119..981a1a8a0d0f8 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeprecationDropdown.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/action/DeprecationDropdown.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import { useBatchUpdateDeprecationMutation } from '../../../../../../../graphql/mutations.generated'; import { UpdateDeprecationModal } from '../../../../EntityDropdown/UpdateDeprecationModal'; import ActionDropdown from './ActionDropdown'; +import { handleBatchError } from '../../../../utils'; type Props = { urns: Array; @@ -32,10 +33,12 @@ export default function DeprecationDropdown({ urns, disabled = false, refetch }: }) .catch((e) => { message.destroy(); - message.error({ - content: `Failed to mark assets as un-deprecated: \n ${e.message || ''}`, - duration: 3, - }); + message.error( + handleBatchError(urns, e, { + content: `Failed to mark assets as un-deprecated: \n ${e.message || ''}`, + duration: 3, + }), + ); }); }; diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/action/DomainsDropdown.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/action/DomainsDropdown.tsx index 7ce8222aad379..ad9c58c67de88 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/action/DomainsDropdown.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/action/DomainsDropdown.tsx @@ -3,6 +3,7 @@ import React, { useState } from 'react'; import { useBatchSetDomainMutation } from '../../../../../../../graphql/mutations.generated'; import { SetDomainModal } from '../../../../containers/profile/sidebar/Domain/SetDomainModal'; import ActionDropdown from './ActionDropdown'; +import { handleBatchError } from '../../../../utils'; type Props = { urns: Array; @@ -31,7 +32,12 @@ export default function DomainsDropdown({ urns, disabled = false, refetch }: Pro }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to remove assets from Domain: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { + content: `Failed to remove assets from Domain: \n ${e.message || ''}`, + duration: 3, + }), + ); }); }; diff --git a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Domain/SetDomainModal.tsx b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Domain/SetDomainModal.tsx index 39014a28581a8..1fce947640702 100644 --- a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Domain/SetDomainModal.tsx +++ b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Domain/SetDomainModal.tsx @@ -9,6 +9,7 @@ import { useEntityRegistry } from '../../../../../../useEntityRegistry'; import { useEnterKeyListener } from '../../../../../../shared/useEnterKeyListener'; import { useGetRecommendations } from '../../../../../../shared/recommendation'; import { DomainLabel } from '../../../../../../shared/DomainLabel'; +import { handleBatchError } from '../../../../utils'; type Props = { urns: string[]; @@ -135,7 +136,12 @@ export const SetDomainModal = ({ urns, onCloseModal, refetch, defaultValue, onOk }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to add assets to Domain: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { + content: `Failed to add assets to Domain: \n ${e.message || ''}`, + duration: 3, + }), + ); }); }; diff --git a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Ownership/EditOwnersModal.tsx b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Ownership/EditOwnersModal.tsx index 3c644b5ebefb3..2bf8e48df3516 100644 --- a/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Ownership/EditOwnersModal.tsx +++ b/datahub-web-react/src/app/entity/shared/containers/profile/sidebar/Ownership/EditOwnersModal.tsx @@ -13,6 +13,7 @@ import { import { useGetSearchResultsLazyQuery } from '../../../../../../../graphql/search.generated'; import { useGetRecommendations } from '../../../../../../shared/recommendation'; import { OwnerLabel } from '../../../../../../shared/OwnerLabel'; +import { handleBatchError } from '../../../../utils'; const SelectInput = styled(Select)` width: 480px; @@ -244,7 +245,12 @@ export const EditOwnersModal = ({ } catch (e: unknown) { message.destroy(); if (e instanceof Error) { - message.error({ content: `Failed to add owners: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { + content: `Failed to add owners: \n ${e.message || ''}`, + duration: 3, + }), + ); } } finally { refetch?.(); @@ -267,7 +273,12 @@ export const EditOwnersModal = ({ } catch (e: unknown) { message.destroy(); if (e instanceof Error) { - message.error({ content: `Failed to remove owners: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { + content: `Failed to remove owners: \n ${e.message || ''}`, + duration: 3, + }), + ); } } finally { refetch?.(); diff --git a/datahub-web-react/src/app/entity/shared/entity/EntityActions.tsx b/datahub-web-react/src/app/entity/shared/entity/EntityActions.tsx index 0599020101ae7..2c06e549b218d 100644 --- a/datahub-web-react/src/app/entity/shared/entity/EntityActions.tsx +++ b/datahub-web-react/src/app/entity/shared/entity/EntityActions.tsx @@ -5,6 +5,7 @@ import { SearchSelectModal } from '../components/styled/search/SearchSelectModal import { useEntityRegistry } from '../../../useEntityRegistry'; import { EntityCapabilityType } from '../../Entity'; import { useBatchAddTermsMutation, useBatchSetDomainMutation } from '../../../../graphql/mutations.generated'; +import { handleBatchError } from '../utils'; export enum EntityActionItem { /** @@ -59,7 +60,12 @@ function EntityActions(props: Props) { }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to add glossary term: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(entityUrns, e, { + content: `Failed to add glossary term: \n ${e.message || ''}`, + duration: 3, + }), + ); }); }; @@ -90,7 +96,12 @@ function EntityActions(props: Props) { }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to add assets to Domain: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(entityUrns, e, { + content: `Failed to add assets to Domain: \n ${e.message || ''}`, + duration: 3, + }), + ); }); }; diff --git a/datahub-web-react/src/app/entity/shared/utils.ts b/datahub-web-react/src/app/entity/shared/utils.ts index 32307d6ea4900..a30f3d4cfb14b 100644 --- a/datahub-web-react/src/app/entity/shared/utils.ts +++ b/datahub-web-react/src/app/entity/shared/utils.ts @@ -124,3 +124,24 @@ export const getMatchPrioritizingPrimary = ( return fromQueryGetBestMatch(matchesThatShouldBeShownOnFE, query); }; + +function getGraphqlErrorCode(e) { + if (e.graphQLErrors && e.graphQLErrors.length) { + const firstError = e.graphQLErrors[0]; + const { extensions } = firstError; + const errorCode = extensions && (extensions.code as number); + return errorCode; + } + return undefined; +} + +export const handleBatchError = (urns, e, defaultMessage) => { + if (urns.length > 1 && getGraphqlErrorCode(e) === 403) { + return { + content: + 'Your bulk edit selection included entities that you are unauthorized to update. The bulk edit being performed will not be saved.', + duration: 3, + }; + } + return defaultMessage; +}; diff --git a/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx b/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx index b71419e1faf7c..01e11ceb9a738 100644 --- a/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx +++ b/datahub-web-react/src/app/shared/tags/AddTagsTermsModal.tsx @@ -16,7 +16,7 @@ import GlossaryBrowser from '../../glossary/GlossaryBrowser/GlossaryBrowser'; import ClickOutside from '../ClickOutside'; import { useEntityRegistry } from '../../useEntityRegistry'; import { useGetRecommendations } from '../recommendation'; -import { FORBIDDEN_URN_CHARS_REGEX } from '../../entity/shared/utils'; +import { FORBIDDEN_URN_CHARS_REGEX, handleBatchError } from '../../entity/shared/utils'; import { TagTermLabel } from './TagTermLabel'; import { ENTER_KEY_CODE } from '../constants'; @@ -267,7 +267,9 @@ export default function EditTagTermsModal({ }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to add: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { content: `Failed to add: \n ${e.message || ''}`, duration: 3 }), + ); }) .finally(() => { setDisableAction(false); @@ -295,7 +297,9 @@ export default function EditTagTermsModal({ }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to add: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { content: `Failed to add: \n ${e.message || ''}`, duration: 3 }), + ); }) .finally(() => { setDisableAction(false); @@ -323,7 +327,9 @@ export default function EditTagTermsModal({ }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }), + ); }) .finally(() => { setDisableAction(false); @@ -351,7 +357,9 @@ export default function EditTagTermsModal({ }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(urns, e, { content: `Failed to remove: \n ${e.message || ''}`, duration: 3 }), + ); }) .finally(() => { setDisableAction(false); diff --git a/datahub-web-react/src/app/shared/tags/CreateTagModal.tsx b/datahub-web-react/src/app/shared/tags/CreateTagModal.tsx index bcc254f10da7b..874dd3313f8de 100644 --- a/datahub-web-react/src/app/shared/tags/CreateTagModal.tsx +++ b/datahub-web-react/src/app/shared/tags/CreateTagModal.tsx @@ -5,6 +5,7 @@ import { useBatchAddTagsMutation } from '../../../graphql/mutations.generated'; import { useCreateTagMutation } from '../../../graphql/tag.generated'; import { ResourceRefInput } from '../../../types.generated'; import { useEnterKeyListener } from '../useEnterKeyListener'; +import { handleBatchError } from '../../entity/shared/utils'; type CreateTagModalProps = { visible: boolean; @@ -50,7 +51,12 @@ export default function CreateTagModal({ onClose, onBack, visible, tagName, reso }) .catch((e) => { message.destroy(); - message.error({ content: `Failed to add tag: \n ${e.message || ''}`, duration: 3 }); + message.error( + handleBatchError(resources, e, { + content: `Failed to add tag: \n ${e.message || ''}`, + duration: 3, + }), + ); onClose(); }) .finally(() => {