Skip to content

Commit

Permalink
Merge pull request #223 from giranm/release/v0.3.0-beta.0
Browse files Browse the repository at this point in the history
[Release] v0.3.0-beta.0
  • Loading branch information
giranm authored Aug 14, 2022
2 parents 5d0596d + b03322e commit e05b3ec
Show file tree
Hide file tree
Showing 29 changed files with 575 additions and 154 deletions.
60 changes: 31 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This is an open-source project designed to be used in a safe/test environment be
## Screenshot

<kbd>
<img width="1625" alt="Screenshot 2021-11-05 at 18 35 16" src="https://user-images.githubusercontent.com/20474443/140561598-d771ea60-157c-4fc6-aaa7-af31765f955f.png">
<img width="1625" alt="Screenshot 2021-11-05 at 18 35 16" src="https://user-images.githubusercontent.com/20474443/184557375-6f2e18ac-bc0a-4ea7-a9b7-800d59f02ec6.png">
</kbd>

## Development
Expand Down Expand Up @@ -64,42 +64,44 @@ The following scripts have been created to run unit, component, and integration
- `$ yarn jest` (Jest Unit/Component)
- `$ yarn cypress:run:local` (Cypress Integration with headless Chromedriver)

Please note that running integration tests will require environment variable `REACT_APP_PD_USER_TOKEN` set.
Please note that running integration tests will require environment variable `REACT_APP_PD_USER_TOKEN` set.

