From a840c440f52538be0ea2bfddbdf6e449b8335033 Mon Sep 17 00:00:00 2001 From: Rajat Dabade Date: Tue, 10 May 2022 16:46:24 +0530 Subject: [PATCH] Added delete feature in Table view (#2383) * Added delete feature in Table view * Fixed the build failure * Added test cased for delete the row in table view * Fixes for useMemo, useCallback and proposed CSS solution * UI fixes * Resolved the test-case issue Co-authored-by: Rajat Dabade --- webapp/i18n/en.json | 1 + .../__snapshots__/centerPanel.test.tsx.snap | 756 ++++++++++++++++++ .../table/__snapshots__/table.test.tsx.snap | 418 ++++++++++ .../__snapshots__/tableRow.test.tsx.snap | 228 ++++++ .../__snapshots__/tableRows.test.tsx.snap | 38 + webapp/src/components/table/table.scss | 7 + webapp/src/components/table/table.test.tsx | 64 +- webapp/src/components/table/tableRow.scss | 37 + webapp/src/components/table/tableRow.tsx | 69 +- 9 files changed, 1616 insertions(+), 2 deletions(-) diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index 602172dda..d8f1cbd81 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -239,6 +239,7 @@ "TableHeaderMenu.sort-ascending": "Sort ascending", "TableHeaderMenu.sort-descending": "Sort descending", "TableRow.open": "Open", + "TableRow.delete": "Delete", "TopBar.give-feedback": "Give Feedback", "URLProperty.copiedLink": "Copied!", "URLProperty.copy": "Copy", diff --git a/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap b/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap index 813443ca1..e36cf8151 100644 --- a/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap +++ b/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap @@ -878,6 +878,42 @@ exports[`components/centerPanel return centerPanel and press touch 1 with readon draggable="true" style="opacity: 1;" > +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
{ const {container} = render(component) expect(container).toMatchSnapshot() }) + + test('should delete snapshot', async () => { + const board = TestBlockFactory.createBoard() + + const modifiedById = Utils.createGuid(IDType.User) + board.cardProperties.push({ + id: modifiedById, + name: 'Last Modified By', + type: 'updatedBy', + options: [], + }) + const card1 = TestBlockFactory.createCard(board) + card1.title = 'card1' + const card2 = TestBlockFactory.createCard(board) + card2.title = 'card2' + const view = TestBlockFactory.createBoardView(board) + view.fields.viewType = 'table' + view.fields.groupById = undefined + view.fields.visiblePropertyIds = ['property1', 'property2', modifiedById] + const mockStore = configureStore([]) + const store = mockStore({ + ...state, + cards: { + cards: { + [card1.id]: card1, + [card2.id]: card2, + }, + }, + }) + + const component = wrapDNDIntl( + + + , + ) + + const {getByTitle, getByRole, getAllByTitle} = render(component) + const card1Name = getByTitle(card1.title) + userEvents.hover(card1Name) + const menuBtn = getAllByTitle('MenuBtn') + userEvents.click(menuBtn[0]) + const deleteBtn = getByRole('button', {name: 'Delete'}) + userEvents.click(deleteBtn) + const dailogDeleteBtn = screen.getByRole('button', {name: 'Delete'}) + userEvents.click(dailogDeleteBtn) + await waitFor(() => { + expect(global.fetch).toHaveBeenCalledWith(`http://localhost/api/v1/boards/${board.id}/blocks/${card1.id}`, {"headers": {"Accept": "application/json", "Authorization": "", "Content-Type": "application/json", "X-Requested-With": "XMLHttpRequest"}, "method": "DELETE"}) + }) + }) }) diff --git a/webapp/src/components/table/tableRow.scss b/webapp/src/components/table/tableRow.scss index 186efe2f7..835d22b9f 100644 --- a/webapp/src/components/table/tableRow.scss +++ b/webapp/src/components/table/tableRow.scss @@ -22,10 +22,47 @@ &:hover { background-color: rgba(var(--center-channel-color-rgb), 0.05); + overflow: initial; + margin-left: -60px; + + .delete-button { + display: block; + } .open-button { display: block; } + + .action-cell { + flex: 0 0 auto; + display: flex; + flex-direction: row; + color: rgb(var(--center-channel-color-rgb)); + border-left: 1px solid transparent; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + box-sizing: border-box; + padding: 8px; + height: 44px; + font-size: 14px; + text-overflow: ellipsis; + background-color: rgb(255, 255, 255); + position: fixed; + z-index: 100; + } + + .octo-table-cell-btn { + display: flex; + } + + .title-cell { + margin-left: 60px; + } + } + + .action-cell { + display: none; + width: 60px; } .URLProperty:hover .Button_Copy { diff --git a/webapp/src/components/table/tableRow.tsx b/webapp/src/components/table/tableRow.tsx index b57af67eb..949f9dc19 100644 --- a/webapp/src/components/table/tableRow.tsx +++ b/webapp/src/components/table/tableRow.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useEffect, useRef, useState, useMemo, useCallback} from 'react' -import {FormattedMessage} from 'react-intl' +import {FormattedMessage, useIntl} from 'react-intl' import {Card} from '../../blocks/card' import {Board, IPropertyTemplate} from '../../blocks/board' @@ -11,7 +11,18 @@ import Button from '../../widgets/buttons/button' import Editable from '../../widgets/editable' import {useSortable} from '../../hooks/sortable' +import {Utils} from '../../utils' + import PropertyValueElement from '../propertyValueElement' +import Menu from '../../widgets/menu' +import MenuWrapper from '../../widgets/menuWrapper' +import IconButton from '../../widgets/buttons/iconButton' +import GripIcon from '../../widgets/icons/grip' +import OptionsIcon from '../../widgets/icons/options' +import DeleteIcon from '../../widgets/icons/delete' +import ConfirmationDialogBox, {ConfirmationDialogBoxProps} from '../confirmationDialogBox' +import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient' + import './tableRow.scss' type Props = { @@ -43,12 +54,14 @@ export const columnWidth = (resizingColumn: string, columnWidths: Record { + const intl = useIntl() const {board, columnRefs, card, isManualSort, groupById, visiblePropertyIds, collapsedOptionIds, columnWidths} = props const titleRef = useRef<{ focus(selectAll?: boolean): void }>(null) const [title, setTitle] = useState(props.card.title || '') const isGrouped = Boolean(groupById) const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly && (isManualSort || isGrouped), props.onDrop) + const [showConfirmationDialogBox, setShowConfirmationDialogBox] = useState(false) useEffect(() => { if (props.focusOnMount) { @@ -99,6 +112,37 @@ const TableRow = (props: Props) => { columnRefs.set(Constants.titleColumnId, React.createRef()) } + const handleDeleteCard = useCallback(async () => { + if (!card) { + Utils.assertFailure() + return + } + TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.DeleteCard, {board: board.id, card: card.id}) + await mutator.deleteBlock(card, 'delete card') + }, [card, board.id]) + + const confirmDialogProps: ConfirmationDialogBoxProps = useMemo(() => { + return { + heading: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-heading', defaultMessage: 'Confirm card delete!'}), + confirmButtonText: intl.formatMessage({id: 'CardDialog.delete-confirmation-dialog-button-text', defaultMessage: 'Delete'}), + onConfirm: handleDeleteCard, + onClose: () => { + setShowConfirmationDialogBox(false) + }, + } + }, [handleDeleteCard]) + + const handleDeleteButtonOnClick = useCallback(() => { + // user trying to delete a card with blank name + // but content present cannot be deleted without + // confirmation dialog + if (card?.title === '' && card?.fields.contentOrder.length === 0) { + handleDeleteCard() + return + } + setShowConfirmationDialogBox(true) + }, [card.title, card.fields.contentOrder, handleDeleteCard]) + return (
{ style={{opacity: isDragging ? 0.5 : 1}} > +
+ + } + /> + + } + id='delete' + name={intl.formatMessage({id: 'TableRow.delete', defaultMessage: 'Delete'})} + onClick={handleDeleteButtonOnClick} + /> + + + }/> +
+ {/* Name / title */}
{
) })} + + {showConfirmationDialogBox && }
) }