Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Change Request] Limit incidents returned from large query #132

Merged
merged 12 commits into from
Jun 10, 2022
Merged
42 changes: 42 additions & 0 deletions cypress/integration/Query/query.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
checkIncidentCellIconAllRows,
manageIncidentTableColumns,
priorityNames,
selectIncident,
} from '../../support/util/common';

registerLocale('en-GB', gb);
Expand Down Expand Up @@ -67,9 +68,45 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {

// Reset query for next test
activateButton('query-urgency-low-button');
});

it('Query for incidents exceeding MAX_INCIDENTS_LIMIT; Cancel Request', () => {
// Update since date to T-2
const queryDate = moment()
.subtract(2, 'days')
.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
cy.get('#query-date-input').clear().type(queryDate.format('DD/MM/yyyy')).type('{enter}');

// Cancel request from modal
cy.get('#cancel-incident-query-button').click();
cy.get('div.query-cancelled-ctr')
.should('be.visible')
.should('contain.text', 'Query has been cancelled by user');
cy.get('div.selected-incidents-ctr').should('be.visible').should('contain.text', 'N/A');

// Reset query for next test
deactivateButton('query-status-resolved-button');
});

it('Query for incidents exceeding MAX_INCIDENTS_LIMIT; Accept Request', () => {
// Accept request from modal
activateButton('query-status-resolved-button');
cy.get('#retrieve-incident-query-button').click();
cy.get('div.query-active-ctr')
.should('be.visible')
.should('contain.text', 'Querying PagerDuty API');
cy.get('div.selected-incidents-ctr').should('be.visible').should('contain.text', 'Querying');
waitForIncidentTable();

// Reset query for next test
deactivateButton('query-status-resolved-button');
const queryDate = moment()
.subtract(1, 'days')
.set({ hour: 0, minute: 0, second: 0, millisecond: 0 });
cy.get('#query-date-input').clear().type(queryDate.format('DD/MM/yyyy')).type('{enter}');
waitForIncidentTable();
});

it('Query for triggered incidents only', () => {
activateButton('query-status-triggered-button');
deactivateButton('query-status-acknowledged-button');
Expand All @@ -79,6 +116,11 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
});

