Skip to content

Commit

Permalink
Merge pull request #132 from giranm/task/limit-incidents-queried
Browse files Browse the repository at this point in the history
[Change Request] Limit incidents returned from large query
  • Loading branch information
giranm authored Jun 10, 2022
2 parents cde83cc + eb95f66 commit 1f62cdd
Show file tree
Hide file tree
Showing 23 changed files with 836 additions and 94 deletions.
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

0 comments on commit 1f62cdd

Please sign in to comment.