diff --git a/.changeset/dull-ants-pull.md b/.changeset/dull-ants-pull.md new file mode 100644 index 0000000000..66adf0898f --- /dev/null +++ b/.changeset/dull-ants-pull.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-react': minor +--- + +Migrated `UnregisterEntityDialog` from Material UI to Backstage UI components. diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 4d137e524f..584fe79014 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -74,6 +74,7 @@ "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-permission-react": "workspace:^", "@backstage/types": "workspace:^", + "@backstage/ui": "workspace:^", "@backstage/version-bridge": "workspace:^", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx index 54086a55a5..d1e13f4ef1 100644 --- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx +++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.tsx @@ -14,256 +14,302 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; +import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { EntityRefLink } from '../EntityRefLink'; -import Box from '@material-ui/core/Box'; -import Button from '@material-ui/core/Button'; -import Dialog from '@material-ui/core/Dialog'; -import DialogActions from '@material-ui/core/DialogActions'; -import DialogContent from '@material-ui/core/DialogContent'; -import DialogContentText from '@material-ui/core/DialogContentText'; -import DialogTitle from '@material-ui/core/DialogTitle'; -import Divider from '@material-ui/core/Divider'; import { makeStyles } from '@material-ui/core/styles'; -import Alert from '@material-ui/lab/Alert'; -import { useCallback, useState } from 'react'; -import { useUnregisterEntityDialogState } from './useUnregisterEntityDialogState'; +import { ReactNode, useCallback, useState } from 'react'; +import { + UseUnregisterEntityDialogState, + useUnregisterEntityDialogState, +} from './useUnregisterEntityDialogState'; import { alertApiRef, configApiRef, useApi } from '@backstage/core-plugin-api'; import { Progress, ResponseErrorPanel } from '@backstage/core-components'; import { assertError } from '@backstage/errors'; import { catalogReactTranslationRef } from '../../translation'; import { useTranslationRef } from '@backstage/core-plugin-api/alpha'; +import { + Accordion, + AccordionPanel, + AccordionTrigger, + Alert, + Box, + Button, + Dialog, + DialogBody, + DialogFooter, + DialogHeader, + Text, +} from '@backstage/ui'; const useStyles = makeStyles({ - advancedButton: { - fontSize: '0.7em', - }, - dialogActions: { - display: 'inline-block', + bodyContent: { + overflowWrap: 'break-word', }, }); -const Contents = ({ - entity, - onConfirm, - onClose, -}: { - entity: Entity; - onConfirm: () => any; - onClose: () => any; -}) => { +type DialogHandlers = { + state: UseUnregisterEntityDialogState; + busyAction: 'unregister' | 'delete' | null; + onUnregister: () => Promise; + onDelete: () => Promise; + onClose: () => void; +}; + +function useUnregisterDialogHandlers( + entity: Entity, + onConfirm: () => void, + onClose: () => void, +): DialogHandlers { const alertApi = useApi(alertApiRef); - const configApi = useApi(configApiRef); - const classes = useStyles(); const state = useUnregisterEntityDialogState(entity); - const [showDelete, setShowDelete] = useState(false); - const [busy, setBusy] = useState(false); - const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + const [busyAction, setBusyAction] = useState<'unregister' | 'delete' | null>( + null, + ); const { t } = useTranslationRef(catalogReactTranslationRef); - const onUnregister = useCallback( - async function onUnregisterFn() { - if ('unregisterLocation' in state) { - setBusy(true); - try { - await state.unregisterLocation(); - onConfirm(); - } catch (err) { - assertError(err); - alertApi.post({ message: err.message }); - } finally { - setBusy(false); - } + const onUnregister = useCallback(async () => { + if ('unregisterLocation' in state) { + setBusyAction('unregister'); + try { + await state.unregisterLocation(); + onConfirm(); + } catch (err) { + assertError(err); + alertApi.post({ message: err.message }); + } finally { + setBusyAction(null); } - }, - [alertApi, onConfirm, state], - ); + } + }, [alertApi, onConfirm, state]); - const onDelete = useCallback( - async function onDeleteFn() { - if ('deleteEntity' in state) { - setBusy(true); - try { - await state.deleteEntity(); - const entityName = entity.metadata.title ?? entity.metadata.name; - onConfirm(); - alertApi.post({ - message: t('unregisterEntityDialog.deleteEntitySuccessMessage', { - entityName, - }), - severity: 'success', - display: 'transient', - }); - } catch (err) { - assertError(err); - alertApi.post({ message: err.message }); - } finally { - setBusy(false); - } + const onDelete = useCallback(async () => { + if ('deleteEntity' in state) { + setBusyAction('delete'); + try { + await state.deleteEntity(); + const entityName = entity.metadata.title ?? entity.metadata.name; + onConfirm(); + alertApi.post({ + message: t('unregisterEntityDialog.deleteEntitySuccessMessage', { + entityName, + }), + severity: 'success', + display: 'transient', + }); + } catch (err) { + assertError(err); + alertApi.post({ message: err.message }); + } finally { + setBusyAction(null); } - }, - [alertApi, onConfirm, state, entity, t], - ); + } + }, [alertApi, onConfirm, state, entity, t]); - const DialogActionsPanel = () => ( - - - - ); + return { state, busyAction, onUnregister, onDelete, onClose }; +} - if (state.type === 'loading') { - return ; - } +function AdvancedDeleteAccordion({ + triggerTitle, + description, + onDelete, + busyAction, +}: { + triggerTitle: string; + description: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); - if (state.type === 'error') { - return ; - } - - if (state.type === 'bootstrap') { - return ( - <> - - {t('unregisterEntityDialog.bootstrapState.title', { - appTitle, - location: state.location, - })} - - - - {!showDelete && ( - <> - - - - )} - - {showDelete && ( - <> - - {t('unregisterEntityDialog.bootstrapState.advancedDescription')} - - - - - )} - - - ); - } - - if (state.type === 'only-delete') { - return ( - <> - - {t('unregisterEntityDialog.onlyDeleteStateTitle')} - - - - - ); - } - - if (state.type === 'unregister') { - return ( - <> - - {t('unregisterEntityDialog.unregisterState.title')} - - - {state.colocatedEntities.map(e => ( -
  • - -
  • - ))} -
    - - {t('unregisterEntityDialog.unregisterState.subTitle')} - - -
  • {state.location}
  • -
    - - {t('unregisterEntityDialog.unregisterState.description', { - appTitle, - })} - - - - {!showDelete && ( - - - - )} - - - {showDelete && ( - <> - - - - - {t('unregisterEntityDialog.unregisterState.advancedDescription')} - + return ( + + + + + {description} + - - )} - - ); - } + + + + + ); +} + +function BootstrapBody({ + location, + appTitle, + onDelete, + busyAction, +}: { + location: string; + appTitle: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); return ( - - {t('unregisterEntityDialog.errorStateTitle')} - + <> + + + ); -}; +} + +function OnlyDeleteBody() { + const { t } = useTranslationRef(catalogReactTranslationRef); + + return {t('unregisterEntityDialog.onlyDeleteStateTitle')}; +} + +function UnregisterBody({ + state, + appTitle, + onDelete, + busyAction, +}: { + state: Extract; + appTitle: string; + onDelete: () => void; + busyAction: 'unregister' | 'delete' | null; +}) { + const { t } = useTranslationRef(catalogReactTranslationRef); + + return ( + <> + {t('unregisterEntityDialog.unregisterState.title')} +
      + {state.colocatedEntities.map((e: CompoundEntityRef) => ( +
    • + +
    • + ))} +
    + {t('unregisterEntityDialog.unregisterState.subTitle')} +
      +
    • {state.location}
    • +
    + + {t('unregisterEntityDialog.unregisterState.description', { + appTitle, + })} + + + + ); +} + +function useDialogContent( + handlers: DialogHandlers, + appTitle: string, +): { body: ReactNode; actionButton: ReactNode | null } { + const { t } = useTranslationRef(catalogReactTranslationRef); + const { state, busyAction, onUnregister, onDelete } = handlers; + + switch (state.type) { + case 'loading': + return { body: , actionButton: null }; + case 'error': + return { + body: , + actionButton: null, + }; + case 'bootstrap': + return { + body: ( + + ), + actionButton: null, + }; + case 'only-delete': + return { + body: , + actionButton: ( + + ), + }; + case 'unregister': + return { + body: ( + + ), + actionButton: ( + + ), + }; + default: + return { + body: ( + + ), + actionButton: null, + }; + } +} /** @public */ export type UnregisterEntityDialogProps = { @@ -273,18 +319,54 @@ export type UnregisterEntityDialogProps = { entity: Entity; }; +function DialogContents({ + entity, + onConfirm, + onClose, +}: { + entity: Entity; + onConfirm: () => void; + onClose: () => void; +}) { + const classes = useStyles(); + const { t } = useTranslationRef(catalogReactTranslationRef); + const configApi = useApi(configApiRef); + const appTitle = configApi.getOptionalString('app.title') ?? 'Backstage'; + + const handlers = useUnregisterDialogHandlers(entity, onConfirm, onClose); + const { body, actionButton } = useDialogContent(handlers, appTitle); + + return ( + <> + {t('unregisterEntityDialog.title')} + {body} + + + {actionButton} + + + ); +} + /** @public */ export const UnregisterEntityDialog = (props: UnregisterEntityDialogProps) => { const { open, onConfirm, onClose, entity } = props; - const { t } = useTranslationRef(catalogReactTranslationRef); + return ( - - - {t('unregisterEntityDialog.title')} - - - - + !isOpen && onClose()} + width={600} + > + {open && ( + + )} ); }; diff --git a/yarn.lock b/yarn.lock index d5683239f0..465548e3db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5400,6 +5400,7 @@ __metadata: "@backstage/plugin-scaffolder-common": "workspace:^" "@backstage/test-utils": "workspace:^" "@backstage/types": "workspace:^" + "@backstage/ui": "workspace:^" "@backstage/version-bridge": "workspace:^" "@material-ui/core": "npm:^4.12.2" "@material-ui/icons": "npm:^4.9.1"