diff --git a/CHANGELOG.md b/CHANGELOG.md index 546d988badd..70b8a42a99f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,13 @@ - Introducing Actions: https://docs.hasura.io/1.0/graphql/manual/actions/index.html - Downgrade command: https://hasura.io/docs/1.0/graphql/manual/deployment/downgrading.html#downgrading-hasura-graphql-engine +- console: add multi select to data table and bulk delete (#3735) + + Added a checkbox to each row on Browse Rows view that allows selecting one or more rows from the table and bulk delete them. + - console: allow setting check constraints during table create (#3881) - There was added a component that allows adding check constraints while creating a new table in the same way as it can be done on the `Modify` view. + Added a component that allows adding check constraints while creating a new table in the same way as it can be done on the `Modify` view. ### Other changes diff --git a/console/cypress/integration/data/insert-browse/spec.js b/console/cypress/integration/data/insert-browse/spec.js index 394bde1d7ab..6f7f3f172c5 100644 --- a/console/cypress/integration/data/insert-browse/spec.js +++ b/console/cypress/integration/data/insert-browse/spec.js @@ -74,6 +74,7 @@ const checkOrder = order => { .find('[role=gridcell]') .first() .next() + .next() .contains(index); } }); @@ -84,6 +85,7 @@ const checkOrder = order => { .find('[role=gridcell]') .first() .next() + .next() .contains(22 - index); } }); @@ -231,7 +233,7 @@ export const checkPagination = () => { cy.wait(3000); // Check if the page changed cy.get( - '.rt-tbody > div:nth-child(1) > div > div:nth-child(2) > div' + '.rt-tbody > div:nth-child(1) > div > div:nth-child(3) > div' ).contains('11'); cy.get('.-pageJump > input').should('have.value', '2'); cy.get('.-previous > button').click(); diff --git a/console/cypress/integration/data/views/spec.js b/console/cypress/integration/data/views/spec.js index d75e6e95136..7dc572c1fd2 100644 --- a/console/cypress/integration/data/views/spec.js +++ b/console/cypress/integration/data/views/spec.js @@ -318,6 +318,7 @@ const checkOrder = order => { .find('[role=gridcell]') .first() .next() + .next() .contains(2); } if (index === 2) { @@ -325,6 +326,7 @@ const checkOrder = order => { .find('[role=gridcell]') .first() .next() + .next() .contains(userId); } }); @@ -335,6 +337,7 @@ const checkOrder = order => { .find('[role=gridcell]') .first() .next() + .next() .contains(2); } if (index === 1) { @@ -342,6 +345,7 @@ const checkOrder = order => { .find('[role=gridcell]') .first() .next() + .next() .contains(userId); } }); diff --git a/console/src/components/Common/Common.scss b/console/src/components/Common/Common.scss index 529a3cfb774..1be862d60d6 100644 --- a/console/src/components/Common/Common.scss +++ b/console/src/components/Common/Common.scss @@ -522,6 +522,10 @@ input { padding-left: 15px; } +.add_padd_left_18 { + padding-left: 18px; +} + .add_mar_top_small { margin-top: 10px; } diff --git a/console/src/components/Common/TableCommon/Table.scss b/console/src/components/Common/TableCommon/Table.scss index bbc91ec12b4..ed11f7d9250 100644 --- a/console/src/components/Common/TableCommon/Table.scss +++ b/console/src/components/Common/TableCommon/Table.scss @@ -1,4 +1,4 @@ -@import "../Common.scss"; +@import '../Common.scss'; .container { padding: 0; @@ -50,7 +50,7 @@ label.radioLabel { padding-top: 0; - input[type="radio"] { + input[type='radio'] { margin-top: 10px; } } @@ -64,7 +64,7 @@ width: auto; tr:nth-child(even) { - background: #f4f4f4 + background: #f4f4f4; } th { @@ -183,3 +183,21 @@ a.expanded { .tableCellExpanded { white-space: pre-wrap; } + +.tableCenterContent { + display: flex; + justify-content: center; + align-items: center; + height: 100%; +} + +.bulkDeleteButton { + margin: 10px; + font-size: 16px; + padding: 0 6px; +} + +.headerInputCheckbox { + height: 16px; + margin-bottom: 2px; +} diff --git a/console/src/components/Common/TableCommon/TableStyles.scss b/console/src/components/Common/TableCommon/TableStyles.scss index b273900a92b..36fedec89ab 100644 --- a/console/src/components/Common/TableCommon/TableStyles.scss +++ b/console/src/components/Common/TableCommon/TableStyles.scss @@ -35,7 +35,7 @@ label.radioLabel { padding-top: 0; - input[type="radio"] { + input[type='radio'] { margin-top: 10px; } } @@ -104,7 +104,7 @@ a.expanded { i:hover { cursor: pointer; - color: #B85C27; + color: #b85c27; transition: 0.2s; } } @@ -127,5 +127,5 @@ a.expanded { .relEditButtons { display: flex; - flex-direction:row; + flex-direction: row; } diff --git a/console/src/components/Common/utils/v1QueryUtils.js b/console/src/components/Common/utils/v1QueryUtils.js index 7af8ab18578..e2c62af34f0 100644 --- a/console/src/components/Common/utils/v1QueryUtils.js +++ b/console/src/components/Common/utils/v1QueryUtils.js @@ -209,3 +209,19 @@ export const getDropComputedFieldQuery = (tableDef, computedFieldName) => { }, }; }; + +export const getDeleteQuery = (pkClause, tableName, schemaName) => { + return { + type: 'delete', + args: { + table: { + name: tableName, + schema: schemaName, + }, + where: pkClause, + }, + }; +}; + +export const getBulkDeleteQuery = (pkClauses, tableName, schemaName) => + pkClauses.map(pkClause => getDeleteQuery(pkClause, tableName, schemaName)); diff --git a/console/src/components/Services/Data/TableBrowseRows/ViewActions.js b/console/src/components/Services/Data/TableBrowseRows/ViewActions.js index 692711bb726..0b968f1f369 100644 --- a/console/src/components/Services/Data/TableBrowseRows/ViewActions.js +++ b/console/src/components/Services/Data/TableBrowseRows/ViewActions.js @@ -9,6 +9,7 @@ import { } from '../../Common/Notification'; import dataHeaders from '../Common/Headers'; import { getConfirmation } from '../../../Common/utils/jsUtils'; +import { getBulkDeleteQuery } from '../../../Common/utils/v1QueryUtils'; /* ****************** View actions *************/ const V_SET_DEFAULTS = 'ViewTable/V_SET_DEFAULTS'; @@ -210,6 +211,47 @@ const deleteItem = pkClause => { }; }; +const deleteItems = pkClauses => { + return (dispatch, getState) => { + const confirmMessage = 'This will permanently delete rows from this table'; + const isOk = getConfirmation(confirmMessage); + if (!isOk) { + return; + } + + const state = getState(); + + const reqBody = { + type: 'bulk', + args: getBulkDeleteQuery( + pkClauses, + state.tables.currentTable, + state.tables.currentSchema + ), + }; + const options = { + method: 'POST', + body: JSON.stringify(reqBody), + headers: dataHeaders(getState), + credentials: globalCookiePolicy, + }; + dispatch(requestAction(Endpoints.query, options)).then( + data => { + const affected = data.reduce((acc, d) => acc + d.affected_rows, 0); + dispatch(vMakeRequest()); + dispatch( + showSuccessNotification('Rows deleted!', 'Affected rows: ' + affected) + ); + }, + err => { + dispatch( + showErrorNotification('Deleting rows failed!', err.error, err) + ); + } + ); + }; +}; + const vExpandRel = (path, relname, pk) => { return dispatch => { // Modify the query (UI will automatically change) @@ -560,6 +602,7 @@ export { vCollapseRow, V_SET_ACTIVE, deleteItem, + deleteItems, UPDATE_TRIGGER_ROW, UPDATE_TRIGGER_FUNCTION, }; diff --git a/console/src/components/Services/Data/TableBrowseRows/ViewRows.js b/console/src/components/Services/Data/TableBrowseRows/ViewRows.js index ff372ff67cd..e23a439e66b 100644 --- a/console/src/components/Services/Data/TableBrowseRows/ViewRows.js +++ b/console/src/components/Services/Data/TableBrowseRows/ViewRows.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, { useState } from 'react'; import 'react-table/react-table.css'; import '../../../Common/TableCommon/ReactTableOverrides.css'; import DragFoldTable from '../../../Common/TableCommon/DragFoldTable'; @@ -11,6 +11,7 @@ import { vExpandRel, vCloseRel, V_SET_ACTIVE, + deleteItems, deleteItem, vExpandRow, vCollapseRow, @@ -73,8 +74,12 @@ const ViewRows = ({ location, readOnlyMode, }) => { + const [selectedRows, setSelectedRows] = useState([]); + const styles = require('../../../Common/TableCommon/Table.scss'); + const NO_PRIMARY_KEY_MSG = 'No primary key to identify row'; + // Invoke manual trigger status const invokeTrigger = (trigger, row) => { updateInvocationRow(row); @@ -86,6 +91,14 @@ const ViewRows = ({ updateInvocationFunction(null); }; + const handleAllCheckboxChange = e => { + if (e.target.checked) { + setSelectedRows(curRows); + } else { + setSelectedRows([]); + } + }; + const checkIfSingleRow = _curRelName => { let _isSingleRow = false; @@ -116,7 +129,7 @@ const ViewRows = ({ ); }; - const getGridHeadings = (_columns, _relationships) => { + const getGridHeadings = (_columns, _relationships, _disableBulkSelect) => { const _gridHeadings = []; const getColWidth = (header, contentRows = []) => { @@ -184,6 +197,26 @@ const ViewRows = ({ width: 152, }); + _gridHeadings.push({ + Header: ( +