forked from splunk/addonfactory-ucc-generator
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add UI feature to enable/disable inputs in bulk (splunk#437)
* add a simple button to disable all input status * Fix disable all status after confirm * change AcceptModal component into ts * refactor: apply review changes remove unwanted .DS_Store file remove ts-ignore and apply react types make props optional in AcceptModal * fix: apply review changes + test disable button label typo AcceptModal tests * fix: apply review changes + test disable button label typo AcceptModal tests * fix: updates after review bump ts lib version to es2022 test.jsx to test.tsx extract Disable Button to new component add InputRowData interface * fix: change place of button move button to table header * fix: refactor of file and test changed some variables names added tests for InteractAllStatusButton * fix: review adjustments Enable all status button is first one move test data init to beforeEach added It may take a while. info
- Loading branch information
1 parent
d490efb
commit ab471d0
Showing
8 changed files
with
428 additions
and
3 deletions.
There are no files selected for viewing
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,54 @@ | ||
import React from 'react'; | ||
|
||
import { render, screen } from '@testing-library/react'; | ||
import AcceptModal from './AcceptModal'; | ||
|
||
describe('AcceptModal', () => { | ||
const handleClose = jest.fn(); | ||
|
||
beforeEach(() => { | ||
render( | ||
<AcceptModal | ||
title="test Title" | ||
message="test message" | ||
open | ||
handleRequestClose={handleClose} | ||
declineBtnLabel="No" | ||
acceptBtnLabel="Yes" | ||
/> | ||
); | ||
}); | ||
|
||
it('Return true on accept btn click', async () => { | ||
const modal = await screen.findByTestId('modal'); | ||
expect(modal).toBeInTheDocument(); | ||
|
||
const acceptButton = screen.getByText('Yes'); | ||
expect(acceptButton).toBeInTheDocument(); | ||
|
||
acceptButton.click(); | ||
expect(handleClose).toHaveBeenCalledWith(true); | ||
}); | ||
|
||
it('Return false on decline btn click', async () => { | ||
const modal = await screen.findByTestId('modal'); | ||
expect(modal).toBeInTheDocument(); | ||
|
||
const declineButton = screen.getByText('No'); | ||
expect(declineButton).toBeInTheDocument(); | ||
|
||
declineButton.click(); | ||
expect(handleClose).toHaveBeenCalledWith(false); | ||
}); | ||
|
||
it('Return false on closing modal by X btn', async () => { | ||
const modal = await screen.findByTestId('modal'); | ||
expect(modal).toBeInTheDocument(); | ||
|
||
const closeXBtn = screen.getByTestId('close'); | ||
expect(closeXBtn).toBeInTheDocument(); | ||
|
||
closeXBtn.click(); | ||
expect(handleClose).toHaveBeenCalledWith(false); | ||
}); | ||
}); |
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,48 @@ | ||
import React from 'react'; | ||
import Modal from '@splunk/react-ui/Modal'; | ||
import Message from '@splunk/react-ui/Message'; | ||
import styled from 'styled-components'; | ||
import { StyledButton } from '../pages/EntryPageStyle'; | ||
|
||
const ModalWrapper = styled(Modal)` | ||
width: 600px; | ||
`; | ||
|
||
interface AcceptModalProps { | ||
title: string; | ||
open: boolean; | ||
handleRequestClose: (accepted: boolean) => void; | ||
message?: string; | ||
declineBtnLabel?: string; | ||
acceptBtnLabel?: string; | ||
} | ||
|
||
function AcceptModal(props: AcceptModalProps) { | ||
return ( | ||
<ModalWrapper open={props.open}> | ||
<Modal.Header | ||
onRequestClose={() => props.handleRequestClose(false)} | ||
title={props.title} | ||
/> | ||
<Modal.Body> | ||
<Message appearance="fill" type="warning"> | ||
{props.message} | ||
</Message> | ||
</Modal.Body> | ||
<Modal.Footer> | ||
<StyledButton | ||
appearance="primary" | ||
onClick={() => props.handleRequestClose(false)} | ||
label={props.declineBtnLabel || 'Cancel'} | ||
/> | ||
<StyledButton | ||
appearance="primary" | ||
onClick={() => props.handleRequestClose(true)} | ||
label={props.acceptBtnLabel || 'OK'} | ||
/> | ||
</Modal.Footer> | ||
</ModalWrapper> | ||
); | ||
} | ||
|
||
export default AcceptModal; |
196 changes: 196 additions & 0 deletions
196
ui/src/main/webapp/components/InteractAllStatusButton.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,196 @@ | ||
import React from 'react'; | ||
|
||
import { render, screen } from '@testing-library/react'; | ||
import { AllInputRowsData, InteractAllStatusButtons } from './InteractAllStatusButton'; | ||
|
||
describe('InteractAllStatusButtons', () => { | ||
const handleToggleStatusChange = jest.fn(); | ||
|
||
let allDataRowsMockUp: AllInputRowsData; | ||
let totalElements: number; | ||
|
||
beforeEach(() => { | ||
allDataRowsMockUp = { | ||
demo_input: { | ||
aaaaa: { | ||
account: 'Test_Account', | ||
disabled: false, | ||
host: '$decideOnStartup', | ||
host_resolved: 'testHost', | ||
index: 'default', | ||
interval: '300', | ||
name: 'aaaaa', | ||
serviceName: 'demo_input', | ||
serviceTitle: 'demo_input', | ||
__toggleShowSpinner: false, | ||
}, | ||
bbbbb: { | ||
account: 'Test_Account_2', | ||
disabled: false, | ||
host: '$decideOnStartup', | ||
host_resolved: 'testHost', | ||
index: 'default', | ||
interval: '300', | ||
name: 'bbbbb', | ||
serviceName: 'demo_input', | ||
serviceTitle: 'demo_input', | ||
__toggleShowSpinner: false, | ||
}, | ||
brrrrrrr: { | ||
account: 'aaaaa', | ||
disabled: false, | ||
host: '$decideOnStartup', | ||
host_resolved: 'testHost', | ||
index: 'default', | ||
interval: '300', | ||
name: 'brrrrrrr', | ||
serviceName: 'demo_input', | ||
serviceTitle: 'demo_input', | ||
__toggleShowSpinner: false, | ||
}, | ||
cccccc: { | ||
account: 'Test_Account', | ||
disabled: true, | ||
host: '$decideOnStartup', | ||
host_resolved: 'testHost', | ||
index: 'default', | ||
interval: '300', | ||
name: 'cccccc', | ||
serviceName: 'demo_input', | ||
serviceTitle: 'demo_input', | ||
__toggleShowSpinner: false, | ||
}, | ||
dddddd: { | ||
account: 'Test_Account_2', | ||
disabled: true, | ||
host: '$decideOnStartup', | ||
host_resolved: 'testHost', | ||
index: 'default', | ||
interval: '300', | ||
name: 'dddddd', | ||
serviceName: 'demo_input', | ||
serviceTitle: 'demo_input', | ||
__toggleShowSpinner: false, | ||
}, | ||
gggggg: { | ||
account: 'aaaaa', | ||
disabled: true, | ||
host: '$decideOnStartup', | ||
host_resolved: 'testHost', | ||
index: 'default', | ||
interval: '300', | ||
name: 'gggggg', | ||
serviceName: 'demo_input', | ||
serviceTitle: 'demo_input', | ||
__toggleShowSpinner: false, | ||
}, | ||
}, | ||
}; | ||
|
||
totalElements = Object.values(allDataRowsMockUp) | ||
.map((data) => Object.values(data).map((row) => row)) | ||
.flat().length; | ||
|
||
render( | ||
<InteractAllStatusButtons | ||
data-testid="actionBtns" | ||
displayActionBtnAllRows | ||
totalElement={totalElements} | ||
allDataRows={allDataRowsMockUp} | ||
changeToggleStatus={handleToggleStatusChange} | ||
/> | ||
); | ||
}); | ||
|
||
it('Disable All enabled rows correctly', async () => { | ||
const disableBtn = await screen.findByText('Disable all'); | ||
expect(disableBtn).toBeInTheDocument(); | ||
|
||
disableBtn.click(); | ||
|
||
const yesPopUpBtn = await screen.findByText('Yes'); | ||
expect(yesPopUpBtn).toBeInTheDocument(); | ||
|
||
yesPopUpBtn.click(); | ||
|
||
expect(handleToggleStatusChange).toHaveBeenCalledWith(allDataRowsMockUp.demo_input.aaaaa); | ||
expect(handleToggleStatusChange).toHaveBeenCalledWith(allDataRowsMockUp.demo_input.bbbbb); | ||
expect(handleToggleStatusChange).toHaveBeenCalledWith( | ||
allDataRowsMockUp.demo_input.brrrrrrr | ||
); | ||
expect(handleToggleStatusChange).toHaveBeenCalledTimes(3); | ||
}); | ||
|
||
it('Enable All disabled rows correctly', async () => { | ||
const enableBtn = await screen.findByText('Enable all'); | ||
expect(enableBtn).toBeInTheDocument(); | ||
|
||
enableBtn.click(); | ||
|
||
const yesPopUpBtn = await screen.findByText('Yes'); | ||
expect(yesPopUpBtn).toBeInTheDocument(); | ||
|
||
yesPopUpBtn.click(); | ||
|
||
expect(handleToggleStatusChange).toHaveBeenCalledWith(allDataRowsMockUp.demo_input.cccccc); | ||
expect(handleToggleStatusChange).toHaveBeenCalledWith(allDataRowsMockUp.demo_input.dddddd); | ||
expect(handleToggleStatusChange).toHaveBeenCalledWith(allDataRowsMockUp.demo_input.gggggg); | ||
expect(handleToggleStatusChange).toHaveBeenCalledTimes(3); | ||
}); | ||
|
||
it('Do not disable status if rejected', async () => { | ||
const disableBtn = await screen.findByText('Disable all'); | ||
expect(disableBtn).toBeInTheDocument(); | ||
|
||
disableBtn.click(); | ||
|
||
const noPopUpBtn = await screen.findByText('No'); | ||
expect(noPopUpBtn).toBeInTheDocument(); | ||
|
||
noPopUpBtn.click(); | ||
|
||
expect(handleToggleStatusChange).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
it('Do not enable status if rejected', async () => { | ||
const enableBtn = await screen.findByText('Enable all'); | ||
expect(enableBtn).toBeInTheDocument(); | ||
|
||
enableBtn.click(); | ||
|
||
const noPopUpBtn = await screen.findByText('No'); | ||
expect(noPopUpBtn).toBeInTheDocument(); | ||
|
||
noPopUpBtn.click(); | ||
|
||
expect(handleToggleStatusChange).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
it('Do not enable status if popup modal closed by X', async () => { | ||
const enableBtn = await screen.findByText('Enable all'); | ||
expect(enableBtn).toBeInTheDocument(); | ||
|
||
enableBtn.click(); | ||
|
||
const closeXBtn = screen.getByTestId('close'); | ||
expect(closeXBtn).toBeInTheDocument(); | ||
|
||
closeXBtn.click(); | ||
|
||
expect(handleToggleStatusChange).toHaveBeenCalledTimes(0); | ||
}); | ||
|
||
it('Do not disable status if popup modal closed by X', async () => { | ||
const disableBtn = await screen.findByText('Disable all'); | ||
expect(disableBtn).toBeInTheDocument(); | ||
|
||
disableBtn.click(); | ||
|
||
const closeXBtn = screen.getByTestId('close'); | ||
expect(closeXBtn).toBeInTheDocument(); | ||
|
||
closeXBtn.click(); | ||
|
||
expect(handleToggleStatusChange).toHaveBeenCalledTimes(0); | ||
}); | ||
}); |
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,97 @@ | ||
import React, { useState } from 'react'; | ||
import styled from 'styled-components'; | ||
import Button from '@splunk/react-ui/Button'; | ||
import { variables } from '@splunk/themes'; | ||
import AcceptModal from './AcceptModal'; | ||
|
||
interface InputRowData { | ||
account: string; | ||
disabled: boolean; | ||
host: string; | ||
host_resolved: string; | ||
index: string; | ||
interval: string; | ||
name: string; | ||
serviceName: string; | ||
serviceTitle: string; | ||
__toggleShowSpinner: boolean; | ||
} | ||
|
||
export interface AllInputRowsData { | ||
[serviceName: string]: { | ||
[inputName: string]: InputRowData; | ||
}; | ||
} | ||
|
||
interface DisableAllStatusButtonProps { | ||
displayActionBtnAllRows: boolean; | ||
totalElement: number; | ||
allDataRows: AllInputRowsData; | ||
changeToggleStatus: (row: InputRowData) => void; | ||
} | ||
|
||
const InteractAllActionButton = styled(Button)` | ||
max-width: fit-content; | ||
font-size: ${variables.fontSize}; | ||
`; | ||
|
||
export function InteractAllStatusButtons(props: DisableAllStatusButtonProps) { | ||
const [tryInteract, setTryInteract] = useState(false); | ||
const [isDisabling, setIsDisabling] = useState(false); | ||
|
||
const handleInteractWithAllRowsStatus = (rowsData: AllInputRowsData) => { | ||
Object.values(rowsData).forEach((data) => | ||
Object.values(data).forEach((row) => { | ||
if (row.disabled !== isDisabling) { | ||
props.changeToggleStatus(row); | ||
} | ||
}) | ||
); | ||
}; | ||
|
||
const handleAcceptModal = (e: boolean) => { | ||
setTryInteract(false); | ||
if (e) { | ||
handleInteractWithAllRowsStatus(props.allDataRows); | ||
} | ||
}; | ||
|
||
return props.displayActionBtnAllRows && props.totalElement > 1 ? ( | ||
<div> | ||
<InteractAllActionButton | ||
data-testid="enableAllBtn" | ||
onClick={() => { | ||
setTryInteract(true); | ||
setIsDisabling(false); | ||
}} | ||
role="button" | ||
disabled={false} | ||
> | ||
Enable all | ||
</InteractAllActionButton> | ||
<InteractAllActionButton | ||
data-testid="disableAllBtn" | ||
onClick={() => { | ||
setTryInteract(true); | ||
setIsDisabling(true); | ||
}} | ||
role="button" | ||
disabled={false} | ||
> | ||
Disable all | ||
</InteractAllActionButton> | ||
{tryInteract && ( | ||
<AcceptModal | ||
message={`Do you want to ${ | ||
isDisabling ? 'disable' : 'enable' | ||
} all? It may take a while.`} | ||
open={tryInteract} | ||
handleRequestClose={handleAcceptModal} | ||
title={isDisabling ? 'Disable all' : 'Enable all'} | ||
declineBtnLabel="No" | ||
acceptBtnLabel="Yes" | ||
/> | ||
)} | ||
</div> | ||
) : null; | ||
} |
Oops, something went wrong.