diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
index 1b38ea58..f8a8f39c 100644
--- a/.github/workflows/codeql.yml
+++ b/.github/workflows/codeql.yml
@@ -46,7 +46,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
- uses: github/codeql-action/init@v2
+ uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
@@ -60,7 +60,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
- uses: github/codeql-action/autobuild@v2
+ uses: github/codeql-action/autobuild@v3
# ℹ️ Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -73,6 +73,6 @@ jobs:
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
- uses: github/codeql-action/analyze@v2
+ uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/snyk-security.yml b/.github/workflows/snyk-security.yml
index 1a33d328..5780a377 100644
--- a/.github/workflows/snyk-security.yml
+++ b/.github/workflows/snyk-security.yml
@@ -60,6 +60,6 @@ jobs:
# Push the Snyk Code results into GitHub Code Scanning tab
- name: Upload result to GitHub Code Scanning
- uses: github/codeql-action/upload-sarif@v2
+ uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: snyk-code.sarif
diff --git a/cypress.config.js b/cypress.config.js
index e1e3d7ce..5be05f0c 100644
--- a/cypress.config.js
+++ b/cypress.config.js
@@ -22,8 +22,6 @@ module.exports = defineConfig({
},
baseUrl: 'http://localhost:3000/pd-live-react',
specPattern: 'cypress/e2e/**/*.spec.{js,ts,jsx,tsx}',
- // Cypress 12 introduces Test Isolation by default which breaks our current tests
- // https://docs.cypress.io/guides/references/migration-guide#Test-Isolation
- testIsolation: false,
+ testIsolation: true,
},
});
diff --git a/cypress/e2e/Incidents/incidents.spec.js b/cypress/e2e/Incidents/incidents.spec.js
index ef7733e4..14507255 100644
--- a/cypress/e2e/Incidents/incidents.spec.js
+++ b/cypress/e2e/Incidents/incidents.spec.js
@@ -25,10 +25,12 @@ import {
manageIncidentTableColumns,
priorityNames,
selectAlert,
+ waitForAlerts,
} from '../../support/util/common';
-describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
- before(() => {
+describe('Manage Open Incidents', { failFast: { enabled: true } }, () => {
+ // We use beforeEach as each test will reload/clear the session
+ beforeEach(() => {
acceptDisclaimer();
const columns = [
['Responders', 'responders'],
@@ -41,47 +43,28 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
waitForIncidentTable();
});
- // We use beforeEach as each test will reload/clear the session
- beforeEach(() => {
- // Handle failing tests by clearing cache
- if (cy.state('test').currentRetry() > 1) {
- acceptDisclaimer();
- const columns = [
- ['Responders', 'responders'],
- ['Latest Log Entry Type', 'latest_log_entry_type'],
- ];
- manageIncidentTableColumns(
- 'add',
- columns.map((column) => column[1]),
- );
- }
- waitForIncidentTable();
- });
-
afterEach(() => {
checkNoIncidentsSelected();
});
it('Select all incidents', () => {
selectAllIncidents();
- cy.get('.selected-incidents-badge').then(($el) => {
+ cy.get('.selected-incidents-badge').should(($el) => {
const text = $el.text();
const incidentNumbers = text.split(' ')[0].split('/');
expect(incidentNumbers[0]).to.equal(incidentNumbers[1]);
});
- // Unselect all incidents for the next run
+ // Unselect all incidents
selectAllIncidents();
- });
-
- it('Shift-select multiple incidents', () => {
+ // Shift-select multiple incidents
selectIncident(0);
selectIncident(4, true);
- cy.get('.selected-incidents-badge').then(($el) => {
+ cy.get('.selected-incidents-badge').should(($el) => {
const text = $el.text();
const incidentNumbers = text.split(' ')[0].split('/');
expect(incidentNumbers[0]).to.equal('5');
});
- // Unselect all incidents for the next run
+ // Unselect all incidents
selectAllIncidents();
selectAllIncidents();
});
@@ -97,41 +80,36 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
});
});
- it('Add note to singular incident', () => {
+ it('Add notes to singular incidents', () => {
const note = 'All your base are belong to us';
- const incidentIdx = 0;
- selectIncident(incidentIdx);
+ selectIncident(0);
- cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
+ cy.get('@selectedIncidentId_0').then((incidentId) => {
addNote(note);
checkActionAlertsModalContent('have been updated with a note');
checkIncidentCellContent(incidentId, 'Latest Note', note);
checkIncidentCellContent(incidentId, 'Latest Log Entry Type', 'annotate');
});
- });
- it('Add a very long note to singular incident which overflows', () => {
- const note = 'This note is so long that I gave up writing a novel and decided to quit!';
- const incidentIdx = 1;
- selectIncident(incidentIdx);
+ // Add a very long note to singular incident which overflows
+ const noteLong = 'This note is so long that I gave up writing a novel and decided to quit!';
+ selectIncident(1);
- cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
- addNote(note);
+ cy.get('@selectedIncidentId_1').then((incidentId) => {
+ addNote(noteLong);
checkActionAlertsModalContent('have been updated with a note');
- checkIncidentCellContent(incidentId, 'Latest Note', note);
+ checkIncidentCellContent(incidentId, 'Latest Note', noteLong);
checkIncidentCellContent(incidentId, 'Latest Log Entry Type', 'annotate');
});
- });
- it('Add note with URL to singular incident', () => {
- const note = 'This note has a URL example.com included';
- const incidentIdx = 2;
- selectIncident(incidentIdx);
+ // 'Add note with URL to singular incident
+ const noteURL = 'This note has a URL example.com included';
+ selectIncident(2);
- cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
- addNote(note);
+ cy.get('@selectedIncidentId_2').then((incidentId) => {
+ addNote(noteURL);
checkActionAlertsModalContent('have been updated with a note');
- checkIncidentCellContent(incidentId, 'Latest Note', note);
+ checkIncidentCellContent(incidentId, 'Latest Note', noteURL);
checkIncidentCellContentHasLink(
incidentId,
'Latest Note',
@@ -139,17 +117,15 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
'http://example.com',
);
});
- });
- it('Add note with email to singular incident', () => {
- const note = 'This note has an email test@example.com included';
- const incidentIdx = 3;
- selectIncident(incidentIdx);
+ // Add note with email to singular incident
+ const noteEmail = 'This note has an email test@example.com included';
+ selectIncident(3);
- cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
- addNote(note);
+ cy.get('@selectedIncidentId_3').then((incidentId) => {
+ addNote(noteEmail);
checkActionAlertsModalContent('have been updated with a note');
- checkIncidentCellContent(incidentId, 'Latest Note', note);
+ checkIncidentCellContent(incidentId, 'Latest Note', noteEmail);
checkIncidentCellContentHasLink(
incidentId,
'Latest Note',
@@ -159,13 +135,13 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
});
});
- // Assumed environment has 3 levels on escalation policy
- for (let escalationLevel = 1; escalationLevel < 4; escalationLevel++) {
- it(`Escalate singular incident to level: ${escalationLevel}`, () => {
- // Ensure that only high urgency incidents are visible
- // deactivateButton('query-urgency-low-button');
- cy.get('.query-urgency-low-button').uncheck({ force: true });
- waitForIncidentTable();
+ it('Escalate singular incident to multiple levels', () => {
+ // Ensure that only high urgency incidents are visible
+ cy.get('.query-urgency-low-button').uncheck({ force: true });
+ waitForIncidentTable();
+
+ // Assumed environment has 3 levels on escalation policy
+ for (let escalationLevel = 1; escalationLevel < 4; escalationLevel++) {
const incidentIdx = 0;
selectIncident(incidentIdx);
cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
@@ -173,75 +149,74 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
checkActionAlertsModalContent(`have been manually escalated to level ${escalationLevel}`);
checkIncidentCellContent(incidentId, 'Latest Log Entry Type', /escalate|notify/);
});
- });
- }
+ }
+ });
- it('Reassign singular incident to User A1', () => {
- // activateButton('query-urgency-low-button'); // bring back low urgency incidents
+ it('Reassign singular incidents to User and Team', () => {
cy.get('.query-urgency-low-button').check({ force: true });
- const assignment = 'User A1';
+ let assignment = 'User A1';
selectIncident(0);
reassign(assignment);
checkActionAlertsModalContent(`have been reassigned to ${assignment}`);
- });
- it('Reassign singular incident to Team A', () => {
- const assignment = 'Team A';
+ assignment = 'Team A';
selectIncident(1);
reassign(assignment);
checkActionAlertsModalContent(`have been reassigned to ${assignment}`);
});
- it('Add responder (User A1) to singular incident', () => {
- const responders = ['User A1'];
+ it('Add User and Team responders to singular incident', () => {
+ let responders = ['User A1'];
const message = 'Need help with this incident';
- const incidentIdx = 0;
+ let incidentIdx = 0;
selectIncident(incidentIdx);
addResponders(responders, message);
checkActionAlertsModalContent('Requested additional response for incident(s)');
cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
checkIncidentCellContent(incidentId, 'Responders', 'UA');
- checkPopoverContent(incidentId, 'Responders', 'user_a1@example.com');
+ checkPopoverContent(incidentId, 'Responders', 'User A');
checkIncidentCellContent(incidentId, 'Latest Log Entry Type', 'responder_request');
});
- });
- it('Add responder (Team A) to singular incident', () => {
- const responders = ['Team A'];
- const message = 'Need help with this incident';
- selectIncident(0);
+ responders = ['Team A'];
+ incidentIdx = 1;
+ selectIncident(incidentIdx);
addResponders(responders, message);
checkActionAlertsModalContent('Requested additional response for incident(s)');
+ cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
+ checkIncidentCellContent(incidentId, 'Responders', 'UA');
+ checkPopoverContent(incidentId, 'Responders', 'User A');
+ checkIncidentCellContent(incidentId, 'Latest Log Entry Type', 'responder_request');
+ });
});
it('Add multiple responders (Team A + Team B) to singular incident', () => {
const responders = ['Team A', 'Team B'];
const message = "Need everyone's help with this incident";
- selectIncident(0);
+ const incidentIdx = 0;
+ selectIncident(incidentIdx);
addResponders(responders, message);
checkActionAlertsModalContent('Requested additional response for incident(s)');
+ cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
+ checkIncidentCellContent(incidentId, 'Responders', 'UA');
+ checkIncidentCellContent(incidentId, 'Responders', 'UB');
+ checkPopoverContent(incidentId, 'Responders', 'User A');
+ checkPopoverContent(incidentId, 'Responders', 'User B');
+ checkIncidentCellContent(incidentId, 'Latest Log Entry Type', 'responder_request');
+ });
});
- it('Snooze singular incident for specified duration (5 minutes)', () => {
- const duration = '5 minutes';
+ it('Snooze singular incidents with specified or custom durations', () => {
selectIncident(0);
- snooze(duration);
+ snooze('5 minutes');
checkActionAlertsModalContent('have been snoozed.');
- });
- it('Snooze singular incident for custom duration (2 hours)', () => {
- const type = 'hours';
- const option = 2;
- selectIncident(0);
- snoozeCustom(type, option);
+ selectIncident(1);
+ snoozeCustom('hours', 2);
checkActionAlertsModalContent('have been snoozed.');
- });
- it('Snooze singular incident for custom duration (tomorrow for 9:00 AM)', () => {
- const type = 'tomorrow';
- const option = '9:00 AM';
- selectIncident(0);
- snoozeCustom(type, option);
+ selectIncident(2);
+ snoozeCustom('tomorrow', '9:00 AM');
checkActionAlertsModalContent('have been snoozed.');
});
@@ -257,10 +232,13 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
selectIncident(0);
cy.get('#incident-action-resolve-button').click();
checkActionAlertsModalContent('have been resolved');
+ // and now show all resolved status (#380)
+ cy.get('.query-status-resolved-button').check({ force: true });
+ waitForIncidentTable();
});
- priorityNames.forEach((priorityName, idx) => {
- it(`Update priority of singular incident to ${priorityName}`, () => {
+ it('Update priority of singular incidents', () => {
+ priorityNames.forEach((priorityName, idx) => {
const incidentIdx = idx;
selectIncident(incidentIdx);
cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
@@ -274,10 +252,6 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
});
it('Run external system sync on singular incident', () => {
- // For some reason this doesn't work on first attempt - clearing cache as a workaround
- // acceptDisclaimer();
- // waitForIncidentTable();
-
const externalSystemName = 'ServiceNow';
selectIncident(0);
runExternalSystemSync(externalSystemName);
@@ -298,33 +272,35 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
runResponsePlay(responsePlayName);
checkActionAlertsModalContent(`Ran "${responsePlayName}" response play for incident(s)`);
});
+});
- it('Hovering over Num Alerts count should popup alert table', () => {
- // Remove the other columns to make it easier to see the alert count without scrolling
- const removeColumns = [
- ['Responders', 'responders'],
- ['Latest Log Entry Type', 'latest_log_entry_type'],
- ];
- manageIncidentTableColumns(
- 'remove',
- removeColumns.map((column) => column[1]),
- );
-
- const addColumns = [
- ['Num Alerts', 'num_alerts'],
- ];
+describe('Manage Alerts', { failFast: { enabled: true } }, () => {
+ // We use beforeEach as each test will reload/clear the session
+ beforeEach(() => {
+ acceptDisclaimer();
+ const addColumns = [['Num Alerts', 'num_alerts']];
manageIncidentTableColumns(
'add',
addColumns.map((column) => column[1]),
);
+ waitForIncidentTable();
+ waitForAlerts();
+ });
+ afterEach(() => {
+ checkNoIncidentsSelected();
+ });
+
+ it('Hovering over Num Alerts count should popup alert table', () => {
const incidentIdx = 0;
cy.get(`[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${incidentIdx}"]`)
.should('be.visible')
.should('contain', '1');
- cy.get(`[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${incidentIdx}"]`).within(() => {
+ cy.get(
+ `[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${incidentIdx}"]`,
+ ).within(() => {
cy.get('[aria-haspopup="dialog"]').realHover();
});
@@ -332,15 +308,14 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
cy.get('[data-popper-placement="bottom"]').should('contain', 'Created At');
cy.get('[data-popper-placement="bottom"]').should('contain', 'Status');
cy.get('[data-popper-placement="bottom"]').should('contain', 'Summary');
-
- // Reset hover state
- cy.get('body').realHover({ position: 'topLeft' });
});
it('Split/move alert from one incident to a new incident', () => {
const incidentIdx = 0;
- cy.get(`[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${incidentIdx}"]`).within(() => {
+ cy.get(
+ `[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${incidentIdx}"]`,
+ ).within(() => {
cy.get('[aria-haspopup="dialog"]').click();
});
@@ -348,7 +323,9 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
cy.get('#alerts-modal-move-btn').click();
cy.get('#alerts-modal-move-select').type('Move all selected alerts to one new incident{enter}');
- cy.get('#alerts-modal-move-summary-input').clear().type('New incident created from split alert');
+ cy.get('#alerts-modal-move-summary-input')
+ .clear()
+ .type('New incident created from split alert');
cy.get('#alerts-modal-complete-move-btn').click();
checkActionAlertsModalContent('Alerts moved');
@@ -368,7 +345,9 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
waitForIncidentTable();
const incidentIdx = 0;
- cy.get(`[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${incidentIdx}"]`).within(() => {
+ cy.get(
+ `[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${incidentIdx}"]`,
+ ).within(() => {
cy.get('[aria-haspopup="dialog"]').should('be.visible').should('have.text', '2').click();
});
@@ -376,7 +355,9 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
selectAlert(1);
cy.get('#alerts-modal-move-btn').click();
- cy.get('#alerts-modal-move-select').type('Move each selected alert to its own new incident{enter}');
+ cy.get('#alerts-modal-move-select').type(
+ 'Move each selected alert to its own new incident{enter}',
+ );
cy.get('#alerts-modal-complete-move-btn').click();
checkActionAlertsModalContent('Alerts moved');
@@ -389,16 +370,20 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
selectIncident(targetIncidentIdx);
cy.get(`@selectedIncidentId_${targetIncidentIdx}`).then((incidentId) => {
- cy.get(`[data-incident-header="Num Alerts"][data-incident-cell-id="${incidentId}"]`).within(() => {
- cy.get('[aria-haspopup="dialog"]').should('be.visible').should('have.text', '1');
- });
+ cy.get(`[data-incident-header="Num Alerts"][data-incident-cell-id="${incidentId}"]`).within(
+ () => {
+ cy.get('[aria-haspopup="dialog"]').should('be.visible').should('have.text', '1');
+ },
+ );
cy.get(`[data-incident-header="Title"][data-incident-cell-id="${incidentId}"]`).within(() => {
cy.get('a').invoke('text').as('targetIncidentTitle');
});
});
- cy.get(`[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${sourceIncidentIdx}"]`).within(() => {
+ cy.get(
+ `[data-incident-header="Num Alerts"][data-incident-row-cell-idx="${sourceIncidentIdx}"]`,
+ ).within(() => {
cy.get('[aria-haspopup="dialog"]').should('be.visible').should('have.text', '1').click();
});
@@ -415,9 +400,11 @@ describe('Manage Open Incidents', { failFast: { enabled: false } }, () => {
waitForIncidentTable();
cy.get(`@selectedIncidentId_${targetIncidentIdx}`).then((incidentId) => {
- cy.get(`[data-incident-header="Num Alerts"][data-incident-cell-id="${incidentId}"]`).within(() => {
- cy.get('[aria-haspopup="dialog"]').should('be.visible').should('have.text', '2');
- });
+ cy.get(`[data-incident-header="Num Alerts"][data-incident-cell-id="${incidentId}"]`).within(
+ () => {
+ cy.get('[aria-haspopup="dialog"]').should('be.visible').should('have.text', '2');
+ },
+ );
});
// Tidy up by resolving the incident with two alerts
diff --git a/cypress/e2e/Query/query.spec.js b/cypress/e2e/Query/query.spec.js
index ca3ab36f..40da603b 100644
--- a/cypress/e2e/Query/query.spec.js
+++ b/cypress/e2e/Query/query.spec.js
@@ -19,28 +19,14 @@ import {
registerLocale('en-GB', gb);
moment.locale('en-GB');
-describe('Query Incidents', { failFast: { enabled: false } }, () => {
- before(() => {
+describe('Query Incidents', { failFast: { enabled: true } }, () => {
+ beforeEach(() => {
acceptDisclaimer();
manageIncidentTableColumns('remove', ['latest_note']);
manageIncidentTableColumns('add', ['urgency', 'teams', 'escalation_policy']);
- // priorityNames.forEach((currentPriority) => {
- // activateButton(`query-priority-${currentPriority}-button`);
- // });
waitForIncidentTable();
});
- beforeEach(() => {
- if (cy.state('test').currentRetry() > 1) {
- acceptDisclaimer();
- manageIncidentTableColumns('remove', ['latest_note']);
- manageIncidentTableColumns('add', ['urgency', 'teams', 'escalation_policy']);
- }
- // priorityNames.forEach((currentPriority) => {
- // activateButton(`query-priority-${currentPriority}-button`);
- // });
- });
-
it('Query for incidents within T-1 since date', () => {
// Limit dataset to high-urgency triggered, ackd and resolved incidents
// activateButton('query-status-resolved-button');
@@ -68,9 +54,6 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
});
}
});
-
- // Reset query for next test - both high and low-urgency triggered, ackd and resolved incidents
- cy.get('.query-urgency-low-button').check({ force: true });
});
it('Query for triggered incidents only', () => {
@@ -100,11 +83,6 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
cy.get('.query-status-resolved-button').check({ force: true });
waitForIncidentTable();
checkIncidentCellIconAllRows('Status', 'fa-circle-check');
-
- // Reset query for next test
- cy.get('.query-status-triggered-button').check({ force: true });
- cy.get('.query-status-acknowledged-button').check({ force: true });
- cy.get('.query-status-resolved-button').uncheck({ force: true });
});
it('Query for high urgency incidents only', () => {
@@ -119,9 +97,6 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
cy.get('.query-urgency-low-button').check({ force: true });
waitForIncidentTable();
checkIncidentCellContentAllRows('Urgency', ' Low');
-
- // Reset query for next test
- cy.get('.query-urgency-high-button').check({ force: true });
});
priorityNames.forEach((currentPriority) => {
@@ -226,7 +201,7 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
cy.get('#query-user-select').click().type('{del}{del}{del}');
});
- it('Sort incident column "#" by ascending order', () => {
+ it('Sort incident columns', () => {
cy.get('[data-column-name="#"]')
.click()
.then(($el) => {
@@ -234,9 +209,7 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
expect(cls).to.contain('th-sorted');
cy.wrap($el).contains('# ▲');
});
- });
- it('Sort incident column "#" by descending order', () => {
cy.get('[data-column-name="#"]')
.click()
.then(($el) => {
@@ -244,9 +217,7 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
expect(cls).to.contain('th-sorted');
cy.wrap($el).contains('# ▼');
});
- });
- it('Clear sort on incident column "#"', () => {
cy.get('[data-column-name="#"]')
.click()
.then(($el) => {
diff --git a/cypress/e2e/Search/search.spec.js b/cypress/e2e/Search/search.spec.js
index a52a5c4d..900df9ac 100644
--- a/cypress/e2e/Search/search.spec.js
+++ b/cypress/e2e/Search/search.spec.js
@@ -11,22 +11,14 @@ import {
updateFuzzySearch,
} from '../../support/util/common';
-describe('Search Incidents', { failFast: { enabled: false } }, () => {
- before(() => {
- acceptDisclaimer();
- waitForIncidentTable();
- });
-
+describe('Search Incidents', { failFast: { enabled: true } }, () => {
beforeEach(() => {
- if (cy.state('test').currentRetry() > 1) {
- acceptDisclaimer();
- }
+ acceptDisclaimer();
waitForIncidentTable();
});
it('Search for `Service A1` returns incidents only on Service A1', () => {
cy.get('#global-search-input').clear().type('Service A1');
- cy.wait(1000);
cy.get('[data-incident-header="Service"]').each(($el) => {
cy.wrap($el).should('have.text', 'Service A1');
});
@@ -39,21 +31,18 @@ describe('Search Incidents', { failFast: { enabled: false } }, () => {
cy.get(`@selectedIncidentId_${incidentIdx}`).then((incidentId) => {
cy.get('#global-search-input').clear().type(incidentId);
});
- cy.wait(1000);
cy.get('.selected-incidents-badge').then(($el) => {
const text = $el.text().split(' ')[0];
expect(text).to.equal('1/1');
});
// Click the select all checkbox twice to unselect all
cy.get('#global-search-input').clear();
- cy.wait(1000);
selectAllIncidents();
selectAllIncidents();
});
it('Search for `zzzzzz` returns no incidents', () => {
cy.get('#global-search-input').clear().type('zzzzzz');
- cy.wait(1000);
cy.get('.empty-incidents-badge').should('be.visible');
cy.get('#global-search-input').clear();
});
@@ -67,13 +56,19 @@ describe('Search Incidents', { failFast: { enabled: false } }, () => {
checkActionAlertsModalContent('have been updated with a note');
selectIncident(incidentIdx);
cy.get('#global-search-input').clear().type('foobar');
- cy.wait(1000);
cy.get('[data-incident-header="Latest Note"]').each(($el) => {
// cy.wrap($el).should('have.text', 'foobar');
- cy.wrap($el).find('*').should((subElements) => {
- const elementWithFoobar = subElements.toArray().find((el) => el.textContent.includes('foobar'));
- assert.isNotNull(elementWithFoobar, 'Expected to find a subelement containing "foobar"');
- });
+ cy.wrap($el)
+ .find('*')
+ .should((subElements) => {
+ const elementWithFoobar = subElements
+ .toArray()
+ .find((el) => el.textContent.includes('foobar'));
+ assert.isNotNull(
+ elementWithFoobar,
+ 'Expected to find a subelement containing "foobar"',
+ );
+ });
});
});
cy.get('#global-search-input').clear();
@@ -81,7 +76,6 @@ describe('Search Incidents', { failFast: { enabled: false } }, () => {
it('Fuzzy search disabled does not return incident with note fuzzy match', () => {
cy.get('#global-search-input').clear().type('foobaz');
- cy.wait(1000);
cy.get('.empty-incidents-badge').should('be.visible');
cy.get('#global-search-input').clear();
});
@@ -93,12 +87,18 @@ describe('Search Incidents', { failFast: { enabled: false } }, () => {
cy.get(`@selectedIncidentId_${incidentIdx}`).then(() => {
cy.get('#global-search-input').clear().type('foobaz');
- cy.wait(1000);
cy.get('[data-incident-header="Latest Note"]').each(($el) => {
- cy.wrap($el).find('*').should((subElements) => {
- const elementWithFoobar = subElements.toArray().find((el) => el.textContent.includes('foobar'));
- assert.isNotNull(elementWithFoobar, 'Expected to find a subelement containing "foobar"');
- });
+ cy.wrap($el)
+ .find('*')
+ .should((subElements) => {
+ const elementWithFoobar = subElements
+ .toArray()
+ .find((el) => el.textContent.includes('foobar'));
+ assert.isNotNull(
+ elementWithFoobar,
+ 'Expected to find a subelement containing "foobar"',
+ );
+ });
});
});
cy.get('#global-search-input').clear();
@@ -107,7 +107,6 @@ describe('Search Incidents', { failFast: { enabled: false } }, () => {
it('Column filtering on Service column for `A1` returns incidents only on Service A1', () => {
cy.get('#service-filter-icon').realHover();
cy.get('input[placeholder="Filter"]').filter(':visible').click().type('A1');
- cy.wait(1000);
cy.get('[data-incident-header="Service"]').each(($el) => {
cy.wrap($el).should('have.text', 'Service A1');
});
diff --git a/cypress/e2e/Settings/settings.spec.js b/cypress/e2e/Settings/settings.spec.js
index 75e06573..beca04ef 100644
--- a/cypress/e2e/Settings/settings.spec.js
+++ b/cypress/e2e/Settings/settings.spec.js
@@ -17,25 +17,12 @@ import {
checkActionAlertsModalContent,
} from '../../support/util/common';
-describe('Manage Settings', { failFast: { enabled: false } }, () => {
+describe('Manage Settings', { failFast: { enabled: true } }, () => {
const localeCode = 'en-US';
moment.locale(localeCode);
- before(() => {
- acceptDisclaimer();
- // priorityNames.forEach((currentPriority) => {
- // activateButton(`query-priority-${currentPriority}-button`);
- // });
- waitForIncidentTable();
- });
-
beforeEach(() => {
- if (cy.state('test').currentRetry() > 1) {
- acceptDisclaimer();
- }
- // priorityNames.forEach((currentPriority) => {
- // activateButton(`query-priority-${currentPriority}-button`);
- // });
+ acceptDisclaimer();
waitForIncidentTable();
});
@@ -50,22 +37,29 @@ describe('Manage Settings', { failFast: { enabled: false } }, () => {
const expectedSinceDateFormat = moment().subtract(1, 'days').format('L');
const expectedIncidentDateFormat = moment().format('LL');
- updateUserLocale(localeName, 'Paramètres', 'Updated user profile settings');
+ updateUserLocale(localeName, 'Settings', 'Updated user profile settings');
cy.get('#query-date-input').should('contain', expectedSinceDateFormat);
cy.get('[data-incident-header="Created At"][data-incident-row-cell-idx="0"]')
.should('be.visible')
.should('contain', expectedIncidentDateFormat);
});
- ['1 Day', '3 Days', '1 Week', '2 Weeks', '1 Month', '3 Months', '6 Months'].forEach((tenor) => {
- it(`Update default since date lookback to ${tenor}`, () => {
- const [sinceDateNum, sinceDateTenor] = tenor.split(' ');
- const expectedDate = moment().subtract(Number(sinceDateNum), sinceDateTenor).format('L');
- updateDefaultSinceDateLookback(tenor);
- updateUserLocale('English (United States)', 'Settings', 'Updated user profile settings');
- cy.get('#query-date-input').should('contain', expectedDate);
- });
- });
+ // 1 Day is the default
+ ['Today', '1 Day', '3 Days', '1 Week', '2 Weeks', '1 Month', '3 Months', '180 Days'].forEach(
+ (tenor) => {
+ it(`Update default since date lookback to ${tenor}`, () => {
+ let [sinceDateNum, sinceDateTenor] = tenor.split(' ');
+ if (tenor === 'Today') {
+ sinceDateNum = '0';
+ sinceDateTenor = 'Day';
+ }
+ const expectedDate = moment().subtract(Number(sinceDateNum), sinceDateTenor).format('L');
+ updateDefaultSinceDateLookback(tenor);
+ updateUserLocale('English (United States)', 'Settings', 'Updated user profile settings');
+ cy.get('#query-date-input').should('contain', expectedDate);
+ });
+ },
+ );
it('Update max rate limit', () => {
const maxRateLimit = 600;
@@ -216,6 +210,22 @@ describe('Manage Settings', { failFast: { enabled: false } }, () => {
});
it('Save presets', () => {
+ updateDarkMode();
+ const columns = [
+ ['Teams', 'teams'],
+ ['Num Alerts', 'num_alerts'],
+ ['Group', 'service_group'],
+ ['Component', 'source_component'],
+ ];
+ manageIncidentTableColumns(
+ 'add',
+ columns.map((column) => column[1]),
+ );
+ columns
+ .map((column) => column[0])
+ .forEach((columnName) => {
+ cy.get(`[data-column-name="${columnName}"]`).scrollIntoView().should('be.visible');
+ });
cy.get('.settings-panel-dropdown').click();
cy.get('.dropdown-item').contains('Load/Save Presets').click();
cy.get('#save-presets-button').click();
@@ -223,13 +233,6 @@ describe('Manage Settings', { failFast: { enabled: false } }, () => {
cy.get('#close-button').click();
});
- it('Clear local cache', () => {
- cy.get('.settings-panel-dropdown').click();
- cy.get('.dropdown-item').contains('Clear Local Cache').click();
- cy.get('.modal-title').contains('Disclaimer & License').should('be.visible');
- acceptDisclaimer();
- });
-
it('Load presets', () => {
cy.get('.settings-panel-dropdown').click();
cy.get('.dropdown-item').contains('Load/Save Presets').click();
diff --git a/cypress/e2e/app.spec.js b/cypress/e2e/app.spec.js
index d9108cc9..f0f5ca8f 100644
--- a/cypress/e2e/app.spec.js
+++ b/cypress/e2e/app.spec.js
@@ -1,12 +1,15 @@
import moment from 'moment/min/moment-with-locales';
import {
- acceptDisclaimer, waitForIncidentTable, pd,
+ acceptDisclaimer,
+ waitForIncidentTable,
+ clearLocalCache,
+ pd,
} from '../support/util/common';
import packageConfig from '../../package.json';
-describe('Integration User Token', { failFast: { enabled: false } }, () => {
+describe('Integration User Token', { failFast: { enabled: true } }, () => {
before(() => {
expect(Cypress.env('PD_USER_TOKEN')).to.be.a('string');
cy.intercept('GET', 'https://api.pagerduty.com/users/me').as('getCurrentUser');
@@ -28,19 +31,12 @@ describe('Integration User Token', { failFast: { enabled: false } }, () => {
});
});
-describe('PagerDuty Live', () => {
- before(() => {
+describe('PagerDuty Live', { failFast: { enabled: true } }, () => {
+ beforeEach(() => {
acceptDisclaimer();
waitForIncidentTable();
});
- beforeEach(() => {
- if (cy.state('test').currentRetry() > 1) {
- acceptDisclaimer();
- waitForIncidentTable();
- }
- });
-
it('Renders the main application page', () => {
cy.get('#navbar-ctr').contains('Live Incidents Console');
});
@@ -62,7 +58,7 @@ describe('PagerDuty Live', () => {
cy.intercept('https://api.pagerduty.com/abilities*', {
abilities: ['teams', 'read_only_users', 'service_support_hours', 'urgencies'],
}).as('getAbilities');
- cy.visit('http://localhost:3000/pd-live-react');
+ clearLocalCache();
acceptDisclaimer();
cy.wait('@getAbilities', { timeout: 30000 });
@@ -75,12 +71,7 @@ describe('PagerDuty Live', () => {
});
it('Application indicates when polling is disabled through url parameter disable-polling', () => {
- cy.visit('http://localhost:3000/pd-live-react/?disable-polling=true');
-
- // cy.get('.modal-title', { timeout: 30000 }).contains('Disclaimer & License');
- // cy.get('#disclaimer-agree-checkbox').click({ force: true });
- // cy.get('#disclaimer-accept-button').click({ force: true });
-
+ cy.visit('/?disable-polling=true');
cy.get('.status-beacon-ctr').realHover();
cy.get('[data-popper-placement="bottom"]').should('be.visible');
cy.get('[data-popper-placement="bottom"]').contains('Live updates disabled');
@@ -101,9 +92,7 @@ describe('PagerDuty Live', () => {
].join(''),
).as('getUrl');
- cy.visit(
- `http://localhost:3000/pd-live-react/?disable-polling=true&since=${since}&until=${until}`,
- );
+ cy.visit(`/?disable-polling=true&since=${since}&until=${until}`);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(5000);
@@ -114,4 +103,13 @@ describe('PagerDuty Live', () => {
cy.get('#since-date-input').should('be.disabled');
cy.get('#until-date-input').should('be.disabled');
});
+
+ it('Application correctly loads iframe for extra buttons when configured', () => {
+ cy.visit('/?button=TestExtra,https://example.com');
+ cy.get('button').contains('TestExtra').should('be.visible');
+ cy.get('button').contains('TestExtra').click();
+ cy.get('[data-popper-placement="top"]').should('be.visible');
+ cy.get('iframe[title="TestExtra"]');
+ // would need to enable cross-domain iframe javascript access to test further
+ });
});
diff --git a/cypress/support/util/common.js b/cypress/support/util/common.js
index bfc24090..f8ca5d7d 100644
--- a/cypress/support/util/common.js
+++ b/cypress/support/util/common.js
@@ -1,4 +1,3 @@
-/* eslint-disable cypress/no-unnecessary-waiting */
/* eslint-disable cypress/unsafe-to-chain-command */
/* eslint-disable import/prefer-default-export */
import {
@@ -11,9 +10,6 @@ export const pd = api({ token: Cypress.env('PD_USER_TOKEN') });
Cypress Helpers
*/
export const acceptDisclaimer = () => {
- cy.clearLocalStorage();
- cy.clearAllSessionStorage();
- cy.clearCookies();
cy.visit('/');
cy.get('.modal-title', { timeout: 30000 }).contains('Disclaimer & License');
cy.get('#disclaimer-agree-checkbox').click({ force: true });
@@ -22,12 +18,23 @@ export const acceptDisclaimer = () => {
export const waitForIncidentTable = () => {
// Ref: https://stackoverflow.com/a/60065672/6480733
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(3000); // Required for query debounce
cy.get('#incident-table-ctr', { timeout: 60000 }).should('be.visible');
+ cy.get('.selected-incidents-ctr', { timeout: 60000 }).should('not.include.text', 'Querying');
// will move on to next command even if table is not scrollable
cy.get('.incident-table-fixed-list').scrollTo('top', { ensureScrollable: false });
};
+export const waitForAlerts = () => {
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
+ cy.wait(3000); // Required for query debounce
+ cy.get('.selected-incidents-ctr', { timeout: 60000 }).should(
+ 'not.include.text',
+ 'Fetching Alerts',
+ );
+};
+
export const selectIncident = (incidentIdx = 0, shiftKey = false) => {
const selector = `[data-incident-row-idx="${incidentIdx}"]`;
cy.get(selector).invoke('attr', 'data-incident-id').as(`selectedIncidentId_${incidentIdx}`);
@@ -45,7 +52,7 @@ export const selectAllIncidents = () => {
};
export const checkNoIncidentsSelected = () => {
- cy.get('.selected-incidents-badge').then(($el) => {
+ cy.get('.selected-incidents-badge').should(($el) => {
const text = $el.text();
const incidentNumbers = text.split(' ')[0].split('/');
expect(incidentNumbers[0]).to.equal('0');
@@ -53,12 +60,10 @@ export const checkNoIncidentsSelected = () => {
};
export const checkActionAlertsModalContent = (content) => {
- cy.wait(2000);
cy.get('.chakra-alert__title').contains(content, { timeout: 10000 });
};
export const checkPopoverContent = (incidentId, incidentHeader, content) => {
- cy.wait(2000);
cy.get(
`[data-incident-header="${incidentHeader}"][data-incident-cell-id="${incidentId}"]`,
).within(() => {
@@ -68,14 +73,12 @@ export const checkPopoverContent = (incidentId, incidentHeader, content) => {
};
export const checkIncidentCellContent = (incidentId, incidentHeader, content) => {
- cy.wait(2000);
cy.get(`[data-incident-header="${incidentHeader}"][data-incident-cell-id="${incidentId}"]`)
.should('be.visible')
.contains(content);
};
export const checkIncidentCellContentAllRows = (incidentHeader, content) => {
- cy.wait(2000);
cy.get('.incident-table-fixed-list').scrollTo('top', { ensureScrollable: true });
cy.get('.incident-table-fixed-list > div').then(($tbody) => {
const visibleIncidentCount = $tbody.find('[role="row"]').length;
@@ -101,7 +104,6 @@ export const checkIncidentCellIcon = (incidentIdx, incidentHeader, icon) => {
};
export const checkIncidentCellIconAllRows = (incidentHeader, icon) => {
- cy.wait(2000);
cy.get('.incident-table-fixed-list > div').then(($tbody) => {
const visibleIncidentCount = $tbody.find('[role="row"]').length;
for (let incidentIdx = 0; incidentIdx < visibleIncidentCount; incidentIdx++) {
@@ -111,7 +113,6 @@ export const checkIncidentCellIconAllRows = (incidentHeader, icon) => {
};
export const checkIncidentCellContentHasLink = (incidentId, incidentHeader, text, link) => {
- cy.wait(2000);
cy.get(`[data-incident-header="${incidentHeader}"][data-incident-cell-id="${incidentId}"]`)
.should('be.visible')
.contains('a', text)
@@ -144,8 +145,10 @@ export const escalate = (escalationLevel) => {
export const reassign = (assignment) => {
cy.get('#incident-action-reassign-button').click();
cy.get('#reassign-select').click();
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(200);
cy.contains('.react-select__option', assignment).click({ force: true });
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(200);
cy.get('#reassign-button').click({ force: true });
};
@@ -198,6 +201,7 @@ export const addNote = (note) => {
};
const toggleRunAction = () => {
+ // eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000); // Unsure why we can't find DOM of action without wait
cy.get('.incident-action-run-action-button').click();
};
@@ -285,8 +289,7 @@ export const updateDefaultSinceDateLookback = (tenor = '1 Day') => {
cy.get('#save-settings-button').click();
checkActionAlertsModalContent('Updated user profile settings');
- cy.reload();
- acceptDisclaimer();
+ cy.visit('/');
};
export const updateAutoRefreshInterval = (autoRefreshInterval = 5) => {
@@ -345,6 +348,11 @@ export const updateDarkMode = () => {
cy.get('[aria-label="Toggle Dark Mode"]').click();
};
+export const clearLocalCache = () => {
+ cy.get('.settings-panel-dropdown').click();
+ cy.get('.dropdown-item').contains('Clear Local Cache').click();
+};
+
export const priorityNames = ['--', 'P5', 'P4', 'P3', 'P2', 'P1'];
/*
diff --git a/jest.config.js b/jest.config.js
index a844009b..f824c75a 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -14,5 +14,7 @@ module.exports = {
'^.+\\.(js|jsx|ts|tsx|mjs)$': 'babel-jest',
'^.+\\.svg$': 'jest-transformer-svg',
},
- transformIgnorePatterns: ['/node_modules/(?!(somePkg)|react-dnd|dnd-core|@react-dnd|jsonpath-plus)'],
+ transformIgnorePatterns: [
+ '/node_modules/(?!(somePkg)|react-dnd|dnd-core|@react-dnd|jsonpath-plus)',
+ ],
};
diff --git a/package.json b/package.json
index a3f74fdb..a7aa4000 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "pd-live-react",
"homepage": "https://pagerduty.github.io/pd-live-react",
- "version": "0.11.0-beta.0",
+ "version": "0.11.1-beta.0",
"private": true,
"dependencies": {
"@chakra-ui/icons": "^2.1.1",
@@ -16,10 +16,10 @@
"@fortawesome/react-fontawesome": "^0.2.0",
"@pagerduty/pdjs": "^2.2.3",
"@types/jest": "^29.5.4",
- "@types/node": "^20.5.7",
+ "@types/node": "^20.10.8",
"@types/react": "^18.2.21",
- "@types/react-dom": "^18.2.7",
- "axios": "^1.6.0",
+ "@types/react-dom": "^18.2.17",
+ "axios": "^1.6.2",
"bootstrap": "^4.6.2",
"bottleneck": "^2.19.5",
"chakra-react-select": "^4.7.0",
@@ -31,21 +31,21 @@
"i18next-browser-languagedetector": "^7.1.0",
"immer": "^10.0.2",
"jsonpath-plus": "^7.2.0",
- "linkify-react": "^4.1.1",
- "linkifyjs": "^4.1.1",
+ "linkify-react": "^4.1.3",
+ "linkifyjs": "^4.1.3",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"pretty-print-error": "^1.1.1",
"react": "^18",
"react-bootstrap": "^2.9.1",
"react-contextmenu": "^2.14.0",
- "react-datepicker": "^4.16.0",
+ "react-datepicker": "^4.21.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dom": "^18",
"react-i18next": "^13.2.0",
"react-icons": "^4.9.0",
- "react-intersection-observer": "^9.5.2",
+ "react-intersection-observer": "^9.5.3",
"react-minimal-pie-chart": "^8.4.0",
"react-redux": "^8.1.2",
"react-select": "^5.7.7",
@@ -64,10 +64,10 @@
"build": "npx genversion src/config/version.js --semi --es6 && vite build",
"genversion": "npx genversion src/config/version.js --semi --es6",
"jest": "npx jest",
- "cypress:open": "npx cypress open",
- "cypress:run:local": "npx cypress run",
- "cypress:run:record": "npx cypress run --record --key ${CYPRESS_RECORD_KEY}",
- "cypress:run:ci": "npx cypress run --group 'e2e' --browser chrome --headless --parallel --record --key ${CYPRESS_RECORD_KEY} --ci-build-id $(date +'%s')",
+ "cypress:open": "npx cypress open --browser chrome --e2e",
+ "cypress:run:local": "npx cypress run --browser chrome --e2e",
+ "cypress:run:record": "npx cypress run --browser chrome --e2e --record --key ${CYPRESS_RECORD_KEY}",
+ "cypress:run:ci": "npx cypress run --browser chrome --e2e --headless --parallel --record --group 'e2e' --key ${CYPRESS_RECORD_KEY} --ci-build-id $(date +'%s')",
"preview": "vite preview",
"lint": "npx eslint . --ext .js,.jsx,.ts,.tsx --exit-on-fatal-error",
"format": "npx prettier-eslint --write '*.js' 'src/**/*.js' 'src/**/*.jsx' 'cypress/**/*.js'",
@@ -95,32 +95,32 @@
]
},
"devDependencies": {
- "@4tw/cypress-drag-drop": "^2.2.4",
+ "@4tw/cypress-drag-drop": "^2.2.5",
"@babel/core": "^7.22.17",
"@babel/eslint-parser": "^7.22.10",
"@babel/preset-env": "^7.22.10",
"@babel/preset-react": "^7.22.5",
"@cypress/react": "^8.0.0",
"@faker-js/faker": "^8.0.2",
- "@testing-library/dom": "^9.3.0",
+ "@testing-library/dom": "^9.3.3",
"@testing-library/jest-dom": "^6.1.4",
- "@testing-library/react": "^14.0.0",
+ "@testing-library/react": "^14.1.2",
"@testing-library/react-hooks": "^8.0.1",
"@testing-library/user-event": "^14.4.3",
"@vitejs/plugin-react": "^4.0.4",
"@welldone-software/why-did-you-render": "^7.0.1",
"babel-jest": "^29.6.3",
- "cypress": "^12.17.1",
- "cypress-fail-fast": "^7.0.0",
- "cypress-real-events": "^1.10.3",
+ "cypress": "^13.5.1",
+ "cypress-fail-fast": "^7.0.3",
+ "cypress-real-events": "^1.11.0",
"dotenv": "^16.3.1",
"eslint": "^8.43.0",
"eslint-config-airbnb": "^18.2.1",
- "eslint-config-prettier": "^8.8.0",
+ "eslint-config-prettier": "^9.0.0",
"eslint-config-react-app": "^7.0.1",
"eslint-import-resolver-alias": "^1.1.2",
- "eslint-plugin-cypress": "^2.14.0",
- "eslint-plugin-import": "^2.28.1",
+ "eslint-plugin-cypress": "^2.15.1",
+ "eslint-plugin-import": "^2.29.0",
"eslint-plugin-jsx": "^0.1.0",
"eslint-plugin-jsx-a11y": "^6.7.1",
"eslint-plugin-prettier": "^4.2.1",
@@ -135,21 +135,21 @@
"jest": "^29.6.3",
"jest-canvas-mock": "^2.5.2",
"jest-environment-jsdom": "^29.7.0",
- "jest-environment-node": "^29.6.3",
+ "jest-environment-node": "^29.7.0",
"jest-location-mock": "^1.0.10",
"jest-transformer-svg": "^2.0.1",
- "prettier": "^2.8.0",
- "prettier-eslint": "^15.0.1",
- "prettier-eslint-cli": "^7.1.0",
+ "prettier": "^3.1.0",
+ "prettier-eslint": "^16.1.2",
+ "prettier-eslint-cli": "^8.0.1",
"redux-mock-store": "^1.5.4",
"redux-saga-test-plan": "^4.0.6",
"sass": "^1.66.1",
"string.prototype.replaceall": "^1.0.6",
- "vite": "^4.4.9",
+ "vite": "^4.4.12",
"vite-plugin-environment": "^1.1.3",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-svgr": "^3.2.0",
- "wait-on": "^7.0.1",
- "yarn-audit-fix": "^10.0.0"
+ "wait-on": "^7.2.0",
+ "yarn-audit-fix": "^10.0.5"
}
}
diff --git a/src/App.jsx b/src/App.jsx
index e63b45a5..92abc5d4 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -206,11 +206,14 @@ const App = () => {
// Setup log entry clearing
useEffect(() => {
- const clearingInterval = setInterval(() => {
- if (userAuthorized) {
- cleanRecentLogEntriesAsync();
- }
- }, 60 * 60 * 1000);
+ const clearingInterval = setInterval(
+ () => {
+ if (userAuthorized) {
+ cleanRecentLogEntriesAsync();
+ }
+ },
+ 60 * 60 * 1000,
+ );
return () => clearInterval(clearingInterval);
}, [userAuthorized]);
diff --git a/src/components/Auth/AuthComponent.jsx b/src/components/Auth/AuthComponent.jsx
index b2f730de..038c78be 100644
--- a/src/components/Auth/AuthComponent.jsx
+++ b/src/components/Auth/AuthComponent.jsx
@@ -30,6 +30,7 @@ const AuthComponent = (props) => {
const code = urlParams.get('code');
const subdomain = urlParams.get('subdomain');
const region = urlParams.get('service_region') || 'us';
+ const buttons = urlParams.getAll('button');
let codeVerifier = sessionStorage.getItem('code_verifier');
let {
@@ -50,12 +51,23 @@ const AuthComponent = (props) => {
(token) => {
sessionStorage.removeItem('code_verifier');
sessionStorage.setItem('pd_access_token', token);
- window.location.assign(redirectURL);
+ // if there were button params on the first load, load the button params and put them back on the URL
+ const savedButtonsStr = sessionStorage.getItem('pd_buttons');
+ const savedButtons = savedButtonsStr ? JSON.parse(savedButtonsStr) : [];
+ const buttonParams = savedButtons ? `?button=${savedButtons.join('&button=')}` : '';
+ window.location.assign(redirectURL + buttonParams);
},
);
} else if (!accessToken) {
codeVerifier = createCodeVerifier();
sessionStorage.setItem('code_verifier', codeVerifier);
+ // if the user wants to use a button, save the button params in session storage
+ // (because we can't pass them through the OAuth flow)
+ if (buttons.length > 0) {
+ sessionStorage.setItem('pd_buttons', JSON.stringify(buttons));
+ } else {
+ sessionStorage.removeItem('pd_buttons');
+ }
getAuthURL(clientId, clientSecret, redirectURL, codeVerifier).then((url) => {
const subdomainParams = subdomain ? `&subdomain=${subdomain}&service_region=${region}` : '';
setAuthURL(`${url}${subdomainParams}`);
diff --git a/src/components/IncidentActions/IncidentActionsComponent.jsx b/src/components/IncidentActions/IncidentActionsComponent.jsx
index cc89063a..69e21c2b 100644
--- a/src/components/IncidentActions/IncidentActionsComponent.jsx
+++ b/src/components/IncidentActions/IncidentActionsComponent.jsx
@@ -4,6 +4,10 @@ import {
Box, Flex,
} from '@chakra-ui/react';
+import {
+ EXTRA_BUTTONS,
+} from 'src/config/constants';
+
import SelectedIncidentsComponent from './subcomponents/SelectedIncidentsComponent';
import AcknowledgeButton from './subcomponents/AcknowledgeButton';
@@ -16,6 +20,7 @@ import EscalateMenu from './subcomponents/EscalateMenu';
import SnoozeMenu from './subcomponents/SnoozeMenu';
import PriorityMenu from './subcomponents/PriorityMenu';
import RunActionMenu from './subcomponents/RunActionMenu';
+import ExtraButton from './subcomponents/ExtraButton';
import './IncidentActionsComponent.scss';
@@ -46,6 +51,14 @@ const IncidentActionsComponent = () => (