-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into feat/verifyAccessToCreatOrgFrontend
- Loading branch information
Showing
43 changed files
with
895 additions
and
131 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
...ent/features/appPublish/components/ConfirmUndeployDialog/ConfirmUndeployDialog.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
.confirmUndeployButton { | ||
margin-top: var(--fds-spacing-3); | ||
} | ||
|
||
.errorContainer { | ||
margin-top: var(--fds-spacing-2); | ||
} | ||
|
||
.errorMessage { | ||
display: inline; | ||
} |
123 changes: 123 additions & 0 deletions
123
...pment/features/appPublish/components/ConfirmUndeployDialog/ConfirmUndeployDialog.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
import { renderWithProviders } from '../../../../test/testUtils'; | ||
import { APP_DEVELOPMENT_BASENAME } from 'app-shared/constants'; | ||
import { app, org } from '@studio/testing/testids'; | ||
import React from 'react'; | ||
import { textMock } from '@studio/testing/mocks/i18nMock'; | ||
import { screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { ConfirmUndeployDialog } from './ConfirmUndeployDialog'; | ||
import { useUndeployMutation } from '../../../../hooks/mutations/useUndeployMutation'; | ||
|
||
jest.mock('../../../../hooks/mutations/useUndeployMutation'); | ||
|
||
describe('ConfirmUndeployDialog', () => { | ||
it('should provide a input field to confirm the app to undeploy and button is disabled', async () => { | ||
renderConfirmUndeployDialog(); | ||
await openDialog(); | ||
|
||
const confirmTextField = getConfirmTextField(); | ||
const undeployButton = getUndeployButton(); | ||
|
||
expect(confirmTextField).toBeInTheDocument(); | ||
expect(undeployButton).toBeDisabled(); | ||
}); | ||
|
||
it('should enable undeploy button when confirm text field matches the app name', async () => { | ||
const user = userEvent.setup(); | ||
renderConfirmUndeployDialog(); | ||
await openDialog(); | ||
|
||
const confirmTextField = getConfirmTextField(); | ||
await user.type(confirmTextField, app); | ||
expect(confirmTextField).toHaveValue(app); | ||
|
||
const undeployButton = getUndeployButton(); | ||
expect(undeployButton).toBeEnabled(); | ||
}); | ||
|
||
it('should not be case-sensitive when confirming the app-name', async () => { | ||
const user = userEvent.setup(); | ||
renderConfirmUndeployDialog(); | ||
await openDialog(); | ||
|
||
const appNameInUpperCase = app.toUpperCase(); | ||
|
||
const confirmTextField = getConfirmTextField(); | ||
await user.type(confirmTextField, appNameInUpperCase); | ||
expect(confirmTextField).toHaveValue(appNameInUpperCase); | ||
|
||
const undeployButton = getUndeployButton(); | ||
expect(undeployButton).toBeEnabled(); | ||
}); | ||
|
||
it('should trigger undeploy when undeploy button is clicked', async () => { | ||
const user = userEvent.setup(); | ||
renderConfirmUndeployDialog(); | ||
await openDialog(); | ||
|
||
const mutateFunctionMock = jest.fn(); | ||
(useUndeployMutation as jest.Mock).mockReturnValue({ | ||
mutate: mutateFunctionMock, | ||
}); | ||
|
||
const confirmTextField = getConfirmTextField(); | ||
await user.type(confirmTextField, app); | ||
await user.click(getUndeployButton()); | ||
|
||
expect(mutateFunctionMock).toBeCalledTimes(1); | ||
expect(mutateFunctionMock).toHaveBeenCalledWith( | ||
expect.objectContaining({ environment: 'unit-test-env' }), | ||
expect.anything(), | ||
); | ||
}); | ||
|
||
it('should display an error alert when the undeploy mutation fails', async () => { | ||
const user = userEvent.setup(); | ||
renderConfirmUndeployDialog(); | ||
await openDialog(); | ||
|
||
const errorMessageKey = 'app_deployment.error_unknown.message'; | ||
const mutateFunctionMock = jest.fn((_, { onError }) => onError()); | ||
|
||
(useUndeployMutation as jest.Mock).mockReturnValue({ | ||
mutate: mutateFunctionMock, | ||
}); | ||
|
||
const confirmTextField = getConfirmTextField(); | ||
await user.type(confirmTextField, app); | ||
|
||
const undeployButton = getUndeployButton(); | ||
await user.click(undeployButton); | ||
|
||
expect(mutateFunctionMock).toBeCalledTimes(1); | ||
expect(mutateFunctionMock).toHaveBeenCalledWith( | ||
expect.objectContaining({ environment: 'unit-test-env' }), | ||
expect.anything(), | ||
); | ||
|
||
const alertMessage = screen.getByText(textMock(errorMessageKey)); | ||
expect(alertMessage).toBeInTheDocument(); | ||
}); | ||
}); | ||
|
||
async function openDialog(): Promise<void> { | ||
const user = userEvent.setup(); | ||
const button = screen.getByRole('button', { name: textMock('app_deployment.undeploy_button') }); | ||
await user.click(button); | ||
} | ||
|
||
function getConfirmTextField(): HTMLInputElement | null { | ||
return screen.getByLabelText(textMock('app_deployment.undeploy_confirmation_input_label')); | ||
} | ||
|
||
function getUndeployButton(): HTMLButtonElement | null { | ||
return screen.getByRole('button', { | ||
name: textMock('app_deployment.undeploy_confirmation_button'), | ||
}); | ||
} | ||
|
||
function renderConfirmUndeployDialog(environment: string = 'unit-test-env'): void { | ||
renderWithProviders(<ConfirmUndeployDialog environment={environment} />, { | ||
startUrl: `${APP_DEVELOPMENT_BASENAME}/${org}/${app}/deploy`, | ||
}); | ||
} |
101 changes: 101 additions & 0 deletions
101
...evelopment/features/appPublish/components/ConfirmUndeployDialog/ConfirmUndeployDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
import type { ReactElement } from 'react'; | ||
import React, { useRef, useState } from 'react'; | ||
import { | ||
StudioButton, | ||
StudioModal, | ||
StudioTextfield, | ||
StudioParagraph, | ||
StudioAlert, | ||
StudioLink, | ||
} from '@studio/components'; | ||
import { Trans, useTranslation } from 'react-i18next'; | ||
import classes from './ConfirmUndeployDialog.module.css'; | ||
import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; | ||
import { useUndeployMutation } from '../../../../hooks/mutations/useUndeployMutation'; | ||
|
||
type ConfirmUndeployDialogProps = { | ||
environment: string; | ||
}; | ||
export const ConfirmUndeployDialog = ({ | ||
environment, | ||
}: ConfirmUndeployDialogProps): ReactElement => { | ||
const { t } = useTranslation(); | ||
const { org, app: appName } = useStudioEnvironmentParams(); | ||
const dialogRef = useRef<HTMLDialogElement>(); | ||
const [isAppNameConfirmed, setIsAppNameConfirmed] = useState<boolean>(false); | ||
const [undeployError, setUndeployError] = useState<string | null>(null); | ||
const mutation = useUndeployMutation(org, appName); | ||
const onAppNameInputChange = (event: React.FormEvent<HTMLInputElement>): void => { | ||
setIsAppNameConfirmed(isAppNameConfirmedForDelete(event.currentTarget.value, appName)); | ||
}; | ||
|
||
const openDialog = () => dialogRef.current.showModal(); | ||
const closeDialog = () => dialogRef.current.close(); | ||
|
||
const onUndeployClicked = (): void => { | ||
mutation.mutate( | ||
{ | ||
environment, | ||
}, | ||
{ | ||
onSuccess: (): void => { | ||
setUndeployError(null); | ||
closeDialog(); | ||
}, | ||
onError: (): void => { | ||
setUndeployError('app_deployment.error_unknown.message'); | ||
}, | ||
}, | ||
); | ||
}; | ||
|
||
return ( | ||
<> | ||
<StudioButton size='sm' onClick={openDialog} variant='primary'> | ||
{t('app_deployment.undeploy_button')} | ||
</StudioButton> | ||
<StudioModal.Dialog | ||
closeButtonTitle={t('sync_header.close_local_changes_button')} | ||
heading={t('app_deployment.undeploy_confirmation_dialog_title')} | ||
ref={dialogRef} | ||
> | ||
<StudioParagraph spacing> | ||
{t('app_deployment.undeploy_confirmation_dialog_description')} | ||
</StudioParagraph> | ||
<StudioTextfield | ||
size='sm' | ||
label={t('app_deployment.undeploy_confirmation_input_label')} | ||
description={t('app_deployment.undeploy_confirmation_input_description', { | ||
appName, | ||
})} | ||
onChange={onAppNameInputChange} | ||
/> | ||
{undeployError && ( | ||
<StudioAlert severity='danger' className={classes.errorContainer}> | ||
<StudioParagraph size='sm'> | ||
<Trans | ||
i18nKey={undeployError} | ||
components={{ | ||
a: <StudioLink href='/contact'> </StudioLink>, | ||
}} | ||
/> | ||
</StudioParagraph> | ||
</StudioAlert> | ||
)} | ||
<StudioButton | ||
disabled={!isAppNameConfirmed} | ||
color='danger' | ||
size='sm' | ||
className={classes.confirmUndeployButton} | ||
onClick={onUndeployClicked} | ||
> | ||
{t('app_deployment.undeploy_confirmation_button')} | ||
</StudioButton> | ||
</StudioModal.Dialog> | ||
</> | ||
); | ||
}; | ||
|
||
function isAppNameConfirmedForDelete(userInputAppName: string, appNameToMatch: string): boolean { | ||
return userInputAppName.toLowerCase().includes(appNameToMatch.toLowerCase()); | ||
} |
1 change: 1 addition & 0 deletions
1
frontend/app-development/features/appPublish/components/ConfirmUndeployDialog/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { ConfirmUndeployDialog } from './ConfirmUndeployDialog'; |
18 changes: 18 additions & 0 deletions
18
...ent/features/appPublish/components/DeployMoreOptionsMenu/DeployMoreOptionsMenu.module.css
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
.listContainer { | ||
padding: 0; | ||
list-style: none; | ||
} | ||
|
||
.content { | ||
padding: 0; | ||
} | ||
|
||
.itemButton { | ||
justify-content: flex-start; | ||
} | ||
|
||
.trigger { | ||
position: absolute; | ||
top: var(--fds-spacing-3); | ||
right: var(--fds-spacing-3); | ||
} |
60 changes: 60 additions & 0 deletions
60
...pment/features/appPublish/components/DeployMoreOptionsMenu/DeployMoreOptionsMenu.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import React from 'react'; | ||
import { render, screen } from '@testing-library/react'; | ||
import userEvent from '@testing-library/user-event'; | ||
import { DeployMoreOptionsMenu } from './DeployMoreOptionsMenu'; | ||
import { textMock } from '@studio/testing/mocks/i18nMock'; | ||
|
||
describe('DeployMoreOptionsMenu', () => { | ||
const linkToEnv = 'https://unit-test'; | ||
|
||
it('should display two options, undeploy app and link to app', async () => { | ||
renderMenu(linkToEnv); | ||
await openMenu(); | ||
|
||
const listItems = screen.getAllByRole('listitem'); | ||
expect(listItems).toHaveLength(2); | ||
|
||
expect(screen.getByRole('dialog')).toBeInTheDocument(); | ||
expect(screen.getByText(textMock('app_deployment.more_options_menu'))).toBeInTheDocument(); | ||
}); | ||
|
||
it('should open list of list-items when menu trigger is clicked', async () => { | ||
renderMenu(linkToEnv); | ||
await openMenu(); | ||
|
||
const listItems = screen.getAllByRole('listitem'); | ||
expect(listItems).toHaveLength(2); | ||
}); | ||
|
||
it('should open dialog if undeploy is clicked', async () => { | ||
renderMenu(linkToEnv); | ||
await openMenu(); | ||
|
||
const dialog = screen.getByRole('dialog'); | ||
expect(dialog).toBeInTheDocument(); | ||
}); | ||
|
||
it('should have a link to app within the env', async () => { | ||
renderMenu(linkToEnv); | ||
await openMenu(); | ||
|
||
const linkButton = screen.getByRole('link', { | ||
name: textMock('app_deployment.more_options_menu'), | ||
}); | ||
expect(linkButton).toHaveAttribute('href', linkToEnv); | ||
expect(linkButton).toHaveAttribute('rel', 'noopener noreferrer'); | ||
}); | ||
}); | ||
|
||
function renderMenu(linkToEnv: string): void { | ||
render(<DeployMoreOptionsMenu linkToEnv={linkToEnv} environment='unit-test-env' />); | ||
} | ||
|
||
async function openMenu(): Promise<void> { | ||
const user = userEvent.setup(); | ||
return user.click( | ||
screen.getByRole('button', { | ||
name: textMock('app_deployment.deploy_more_options_menu_label'), | ||
}), | ||
); | ||
} |
51 changes: 51 additions & 0 deletions
51
...evelopment/features/appPublish/components/DeployMoreOptionsMenu/DeployMoreOptionsMenu.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React, { type ReactElement } from 'react'; | ||
import { StudioPopover, StudioButton } from '@studio/components'; | ||
import { ExternalLinkIcon, MenuElipsisVerticalIcon } from '@studio/icons'; | ||
import { UndeployConsequenceDialog } from '../UndeployConsequenceDialog/UndeployConsequenceDialog'; | ||
import classes from './DeployMoreOptionsMenu.module.css'; | ||
import { useTranslation } from 'react-i18next'; | ||
|
||
type DeployMoreOptionsMenuProps = { | ||
environment: string; | ||
linkToEnv: string; | ||
}; | ||
|
||
export const DeployMoreOptionsMenu = ({ | ||
linkToEnv, | ||
environment, | ||
}: DeployMoreOptionsMenuProps): ReactElement => { | ||
const { t } = useTranslation(); | ||
return ( | ||
<StudioPopover> | ||
<StudioPopover.Trigger | ||
size='sm' | ||
variant='secondary' | ||
className={classes.trigger} | ||
aria-label={t('app_deployment.deploy_more_options_menu_label')} | ||
> | ||
<MenuElipsisVerticalIcon /> | ||
</StudioPopover.Trigger> | ||
<StudioPopover.Content className={classes.content}> | ||
<ul className={classes.listContainer}> | ||
<li> | ||
<UndeployConsequenceDialog environment={environment} /> | ||
</li> | ||
<li> | ||
<StudioButton | ||
className={classes.itemButton} | ||
as='a' | ||
fullWidth | ||
href={linkToEnv} | ||
icon={<ExternalLinkIcon />} | ||
rel='noopener noreferrer' | ||
size='sm' | ||
variant='tertiary' | ||
> | ||
{t('app_deployment.more_options_menu')} | ||
</StudioButton> | ||
</li> | ||
</ul> | ||
</StudioPopover.Content> | ||
</StudioPopover> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.