Skip to content

Commit

Permalink
feat: add restore buttons
Browse files Browse the repository at this point in the history
  • Loading branch information
pyphilia committed Oct 8, 2021
1 parent 45febb4 commit 49dcd31
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 14 deletions.
88 changes: 88 additions & 0 deletions cypress/integration/item/delete/listRestoreItem.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { DEFAULT_ITEM_LAYOUT_MODE } from '../../../../src/config/constants';
import { ITEM_LAYOUT_MODES } from '../../../../src/enums';
import { RECYCLE_BIN_PATH } from '../../../../src/config/paths';
import {
buildItemsTableRowIdAttribute,
ITEMS_TABLE_RESTORE_SELECTED_ITEMS_ID,
RESTORE_ITEMS_BUTTON_CLASS,
} from '../../../../src/config/selectors';
import { DATABASE_WITH_RECYCLE_BIN } from '../../../fixtures/recycleBin';
import { TABLE_ITEM_RENDER_TIME } from '../../../support/constants';

const restoreItem = (id) => {
cy.wait(TABLE_ITEM_RENDER_TIME);
cy.get(
`${buildItemsTableRowIdAttribute(id)} .${RESTORE_ITEMS_BUTTON_CLASS}`,
).click();
};

const restoreItems = (itemIds) => {
// check selected ids
itemIds.forEach((id) => {
cy.wait(TABLE_ITEM_RENDER_TIME);
cy.get(`${buildItemsTableRowIdAttribute(id)} .ag-checkbox-input`).click();
});

cy.get(`#${ITEMS_TABLE_RESTORE_SELECTED_ITEMS_ID}`).click();
};

describe('Restore Items in List', () => {
it('restore one item', () => {
cy.setUpApi(DATABASE_WITH_RECYCLE_BIN);
cy.visit(RECYCLE_BIN_PATH);

if (DEFAULT_ITEM_LAYOUT_MODE !== ITEM_LAYOUT_MODES.LIST) {
cy.switchMode(ITEM_LAYOUT_MODES.LIST);
}

const { id } = DATABASE_WITH_RECYCLE_BIN.recycledItems[0];

// restore
restoreItem(id);
cy.wait('@restoreItems').then(({ request: { url } }) => {
expect(url).to.contain(id);
});
cy.wait('@getRecycledItems');
});

it.only('restore multiple items', () => {
cy.setUpApi(DATABASE_WITH_RECYCLE_BIN);
cy.visit(RECYCLE_BIN_PATH);

if (DEFAULT_ITEM_LAYOUT_MODE !== ITEM_LAYOUT_MODES.LIST) {
cy.switchMode(ITEM_LAYOUT_MODES.LIST);
}

// restore
const itemIds = DATABASE_WITH_RECYCLE_BIN.recycledItems.map(({ id }) => id);
restoreItems(itemIds);
cy.wait('@restoreItems').then(({ request: { url } }) => {
for (const id of itemIds) {
expect(url).to.contain(id);
}
});
cy.wait('@getRecycledItems');
});

describe('Error handling', () => {
it('error while restoring item does not delete in interface', () => {
cy.setUpApi({ ...DATABASE_WITH_RECYCLE_BIN, restoretItemsError: true });
const { id } = DATABASE_WITH_RECYCLE_BIN.recycledItems[0];

// go to children item
cy.visit(RECYCLE_BIN_PATH);

if (DEFAULT_ITEM_LAYOUT_MODE !== ITEM_LAYOUT_MODES.LIST) {
cy.switchMode(ITEM_LAYOUT_MODES.LIST);
}

// restore
restoreItem(id);

cy.wait('@restoreItems').then(() => {
// check item is still displayed
cy.get(buildItemsTableRowIdAttribute(id)).should('exist');
});
});
});
});
4 changes: 4 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
mockRecycleItems,
mockGetRecycledItems,
mockDeleteItemTag,
mockRestoreItems,
} from './server';
import './commands/item';
import './commands/navigation';
Expand Down Expand Up @@ -101,6 +102,7 @@ Cypress.Commands.add(
recycleItemsError = false,
getRecycledItemsError = false,
deleteItemTagError = false,
restoretItemsError = false,
} = {}) => {
const cachedItems = JSON.parse(JSON.stringify(items));
const cachedMembers = JSON.parse(JSON.stringify(members));
Expand Down Expand Up @@ -206,6 +208,8 @@ Cypress.Commands.add(
mockRecycleItems(items, recycleItemsError);

mockGetRecycledItems(recycledItems, getRecycledItemsError);

mockRestoreItems(recycledItems, restoretItemsError);
},
);

Expand Down
23 changes: 23 additions & 0 deletions cypress/support/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,29 @@ export const mockRecycleItems = (items, shouldThrowError) => {
).as('recycleItems');
};

