Skip to content

Commit

Permalink
feat: add UI feature to enable/disable inputs in bulk (#437)
Browse files Browse the repository at this point in the history
* 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
soleksy-splunk committed Oct 2, 2023
1 parent d490efb commit ab471d0
Show file tree
Hide file tree
Showing 8 changed files with 428 additions and 3 deletions.
54 changes: 54 additions & 0 deletions ui/src/main/webapp/components/AcceptModal.test.tsx
@@ -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);
});
});
48 changes: 48 additions & 0 deletions ui/src/main/webapp/components/AcceptModal.tsx
@@ -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 ui/src/main/webapp/components/InteractAllStatusButton.test.tsx
@@ -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);
});
});
97 changes: 97 additions & 0 deletions ui/src/main/webapp/components/InteractAllStatusButton.tsx
@@ -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;
}

0 comments on commit ab471d0

Please sign in to comment.