it('Query for acknowledged incidents only', () => {
// Ensure at least one incident is acknowledged for test
selectIncident(0);
cy.get('#incident-action-acknowledge-button').click();
cy.get('.action-alerts-modal').type('{esc}');

deactivateButton('query-status-triggered-button');
activateButton('query-status-acknowledged-button');
deactivateButton('query-status-resolved-button');
Expand Down
18 changes: 6 additions & 12 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,8 @@ import AddNoteModalComponent from 'components/AddNoteModal/AddNoteModalComponent
import ReassignModalComponent from 'components/ReassignModal/ReassignModalComponent';
import AddResponderModalComponent from 'components/AddResponderModal/AddResponderModalComponent';
import MergeModalComponent from 'components/MergeModal/MergeModalComponent';
import ConfirmQueryModalComponent from 'components/ConfirmQueryModal/ConfirmQueryModalComponent';

import {
getIncidentsAsync as getIncidentsAsyncConnected,
getAllIncidentNotesAsync as getAllIncidentNotesAsyncConnected,
} from 'redux/incidents/actions';
import {
getLogEntriesAsync as getLogEntriesAsyncConnected,
cleanRecentLogEntriesAsync as cleanRecentLogEntriesAsyncConnected,
Expand Down Expand Up @@ -90,8 +87,6 @@ const App = ({
getEscalationPoliciesAsync,
getExtensionsAsync,
getResponsePlaysAsync,
getIncidentsAsync,
getAllIncidentNotesAsync,
getLogEntriesAsync,
cleanRecentLogEntriesAsync,
}) => {
Expand All @@ -108,6 +103,7 @@ const App = ({
const {
userAuthorized, userAcceptedDisclaimer, currentUserLocale,
} = state.users;
const queryError = state.querySettings.error;
useEffect(() => {
userAuthorize();
if (userAuthorized) {
Expand All @@ -120,8 +116,7 @@ const App = ({
getExtensionsAsync();
getResponsePlaysAsync();
getPrioritiesAsync();
getIncidentsAsync();
getAllIncidentNotesAsync();
// NB: Get Incidents and Notes are implicitly done from query now
checkConnectionStatus();
}
}, [userAuthorized]);
Expand All @@ -139,15 +134,15 @@ const App = ({
const {
abilities,
} = store.getState().connection;
if (userAuthorized && abilities.includes(PD_REQUIRED_ABILITY)) {
if (userAuthorized && abilities.includes(PD_REQUIRED_ABILITY) && !queryError) {
const lastPolledDate = moment()
.subtract(2 * LOG_ENTRIES_POLLING_INTERVAL_SECONDS, 'seconds')
.toDate();
getLogEntriesAsync(lastPolledDate);
}
}, LOG_ENTRIES_POLLING_INTERVAL_SECONDS * 1000);
return () => clearInterval(pollingInterval);
}, [userAuthorized]);
}, [userAuthorized, queryError]);

// Setup log entry clearing
useEffect(() => {
Expand Down Expand Up @@ -191,6 +186,7 @@ const App = ({
<ReassignModalComponent />
<AddResponderModalComponent />
<MergeModalComponent />
<ConfirmQueryModalComponent />
</Container>
</div>
);
Expand All @@ -210,8 +206,6 @@ const mapDispatchToProps = (dispatch) => ({
getEscalationPoliciesAsync: () => dispatch(getEscalationPoliciesAsyncConnected()),
getExtensionsAsync: () => dispatch(getExtensionsAsyncConnected()),
getResponsePlaysAsync: () => dispatch(getResponsePlaysAsyncConnected()),
getIncidentsAsync: () => dispatch(getIncidentsAsyncConnected()),
getAllIncidentNotesAsync: () => dispatch(getAllIncidentNotesAsyncConnected()),
getLogEntriesAsync: (since) => dispatch(getLogEntriesAsyncConnected(since)),
cleanRecentLogEntriesAsync: () => dispatch(cleanRecentLogEntriesAsyncConnected()),
});
Expand Down
79 changes: 79 additions & 0 deletions src/components/ConfirmQueryModal/ConfirmQueryModalComponent.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
connect,
} from 'react-redux';

import {
Modal, Button,
} from 'react-bootstrap';

import {
toggleDisplayConfirmQueryModal as toggleDisplayConfirmQueryModalConnected,
confirmIncidentQuery as confirmIncidentQueryConnected,
} from 'redux/query_settings/actions';

import {
MAX_INCIDENTS_LIMIT,
} from 'config/constants';

const ConfirmQueryModalComponent = ({
querySettings,
toggleDisplayConfirmQueryModal,
confirmIncidentQuery,
}) => {
const {
displayConfirmQueryModal, totalIncidentsFromQuery,
} = querySettings;

const handleCancel = () => {
confirmIncidentQuery(false);
toggleDisplayConfirmQueryModal();
};

return (
<div className="confirm-query-modal-ctr">
<Modal show={displayConfirmQueryModal} onHide={handleCancel}>
<Modal.Header closeButton>
<Modal.Title>Max Incidents Limit Reached</Modal.Title>
</Modal.Header>
<Modal.Body>
Current query parameters match&nbsp;
{totalIncidentsFromQuery}
&nbsp;incidents.
<br />
Only the first&nbsp;
{MAX_INCIDENTS_LIMIT}
&nbsp;incidents will be retrieved.
<br />
<br />
Continue?
</Modal.Body>
<Modal.Footer>
<Button
id="retrieve-incident-query-button"
variant="primary"
onClick={() => {
confirmIncidentQuery(true);
toggleDisplayConfirmQueryModal();
}}
>
Retrieve Incidents
</Button>
<Button id="cancel-incident-query-button" variant="danger" onClick={handleCancel}>
Cancel
</Button>
</Modal.Footer>
</Modal>
</div>
);
};

const mapStateToProps = (state) => ({
querySettings: state.querySettings,
});

const mapDispatchToProps = (dispatch) => ({
toggleDisplayConfirmQueryModal: () => dispatch(toggleDisplayConfirmQueryModalConnected()),
confirmIncidentQuery: (confirm) => dispatch(confirmIncidentQueryConnected(confirm)),
});

export default connect(mapStateToProps, mapDispatchToProps)(ConfirmQueryModalComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
mockStore, componentWrapper,
} from 'mocks/store.test';

import {
MAX_INCIDENTS_LIMIT,
} from 'config/constants';

import {
generateRandomInteger,
} from 'util/helpers';

import ConfirmQueryModalComponent from './ConfirmQueryModalComponent';

describe('ConfirmQueryModalComponent', () => {
it('should render modal noting max incident limit has been reached', () => {
const totalIncidentsFromQuery = generateRandomInteger(
MAX_INCIDENTS_LIMIT + 1,
MAX_INCIDENTS_LIMIT * 2,
);
const store = mockStore({
querySettings: {
displayConfirmQueryModal: true,
totalIncidentsFromQuery,
},
});

const wrapper = componentWrapper(store, ConfirmQueryModalComponent);
expect(wrapper.find('.modal-title').at(0).getDOMNode().textContent).toEqual(
'Max Incidents Limit Reached',
);
expect(wrapper.find('.modal-body').at(0).getDOMNode().textContent).toEqual(
[
`Current query parameters match\u00A0${totalIncidentsFromQuery}\u00A0incidents.`,
`Only the first\u00A0${MAX_INCIDENTS_LIMIT}\u00A0incidents will be retrieved.Continue?`,
].join(''),
);
});
});
30 changes: 3 additions & 27 deletions src/components/IncidentActions/IncidentActionsComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,12 @@ import {

import {
Container,
Badge,
Row,
Col,
Button,
Dropdown,
DropdownButton,
ButtonGroup,
Spinner,
} from 'react-bootstrap';
import Select from 'react-select';
import makeAnimated from 'react-select/animated';
Expand Down Expand Up @@ -75,11 +73,12 @@ import {
getObjectsFromListbyKey,
} from 'util/helpers';

import SelectedIncidentsComponent from './subcomponents/SelectedIncidentsComponent';

const animatedComponents = makeAnimated();

const IncidentActionsComponent = ({
incidentTable,
incidents,
priorities,
escalationPolicies,
extensions,
Expand All @@ -98,9 +97,6 @@ const IncidentActionsComponent = ({
runResponsePlayAsync,
syncWithExternalSystem,
}) => {
const {
fetchingIncidents, filteredIncidentsByQuery,
} = incidents;
const {
selectedCount, selectedRows,
} = incidentTable;
Expand Down Expand Up @@ -222,26 +218,7 @@ const IncidentActionsComponent = ({
<Container className="incident-actions-ctr" id="incident-actions-ctr" fluid>
<Row>
<Col sm={{ span: -1 }}>
<div className="selected-incidents-ctr">
{fetchingIncidents ? (
<>
<Spinner animation="border" variant="success" />
<p className="selected-incidents">Querying</p>
</>
) : (
<>
<h4>
<Badge
className="selected-incidents-badge"
variant={filteredIncidentsByQuery.length ? 'primary' : 'secondary'}
>
{`${selectedCount}/${filteredIncidentsByQuery.length}`}
</Badge>
</h4>
<p className="selected-incidents">Selected</p>
</>
)}
</div>
<SelectedIncidentsComponent />
</Col>
<Col>
<Button
Expand Down Expand Up @@ -517,7 +494,6 @@ const IncidentActionsComponent = ({

const mapStateToProps = (state) => ({
incidentTable: state.incidentTable,
incidents: state.incidents,
priorities: state.priorities.priorities,
escalationPolicies: state.escalationPolicies.escalationPolicies,
extensions: state.extensions,
Expand Down
Loading