The integration tests also assume the PagerDuty account associated with the above user token has been setup with the following [Terraform environment](https://github.com/giranm/pd-live-integration-test-environment).

## Versioning & Release
To prepare PagerDuty Live for release, the current workflow should be carried out:
1. Checkout to `develop` branch and verify if it's stable - i.e. no test and linting failures.

2. Update version information in `package.json` using `npm version` - example commands given below:
- Bumping patch version for beta release
```
$ npm --no-git-tag-version version preminor --preid beta
v0.1.0-beta.0
```
- Bumping minor version for main release
```
$ npm --no-git-tag-version version minor
v0.2.0
```
3. Update application code version using `$ yarn genversion`
4. Prepare the appropriate release branch via `$ git checkout -b release/<VERSION>` (following above [semver](https://semver.org/))
5. Stage modified files, commit, and push changes upstream:

To prepare PagerDuty Live for release, the current workflow should be carried out:

1. Checkout to `develop` branch and verify if it's stable - i.e. no test and linting failures.

2. Update version information in `package.json` using `npm version` - example commands given below:

- Bumping patch version for beta release
```
$ npm --no-git-tag-version version preminor --preid beta
v0.1.0-beta.0
```
$ git add .
$ git commit -m "Publishing release <VERSION>"
$ git push --set-upstream origin release/<VERSION>
- Bumping minor version for main release
```
6. Raise [pull requests](https://github.com/giranm/pd-live-react/pulls) to merge into `main` branch.
$ npm --no-git-tag-version version minor
v0.2.0
```
3. Update application code version using `$ yarn genversion`
4. Prepare the appropriate release branch via `$ git checkout -b release/<VERSION>` (following above [semver](https://semver.org/))
5. Stage modified files, commit, and push changes upstream:
```
$ git add .
$ git commit -m "Publishing release <VERSION>"
$ git push --set-upstream origin release/<VERSION>
```
7. Build static bundle via `$ yarn build` or use appropriate CI/CD workflow.
6. Raise [pull requests](https://github.com/giranm/pd-live-react/pulls) to merge into `main` branch.
7. Build static bundle via `$ yarn build` or use appropriate CI/CD workflow.
## License
Expand Down
27 changes: 24 additions & 3 deletions cypress/integration/Query/query.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ moment.locale('en-GB');
describe('Query Incidents', { failFast: { enabled: false } }, () => {
before(() => {
acceptDisclaimer();
manageIncidentTableColumns('remove', ['Assignees']);
manageIncidentTableColumns('remove', ['Latest Note']);
manageIncidentTableColumns('add', ['Urgency', 'Teams']);
priorityNames.forEach((currentPriority) => {
activateButton(`query-priority-${currentPriority}-button`);
Expand All @@ -34,7 +34,7 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
beforeEach(() => {
if (cy.state('test').currentRetry() > 1) {
acceptDisclaimer();
manageIncidentTableColumns('remove', ['Assignees']);
manageIncidentTableColumns('remove', ['Latest Note']);
manageIncidentTableColumns('add', ['Urgency', 'Teams']);
}
priorityNames.forEach((currentPriority) => {
Expand Down Expand Up @@ -202,7 +202,7 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
cy.get('#query-service-select').click().type('{del}');
});

it('Query on Team A only allows further querying for associated services', () => {
it('Query on Team A only allows further querying for associated services and users', () => {
cy.get('#query-team-select').click().type('Team A{enter}');
waitForIncidentTable();

Expand All @@ -212,9 +212,30 @@ describe('Query Incidents', { failFast: { enabled: false } }, () => {
expect(body.find(`[class*="-option"]:contains("${service}")`).length).to.equal(1);
});
});

cy.get('#query-user-select').click();
cy.get('body').then((body) => {
['User A1', 'User A2', 'User A3'].forEach((user) => {
expect(body.find(`[class*="-option"]:contains("${user}")`).length).to.equal(1);
});
});

cy.get('#query-team-select').click().type('{del}');
});

it('Query for incidents assigned to User A1, A2, or A3', () => {
cy.get('#query-user-select')
.click()
.type('User A1{enter}')
.type('User A2{enter}')
.type('User A3{enter}');

waitForIncidentTable();
checkIncidentCellContentAllRows('Assignees', 'UA');

cy.get('#query-user-select').click().type('{del}{del}{del}');
});

it('Sort incident column "#" by ascending order', () => {
cy.get('[data-column-name="#"]')
.click()
Expand Down
1 change: 1 addition & 0 deletions cypress/support/util/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const acceptDisclaimer = () => {

export const waitForIncidentTable = () => {
// Ref: https://stackoverflow.com/a/60065672/6480733
cy.wait(3000); // Required for query debounce
cy.get('.incident-table-ctr', { timeout: 60000 }).should('be.visible');
};

Expand Down
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ module.exports = {
},
transform: {
'^.+\\.[t|j]sx?$': 'babel-jest',
'^.+\\.svg$': 'svg-jest',
},
};
12 changes: 8 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
{
"name": "pd-live-react",
"homepage": "https://giranm.github.io/pd-live-react",
"version": "0.2.1-beta.0",
"version": "0.3.0-beta.0",
"private": true,
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"@craco/craco": "7.0.0-alpha.3",
"@datadog/browser-rum": "^4.16.1",
"@datadog/browser-rum": "^4.17.1",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-brands-svg-icons": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
Expand All @@ -14,7 +15,7 @@
"@pagerduty/pdjs": "^2.2.3",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^12.1.5",
"@testing-library/user-event": "^14.3.0",
"@testing-library/user-event": "^14.4.3",
"@wojtekmaj/enzyme-adapter-react-17": "^0.6.7",
"autoprefixer": "10.4.8",
"axios": "^0.27.2",
Expand Down Expand Up @@ -47,6 +48,7 @@
"redux-saga": "^1.1.3",
"styled-components": "^5.3.5",
"use-debounce": "^8.0.3",
"validator": "^13.7.0",
"web-vitals": "^2.1.4"
},
"resolutions": {
Expand Down Expand Up @@ -91,7 +93,7 @@
"@babel/preset-env": "^7.18.9",
"@babel/preset-react": "^7.16.7",
"@cypress/react": "5.12.4",
"@cypress/webpack-dev-server": "^2.0.0",
"@cypress/webpack-dev-server": "^2.1.0",
"@faker-js/faker": "^7.4.0",
"cy2": "^2.0.0",
"cypress": "^9.2.1",
Expand All @@ -118,6 +120,8 @@
"redux-mock-store": "^1.5.4",
"redux-saga-test-plan": "^4.0.5",
"resolve-url-loader": "^5.0.0",
"string.prototype.replaceall": "^1.0.6",
"svg-jest": "^1.0.1",
"wait-on": "^6.0.1",
"yarn-audit-fix": "^9.3.3"
}
Expand Down
4 changes: 4 additions & 0 deletions setupTests.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ import {
} from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';

// eslint-disable-next-line import/no-extraneous-dependencies
import replaceAllInserter from 'string.prototype.replaceall';

configure({ adapter: new Adapter() });
replaceAllInserter.shim();
30 changes: 24 additions & 6 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ const App = ({
autoRefreshInterval,
} = state.settings;
const {
fetchingIncidents, fetchingIncidentNotes, fetchingIncidentAlerts, refreshingIncidents,
fetchingIncidents,
fetchingIncidentNotes,
fetchingIncidentAlerts,
refreshingIncidents,
lastFetchDate,
} = state.incidents;
const queryError = state.querySettings.error;
useEffect(() => {
Expand All @@ -134,17 +138,31 @@ const App = ({
// Setup log entry polling
useEffect(
() => {
let useLastFetchDate = true;
const pollingInterval = setInterval(() => {
checkAbilities();
checkConnectionStatus();
const {
abilities,
} = store.getState().connection;
if (userAuthorized && abilities.includes(PD_REQUIRED_ABILITY) && !queryError) {
const lastPolledDate = moment()
.subtract(2 * LOG_ENTRIES_POLLING_INTERVAL_SECONDS, 'seconds')
.toDate();
getLogEntriesAsync(lastPolledDate);
// Determine lookback based on last fetch/refresh of incidents
if (
!fetchingIncidents
&& !fetchingIncidentNotes
&& !fetchingIncidentAlerts
&& !refreshingIncidents
) {
if (useLastFetchDate) {
getLogEntriesAsync(lastFetchDate);
} else {
const lastPolledDate = moment()
.subtract(2 * LOG_ENTRIES_POLLING_INTERVAL_SECONDS, 'seconds')
.toDate();
getLogEntriesAsync(lastPolledDate);
}
useLastFetchDate = false;
}
}
}, LOG_ENTRIES_POLLING_INTERVAL_SECONDS * 1000);
return () => clearInterval(pollingInterval);
Expand Down Expand Up @@ -233,7 +251,7 @@ const mapDispatchToProps = (dispatch) => ({
getServicesAsync: (teamIds) => dispatch(getServicesAsyncConnected(teamIds)),
getTeamsAsync: () => dispatch(getTeamsAsyncConnected()),
getPrioritiesAsync: () => dispatch(getPrioritiesAsyncConnected()),
getUsersAsync: () => dispatch(getUsersAsyncConnected()),
getUsersAsync: (teamIds) => dispatch(getUsersAsyncConnected(teamIds)),
getEscalationPoliciesAsync: () => dispatch(getEscalationPoliciesAsyncConnected()),
getResponsePlaysAsync: () => dispatch(getResponsePlaysAsyncConnected()),
getLogEntriesAsync: (since) => dispatch(getLogEntriesAsyncConnected(since)),
Expand Down
1 change: 1 addition & 0 deletions src/components/Auth/AuthComponent.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('AuthComponent', () => {
wrapper.find(Button).simulate('click');
expect(window.location.assign).toBeCalled();
// FIX ME: This assertion doesn't work within Jest for some reason
// eslint-disable-next-line max-len
// expect(window.location.href).toContain('https://app.pagerduty.com/global/authn/authentication');
});
});
106 changes: 106 additions & 0 deletions src/components/IncidentTable/IncidentTableComponent.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import '@testing-library/jest-dom';

import {
sanitizeUrl,
} from '@braintree/sanitize-url';
import validator from 'validator';

import {
mockStore, componentWrapper,
} from 'mocks/store.test';

import {
generateMockIncidents,
} from 'mocks/incidents.test';

import IncidentTableComponent from './IncidentTableComponent';

describe('IncidentTableComponent', () => {
let baseStore;
let store;
// FIXME: Jest can only render max of 3 incidents for some reason?
const mockIncidents = generateMockIncidents(3);

beforeEach(() => {
baseStore = {
incidentTable: {
incidentTableState: {},
incidentTableColumns: [
{
Header: '#',
width: 60,
columnType: 'incident',
},
{
Header: 'Status',
width: 100,
columnType: 'incident',
},
{
Header: 'Title',
width: 400,
columnType: 'incident',
},
{
Header: 'quote',
accessorPath: 'details.quote',
aggregator: null,
width: 100,
columnType: 'alert',
},
{
Header: 'link',
accessorPath: 'details.link',
aggregator: null,
width: 100,
columnType: 'alert',
},
],
},
incidentActions: {
status: '',
},
incidents: {
filteredIncidentsByQuery: mockIncidents,
fetchingIncidents: false,
},
querySettings: {
displayConfirmQueryModal: false,
},
};
});

it('should render incident table with non-empty data', () => {
store = mockStore(baseStore);
const wrapper = componentWrapper(store, IncidentTableComponent);
expect(wrapper.find('.incident-table-ctr')).toBeTruthy();
expect(
wrapper
.find('.th')
.getElements()
.filter((th) => th.key.includes('header')),
).toHaveLength(baseStore.incidentTable.incidentTableColumns.length + 1); // Include selection header
expect(
wrapper
.find('[role="row"]')
.getElements()
.filter((tr) => tr.key.includes('row')),
).toHaveLength(mockIncidents.length);
});

it('should render cell with valid hyperlink for custom detail field', () => {
store = mockStore(baseStore);
const wrapper = componentWrapper(store, IncidentTableComponent);

const incidentNumber = 1;
const customDetailField = 'link';
const url = wrapper
.find('[role="row"]')
.get(incidentNumber)
.props.children.find((td) => td.key.includes(customDetailField)).props.children.props
.cell.value;
const sanitizedUrl = sanitizeUrl(url);

expect(validator.isURL(sanitizedUrl)).toBeTruthy();
});
});
Loading

0 comments on commit e05b3ec

Please sign in to comment.