export const mockRestoreItems = (items, shouldThrowError) => {
cy.intercept(
{
method: DEFAULT_POST.method,
url: new RegExp(`${API_HOST}/${ITEMS_ROUTE}/restore\\?id\\=`),
query: { id: new RegExp(ID_FORMAT) },
},
({ url, reply }) => {
let ids = qs.parse(url.slice(url.indexOf('?') + 1)).id;
if (typeof ids !== 'object') ids = [ids];

if (shouldThrowError) {
return reply({ statusCode: StatusCodes.BAD_REQUEST, body: null });
}

return reply({
statusCode: StatusCodes.OK,
body: ids.map((id) => getItemById(items, id)),
});
},
).as('restoreItems');
};

export const mockGetItem = ({ items, currentMember }, shouldThrowError) => {
cy.intercept(
{
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"license": "AGPL-3.0-only",
"dependencies": {
"@graasp/chatbox": "git://github.com/graasp/graasp-chatbox.git#main",
"@graasp/query-client": "git://github.com/graasp/graasp-query-client.git",
"@graasp/query-client": "git://github.com/graasp/graasp-query-client.git#78/restore",
"@graasp/ui": "git://github.com/graasp/graasp-ui.git",
"@material-ui/core": "4.11.2",
"@material-ui/icons": "4.11.2",
Expand Down
14 changes: 11 additions & 3 deletions src/components/RecycleBinScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import Loader from './common/Loader';
import Main from './main/Main';
import DeleteButton from './common/DeleteButton';
import RestoreButton from './common/RestoreButton';
import { ITEMS_TABLE_DELETE_SELECTED_ITEMS_ID } from '../config/selectors';
import {
ITEMS_TABLE_DELETE_SELECTED_ITEMS_ID,
ITEMS_TABLE_RESTORE_SELECTED_ITEMS_ID,
} from '../config/selectors';

const RowActions = ({ data: item }) => (
<>
<RestoreButton itemId={item.id} />
<RestoreButton itemIds={[item.id]} />
<DeleteButton itemIds={[item.id]} />
</>
);
Expand All @@ -24,7 +27,11 @@ RowActions.propTypes = {

const ToolbarActions = ({ selectedIds }) => (
<>
<RestoreButton itemIds={selectedIds} color="secondary" />
<RestoreButton
itemIds={selectedIds}
color="secondary"
id={ITEMS_TABLE_RESTORE_SELECTED_ITEMS_ID}
/>
<DeleteButton
id={ITEMS_TABLE_DELETE_SELECTED_ITEMS_ID}
itemIds={selectedIds}
Expand Down Expand Up @@ -52,6 +59,7 @@ const RecycleBinScreen = () => {
<Main>
<ItemHeader />
<Items
clickable={false}
title={t('Deleted Items')}
items={List(items)}
actions={RowActions}
Expand Down
12 changes: 8 additions & 4 deletions src/components/common/RestoreButton.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
import React from 'react';
import { MUTATION_KEYS } from '@graasp/query-client';
import PropTypes from 'prop-types';
import IconButton from '@material-ui/core/IconButton';
import { useTranslation } from 'react-i18next';
import RestoreFromTrashIcon from '@material-ui/icons/RestoreFromTrash';
import Tooltip from '@material-ui/core/Tooltip';
import { RESTORE_ITEMS_BUTTON_CLASS } from '../../config/selectors';
import { useMutation } from '../../config/queryClient';

const RestoreButton = ({ itemIds, color }) => {
const RestoreButton = ({ itemIds, color, id }) => {
const { t } = useTranslation();
const { mutate: restoreItems } = useMutation(MUTATION_KEYS.RESTORE_ITEMS);

const onClick = () => {
// restore items
// eslint-disable-next-line no-console
console.log(itemIds);
restoreItems(itemIds);
};

return (
<Tooltip title={t('Restore')}>
<span>
<IconButton
id={id}
aria-label="restore"
color={color}
className={RESTORE_ITEMS_BUTTON_CLASS}
onClick={onClick}
disabled
>
<RestoreFromTrashIcon />
</IconButton>
Expand All @@ -35,9 +37,11 @@ const RestoreButton = ({ itemIds, color }) => {
RestoreButton.propTypes = {
itemIds: PropTypes.arrayOf(PropTypes.string).isRequired,
color: PropTypes.string,
id: PropTypes.string,
};
RestoreButton.defaultProps = {
color: 'default',
id: null,
};

export default RestoreButton;
5 changes: 5 additions & 0 deletions src/components/main/Items.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const Items = ({
headerElements,
actions,
toolbarActions,
clickable,
}) => {
const { mode } = useContext(LayoutContext);
const itemSearch = useItemSearch(items);
Expand All @@ -28,6 +29,7 @@ const Items = ({
// This enables the possiblity to display messages (item is empty, no search result)
itemSearch={itemSearch}
headerElements={[itemSearch.input, ...headerElements]}
clickable={clickable}
/>
);
case ITEM_LAYOUT_MODES.LIST:
Expand All @@ -41,6 +43,7 @@ const Items = ({
isSearching={Boolean(itemSearch.text)}
actions={actions}
toolbarActions={toolbarActions}
clickable={clickable}
/>
);
}
Expand All @@ -53,13 +56,15 @@ Items.propTypes = {
headerElements: PropTypes.arrayOf(PropTypes.element),
actions: PropTypes.element,
toolbarActions: PropTypes.element,
clickable: PropTypes.bool,
};

Items.defaultProps = {
id: null,
headerElements: [],
actions: null,
toolbarActions: null,
clickable: true,
};

export default Items;
7 changes: 5 additions & 2 deletions src/components/main/ItemsTable.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const ItemsTable = ({
isSearching,
actions,
toolbarActions,
clickable,
}) => {
const { t } = useTranslation();
const { push } = useHistory();
Expand Down Expand Up @@ -178,8 +179,8 @@ const ItemsTable = ({
onRowDragEnd={onDragEnd}
onGridReady={onGridReady}
onSelectionChanged={onSelectionChanged}
onCellClicked={onCellClicked}
rowClass={classes.row}
onCellClicked={clickable ? onCellClicked : null}
rowClass={clickable ? classes.row : null}
getRowNodeId={getRowNodeId}
onRowDataChanged={onRowDataChanged}
applyColumnDefOrder
Expand Down Expand Up @@ -250,6 +251,7 @@ ItemsTable.propTypes = {
isSearching: PropTypes.bool,
actions: PropTypes.element,
toolbarActions: PropTypes.element,
clickable: PropTypes.bool,
};

ItemsTable.defaultProps = {
Expand All @@ -259,6 +261,7 @@ ItemsTable.defaultProps = {
isSearching: false,
actions: null,
toolbarActions: null,
clickable: true,
};

export default ItemsTable;
2 changes: 2 additions & 0 deletions src/config/selectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,5 @@ export const SHARE_ITEM_PSEUDONYMIZED_SCHEMA_ID =
'shareItemPseudonymizedSchema';
export const ITEM_RECYCLE_BUTTON_CLASS = 'itemRecycleButton';
export const buildItemsTableId = (id) => `itemsTable-${id}`;
export const ITEMS_TABLE_RESTORE_SELECTED_ITEMS_ID =
'itemsTableRestoreSelectedItems';
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2155,9 +2155,9 @@ __metadata:
languageName: node
linkType: hard

"@graasp/query-client@git://github.com/graasp/graasp-query-client.git":
"@graasp/query-client@git://github.com/graasp/graasp-query-client.git#78/restore":
version: 0.1.0
resolution: "@graasp/query-client@git://github.com/graasp/graasp-query-client.git#commit=3503b979f5873b66dbc26cc6f49464a7b33dcfd7"
resolution: "@graasp/query-client@git://github.com/graasp/graasp-query-client.git#commit=72d8121a038e1da0e47e4345e28b09a5370e042f"
dependencies:
axios: 0.21.4
http-status-codes: 2.1.4
Expand All @@ -2168,7 +2168,7 @@ __metadata:
uuid: 8.3.2
peerDependencies:
react: ^17.0.0
checksum: 546038e171d268f1877f9059137f4e0db9f3dfcd23a007a12f717f8018f77f894dc2e7609223d2e8227932b48209166d5130ad12a50972fe0579824727c85fae
checksum: e33c60533bde8a4c0cb252ef55fad9438480d8f3a7177aff8ad61ad43ea155b7895b4690c8376b769ce4e5eacc455a283f082db2fbacec524d61f771f4611f5d
languageName: node
linkType: hard

Expand Down Expand Up @@ -10224,7 +10224,7 @@ fsevents@^1.2.7:
"@cypress/code-coverage": 3.9.2
"@cypress/instrument-cra": 1.4.0
"@graasp/chatbox": "git://github.com/graasp/graasp-chatbox.git#main"
"@graasp/query-client": "git://github.com/graasp/graasp-query-client.git"
"@graasp/query-client": "git://github.com/graasp/graasp-query-client.git#78/restore"
"@graasp/ui": "git://github.com/graasp/graasp-ui.git"
"@graasp/websockets": "git://github.com/graasp/graasp-websockets.git#master"
"@material-ui/core": 4.11.2
Expand Down

0 comments on commit 49dcd31

Please sign in to comment.