From 8c658d99a124dbf5f32d575392f9fe662f087e40 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Tue, 14 Feb 2023 18:00:10 +0000 Subject: [PATCH 01/21] fix: upgrade axios from 1.2.3 to 1.2.4 Snyk has created this PR to upgrade axios from 1.2.3 to 1.2.4. See this package in npm: See this project in Snyk: https://app.snyk.io/org/giranm/project/d64f0b69-278b-4bed-90e9-845c5a282b48?utm_source=github&utm_medium=referral&page=upgrade-pr --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 37c4458d..709965ca 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@testing-library/user-event": "^14.4.3", "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", "autoprefixer": "10.4.13", - "axios": "^1.2.2", + "axios": "^1.2.4", "babel-eslint": "^10.1.0", "bootstrap": "^4.6.2", "bottleneck": "^2.19.5", diff --git a/yarn.lock b/yarn.lock index 4e911e98..e937c67c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3810,10 +3810,10 @@ axios@^0.27.2: follow-redirects "^1.14.9" form-data "^4.0.0" -axios@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.2.3.tgz#31a3d824c0ebf754a004b585e5f04a5f87e6c4ff" - integrity sha512-pdDkMYJeuXLZ6Xj/Q5J3Phpe+jbGdsSzlQaFVkMQzRUL05+6+tetX8TV3p4HrU4kzuO9bt+io/yGQxuyxA/xcw== +axios@^1.2.4: + version "1.3.3" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.3.tgz#e7011384ba839b885007c9c9fae1ff23dceb295b" + integrity sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" From fb2d6dd5db95ccc3096582d94bad494a7297fa1b Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Sat, 4 Mar 2023 17:56:53 +0000 Subject: [PATCH 02/21] fix: package.json & yarn.lock to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-STYLEDCOMPONENTS-3149924 --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 9763c12f..808a821d 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "redux": "^4.2.1", "redux-persist": "^6.0.0", "redux-saga": "^1.2.1", - "styled-components": "^5.3.6", + "styled-components": "^5.3.7", "use-debounce": "^9.0.3", "validator": "^13.7.0", "web-vitals": "^3.1.0" diff --git a/yarn.lock b/yarn.lock index 4e911e98..65815066 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12692,10 +12692,10 @@ style-loader@^3.3.1: resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-3.3.1.tgz#057dfa6b3d4d7c7064462830f9113ed417d38575" integrity sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ== -styled-components@^5.3.6: - version "5.3.6" - resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.6.tgz#27753c8c27c650bee9358e343fc927966bfd00d1" - integrity sha512-hGTZquGAaTqhGWldX7hhfzjnIYBZ0IXQXkCYdvF1Sq3DsUaLx6+NTHC5Jj1ooM2F68sBiVz3lvhfwQs/S3l6qg== +styled-components@^5.3.7: + version "5.3.8" + resolved "https://registry.yarnpkg.com/styled-components/-/styled-components-5.3.8.tgz#b92e2d10352dc9ecf3b1bc5d27c7500832dcf870" + integrity sha512-6jQrlvaJQ16uWVVO0rBfApaTPItkqaG32l3746enNZzpMDxMvzmHzj8rHUg39bvVtom0Y8o8ZzWuchEXKGjVsg== dependencies: "@babel/helper-module-imports" "^7.0.0" "@babel/traverse" "^7.4.5" From a5f0c57a24ed395bc784df3643f147488234045f Mon Sep 17 00:00:00 2001 From: Martin Stone Date: Tue, 28 Mar 2023 15:31:48 -0400 Subject: [PATCH 03/21] CSV export --- package.json | 1 + .../IncidentTable/IncidentTableComponent.js | 131 +++++++++++++----- .../IncidentTable/IncidentTableComponent.scss | 6 + yarn.lock | 8 ++ 4 files changed, 110 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 9763c12f..e5c89ba1 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "node-sass": "^7.0.3", "react": "^17.0.2", "react-bootstrap": "^1.6.6", + "react-contextmenu": "^2.14.0", "react-datepicker": "^4.10.0", "react-dom": "^17.0.2", "react-dual-listbox": "^4.0.0", diff --git a/src/components/IncidentTable/IncidentTableComponent.js b/src/components/IncidentTable/IncidentTableComponent.js index 3932be79..4bde9bf8 100644 --- a/src/components/IncidentTable/IncidentTableComponent.js +++ b/src/components/IncidentTable/IncidentTableComponent.js @@ -35,6 +35,12 @@ import { getReactTableColumnSchemas, } from 'config/incident-table-columns'; +import { + ContextMenu, + MenuItem, + ContextMenuTrigger, +} from 'react-contextmenu'; + import CheckboxComponent from './subcomponents/CheckboxComponent'; import EmptyIncidentsComponent from './subcomponents/EmptyIncidentsComponent'; import QueryActiveComponent from './subcomponents/QueryActiveComponent'; @@ -71,6 +77,40 @@ const Delayed = ({ return isShown ? children : null; }; +const exportTableDataToCsv = (tableData) => { + // Create headers from table columns + const headers = tableData.columns.map((column) => column.Header); + + const rowsToMap = tableData.selectedFlatRows.length > 0 + ? tableData.selectedFlatRows : tableData.rows; + const exportRows = rowsToMap.map((row) => { + tableData.prepareRow(row); + const cells = row.cells.slice(1); + const cleanCells = cells.map((cell) => { + let cellStr = `${cell.value?.props?.children || cell.value}`; + if (cellStr.match(/[,"\r\n]/)) { + cellStr = `"${cellStr.replace(/"/g, '""')}"`; + } + return cellStr; + }); + return cleanCells.join(','); + }); + + // Join headers and rows into CSV string + const csv = [headers.join(','), ...exportRows].join('\n'); + + // Download CSV file + const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' }); + const link = document.createElement('a'); + const url = URL.createObjectURL(blob); + link.setAttribute('href', url); + link.setAttribute('download', 'table-data.csv'); + link.style.visibility = 'hidden'; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); +}; + const IncidentTableComponent = ({ selectIncidentTableRows, updateIncidentTableState, @@ -138,19 +178,7 @@ const IncidentTableComponent = ({ const getRowId = useCallback((row) => row.id, []); // Create instance of react-table with options and plugins - const { - state: { - selectedRowIds, - }, - getTableProps, - getTableBodyProps, - headerGroups, - rows, - prepareRow, - selectedFlatRows, - toggleAllRowsSelected, - totalColumnsWidth, - } = useTable( + const tableHook = useTable( { columns: memoizedColumns, data: filteredIncidentsByQuery, // Potential issue with Memoization hook? @@ -209,6 +237,20 @@ const IncidentTableComponent = ({ }, ); + const { + state: { + selectedRowIds, + }, + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + selectedFlatRows, + toggleAllRowsSelected, + totalColumnsWidth, + } = tableHook; + // Custom component required for virtualized rows const RenderRow = useCallback( ({ @@ -244,7 +286,7 @@ const IncidentTableComponent = ({ useEffect(() => { const selectedRows = selectedFlatRows.map((row) => row.original); selectIncidentTableRows(true, selectedRows.length, selectedRows); - return () => {}; + return () => { }; }, [selectedFlatRows]); // Handle deselecting rows after incident action has completed @@ -283,28 +325,32 @@ const IncidentTableComponent = ({ {headerGroups.map((headerGroup) => ( - - {headerGroup.headers.map((column) => ( - - ))} - + + + {headerGroup.headers.map((column) => ( + + ))} + + ))} @@ -320,6 +366,19 @@ const IncidentTableComponent = ({
- {column.render('Header')} - {column.isSorted ? (column.isSortedDesc ? ' ▼' : ' ▲') : ''} - {column.canResize && ( -
{ - e.preventDefault(); - e.stopPropagation(); - }} - /> - )} -
+ {column.render('Header')} + + {column.isSorted ? (column.isSortedDesc ? ' ▼' : ' ▲') : ''} + + {column.canResize && ( +
{ + e.preventDefault(); + e.stopPropagation(); + }} + /> + )} +
+ + { + exportTableDataToCsv(tableHook); + }} + > + Export CSV + {tableHook.selectedFlatRows.length > 0 + ? ` (${tableHook.selectedFlatRows.length} rows)` + : ` (${tableHook.rows.length} rows)`} + + ); diff --git a/src/components/IncidentTable/IncidentTableComponent.scss b/src/components/IncidentTable/IncidentTableComponent.scss index 3081b401..04957596 100644 --- a/src/components/IncidentTable/IncidentTableComponent.scss +++ b/src/components/IncidentTable/IncidentTableComponent.scss @@ -201,3 +201,9 @@ line-clamp: 2; -webkit-box-orient: vertical; } + +.react-contextmenu { + z-index: 1000; + background-color: $pd-white; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2); +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 4e911e98..3d9fa78f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11169,6 +11169,14 @@ react-bootstrap@^1.6.6: uncontrollable "^7.2.1" warning "^4.0.3" +react-contextmenu@^2.14.0: + version "2.14.0" + resolved "https://registry.yarnpkg.com/react-contextmenu/-/react-contextmenu-2.14.0.tgz#d8966f30614b9b780b928be4c8d92bd740d55cdd" + integrity sha512-ktqMOuad6sCFNJs/ltEwppN8F0YeXmqoZfwycgtZR/MxOXMYx1xgYC44SzWH259HdGyshk1/7sXGuIRwj9hzbw== + dependencies: + classnames "^2.2.5" + object-assign "^4.1.0" + react-datepicker@^4.10.0: version "4.10.0" resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.10.0.tgz#3f386ac5873dac5ea56544e51cdc01109938796c" From 6afad412247f9a0ae9e9c7d24581b1d949c702c8 Mon Sep 17 00:00:00 2001 From: Gavin Reynolds Date: Tue, 4 Apr 2023 10:39:16 +0100 Subject: [PATCH 04/21] eslint format Signed-off-by: Gavin Reynolds --- .../IncidentTable/IncidentTableComponent.js | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/components/IncidentTable/IncidentTableComponent.js b/src/components/IncidentTable/IncidentTableComponent.js index 4bde9bf8..b0910c37 100644 --- a/src/components/IncidentTable/IncidentTableComponent.js +++ b/src/components/IncidentTable/IncidentTableComponent.js @@ -36,9 +36,7 @@ import { } from 'config/incident-table-columns'; import { - ContextMenu, - MenuItem, - ContextMenuTrigger, + ContextMenu, MenuItem, ContextMenuTrigger, } from 'react-contextmenu'; import CheckboxComponent from './subcomponents/CheckboxComponent'; @@ -81,8 +79,7 @@ const exportTableDataToCsv = (tableData) => { // Create headers from table columns const headers = tableData.columns.map((column) => column.Header); - const rowsToMap = tableData.selectedFlatRows.length > 0 - ? tableData.selectedFlatRows : tableData.rows; + const rowsToMap = tableData.selectedFlatRows.length > 0 ? tableData.selectedFlatRows : tableData.rows; const exportRows = rowsToMap.map((row) => { tableData.prepareRow(row); const cells = row.cells.slice(1); @@ -286,7 +283,7 @@ const IncidentTableComponent = ({ useEffect(() => { const selectedRows = selectedFlatRows.map((row) => row.original); selectIncidentTableRows(true, selectedRows.length, selectedRows); - return () => { }; + return () => {}; }, [selectedFlatRows]); // Handle deselecting rows after incident action has completed @@ -334,9 +331,7 @@ const IncidentTableComponent = ({ {...column.getHeaderProps(column.getSortByToggleProps())} > {column.render('Header')} - - {column.isSorted ? (column.isSortedDesc ? ' ▼' : ' ▲') : ''} - + {column.isSorted ? (column.isSortedDesc ? ' ▼' : ' ▲') : ''} {column.canResize && (
Date: Tue, 4 Apr 2023 15:29:21 +0100 Subject: [PATCH 05/21] Add dark mode setting Signed-off-by: Gavin Reynolds --- cypress/e2e/Settings/settings.spec.js | 13 ++++++ cypress/support/util/common.js | 16 ++++++++ .../SettingsModal/SettingsModalComponent.js | 22 ++++++++++ .../SettingsModal/SettingsModalComponent.scss | 2 +- .../SettingsModalComponent.test.js | 18 +++++++++ src/redux/rootSaga.js | 2 + src/redux/settings/actions.js | 8 ++++ src/redux/settings/reducers.js | 12 ++++++ src/redux/settings/sagas.js | 16 ++++++++ src/redux/settings/sagas.test.js | 40 +++++++++++++++++++ 10 files changed, 148 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/Settings/settings.spec.js b/cypress/e2e/Settings/settings.spec.js index dbcca15f..cbd8cd96 100644 --- a/cypress/e2e/Settings/settings.spec.js +++ b/cypress/e2e/Settings/settings.spec.js @@ -14,6 +14,7 @@ import { updateMaxRateLimit, updateAutoAcceptIncidentQuery, updateAutoRefreshInterval, + updateDarkMode, manageIncidentTableColumns, manageCustomAlertColumnDefinitions, activateButton, @@ -193,4 +194,16 @@ describe('Manage Settings', { failFast: { enabled: false } }, () => { cy.get('.btn').contains('Clear Local Cache').click(); cy.get('.modal-title').contains('Disclaimer & License').should('be.visible'); }); + + it('Update dark mode', () => { + [true, false].forEach((darkMode) => { + updateDarkMode(darkMode); + cy.window() + .its('store') + .invoke('getState') + .then((state) => expect( + state.settings.darkMode, + ).to.equal(darkMode)); + }); + }); }); diff --git a/cypress/support/util/common.js b/cypress/support/util/common.js index b683b5b7..c8bff941 100644 --- a/cypress/support/util/common.js +++ b/cypress/support/util/common.js @@ -305,6 +305,22 @@ export const updateAutoAcceptIncidentQuery = (autoAcceptIncidentsQuery = false) cy.get('.close').click(); }; +export const updateDarkMode = (darkMode = false) => { + cy.get('.settings-panel-dropdown').click(); + cy.get('.dropdown-item').contains('Settings').click(); + cy.get('.nav-item').contains('User Profile').click(); + + if (darkMode) { + cy.get('#user-profile-dark-mode-checkbox').check({ force: true }); + } else { + cy.get('#user-profile-dark-mode-checkbox').uncheck({ force: true }); + } + + cy.get('.btn').contains('Update User Profile').click(); + checkActionAlertsModalContent('Updated user profile settings'); + cy.get('.close').click(); +}; + export const priorityNames = ['--', 'P5', 'P4', 'P3', 'P2', 'P1']; /* diff --git a/src/components/SettingsModal/SettingsModalComponent.js b/src/components/SettingsModal/SettingsModalComponent.js index 73ce86e2..f6ce0b71 100644 --- a/src/components/SettingsModal/SettingsModalComponent.js +++ b/src/components/SettingsModal/SettingsModalComponent.js @@ -39,6 +39,7 @@ import { setAutoAcceptIncidentsQuery as setAutoAcceptIncidentsQueryConnected, setAutoRefreshInterval as setAutoRefreshIntervalConnected, clearLocalCache as clearLocalCacheConnected, + setDarkMode as setDarkModeConnected, } from 'redux/settings/actions'; import { @@ -100,6 +101,7 @@ const SettingsModalComponent = ({ setMaxRateLimit, setAutoAcceptIncidentsQuery, setAutoRefreshInterval, + setDarkMode, clearLocalCache, updateActionAlertsModal, toggleDisplayActionAlertsModal, @@ -115,6 +117,7 @@ const SettingsModalComponent = ({ autoAcceptIncidentsQuery, autoRefreshInterval, alertCustomDetailFields, + darkMode, } = settings; const { incidentTableColumns, @@ -173,6 +176,8 @@ const SettingsModalComponent = ({ const [tempAutoAcceptQuery, setTempAutoAcceptQuery] = useState(autoAcceptIncidentsQuery); + const [tempDarkMode, setTempDarkMode] = useState(darkMode); + const [selectedColumns, setSelectedColumns] = useState( incidentTableColumns.map((column) => { // Recreate original value used from react-select in order to populate dual list @@ -347,6 +352,19 @@ const SettingsModalComponent = ({ /> + + + {t('Dark Mode')} + + + setTempDarkMode(e.target.checked)} + /> + +