Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GH-4843] Deletes newly created card after close if no interaction were made in #5041

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion webapp/cypress/integration/createBoard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,10 @@ describe('Create and delete board / card', () => {
}

// Create empty card in last group
cy.log('**Create new empty card in first group**')
cy.log('**Create new non empty card in first group**')
cy.get('.octo-board-column').last().contains('+ New').scrollIntoView().click()
cy.get('.Dialog').should('exist')
cy.get('.Dialog .EditableArea.Editable.title').should('exist').type('New card')
cy.get('.Dialog Button[title=\'Close dialog\']').should('be.visible').click()
cy.get('.KanbanCard').scrollIntoView().should('exist')

Expand Down Expand Up @@ -225,4 +226,23 @@ describe('Create and delete board / card', () => {
type(`{shift+${ctrlKey}+z}`).
should('have.text', '')
})

it('Deletes newly created card after close if no interaction were made in the card', () => {
// Visit a page and create new empty board
cy.visit('/')
cy.wait(500)
cy.uiCreateEmptyBoard()

cy.log('**Create new empty group**')
cy.contains('+ Add a group').scrollIntoView().should('be.visible').click()
cy.get('.KanbanColumnHeader .Editable[value=\'New group\']').should('have.length', 1)

cy.log('**Create new non empty card in first group**')
cy.get('.octo-board-column').last().contains('+ New').scrollIntoView().click()
cy.get('.Dialog').should('exist')

cy.log('**Close dialog without touching any field**')
cy.get('.Dialog Button[title=\'Close dialog\']').should('be.visible').click()
cy.get('.KanbanCard').should('not.exist')
})
})
10 changes: 8 additions & 2 deletions webapp/src/components/cardDetail/cardDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../teleme
import BlockIconSelector from '../blockIconSelector'

import {useAppDispatch, useAppSelector} from '../../store/hooks'
import {updateCards, setCurrent as setCurrentCard} from '../../store/cards'
import {updateCards, setCurrent as setCurrentCard, touchCard} from '../../store/cards'
import {updateContents} from '../../store/contents'
import {Permission} from '../../constants'
import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
Expand Down Expand Up @@ -225,7 +225,10 @@ const CardDetail = (props: Props): JSX.Element|null => {
className='title'
value={title}
placeholderText='Untitled'
onChange={(newTitle: string) => setTitle(newTitle)}
onChange={(newTitle: string) => {
setTitle(newTitle)
dispatch(touchCard(card.id))
}}
saveOnEsc={true}
onSave={saveTitle}
onCancel={() => setTitle(props.card.title)}
Expand Down Expand Up @@ -320,6 +323,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
boardId={card.boardId}
blocks={blocks}
onBlockCreated={async (block: any, afterBlock: any): Promise<BlockData|null> => {
dispatch(touchCard(card.id))
if (block.contentType === 'text' && block.value === '') {
return null
}
Expand All @@ -335,6 +339,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
return {...block, id: newBlock.id}
}}
onBlockModified={async (block: any): Promise<BlockData<any>|null> => {
dispatch(touchCard(card.id))
const originalContentBlock = props.contents.flatMap((b) => b).find((b) => b.id === block.id)
if (!originalContentBlock) {
return null
Expand All @@ -359,6 +364,7 @@ const CardDetail = (props: Props): JSX.Element|null => {
return block
}}
onBlockMoved={async (block: BlockData, beforeBlock: BlockData|null, afterBlock: BlockData|null): Promise<void> => {
dispatch(touchCard(card.id))
if (block.id) {
const idx = card.fields.contentOrder.indexOf(block.id)
let sourceBlockId: string
Expand Down
6 changes: 6 additions & 0 deletions webapp/src/components/cardDetail/cardDetailContents.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {MarkdownEditor} from '../markdownEditor'

import AddDescriptionTourStep from '../onboardingTour/addDescription/add_description'

import {touchCard} from '../../store/cards'
import {useAppDispatch} from '../../store/hooks'

import {dragAndDropRearrange} from './cardDetailContentsUtility'

export type Position = 'left' | 'right' | 'above' | 'below' | 'aboveRow' | 'belowRow'
Expand Down Expand Up @@ -161,6 +164,8 @@ const ContentBlockWithDragAndDrop = (props: ContentBlockWithDragAndDropProps) =>
const CardDetailContents = (props: Props) => {
const intl = useIntl()
const {contents, card, id} = props
const dispatch = useAppDispatch()

if (contents.length) {
return (
<div className='octo-content'>
Expand Down Expand Up @@ -196,6 +201,7 @@ const CardDetailContents = (props: Props) => {
addTextBlock(card, intl, text)
}
}}
onChange={() => dispatch(touchCard(card.id))}
/>
}
</div>
Expand Down
12 changes: 10 additions & 2 deletions webapp/src/components/cardDetail/cardDetailContentsMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import React, {useCallback} from 'react'
import {FormattedMessage, IntlShape, useIntl} from 'react-intl'

import {ThunkDispatch} from '@reduxjs/toolkit'

import {BlockTypes} from '../../blocks/block'
import {Utils} from '../../utils'
import Button from '../../widgets/buttons/button'
Expand All @@ -11,9 +13,13 @@ import MenuWrapper from '../../widgets/menuWrapper'

import {contentRegistry} from '../content/contentRegistry'

import {touchCard} from '../../store/cards'

import {useAppDispatch} from '../../store/hooks'

import {useCardDetailContext} from './cardDetailContext'

function addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {
function addContentMenu(intl: IntlShape, type: BlockTypes, dispatch: ThunkDispatch<any, any, any>): JSX.Element {
const handler = contentRegistry.getHandler(type)
if (!handler) {
Utils.logError(`addContentMenu, unknown content type: ${type}`)
Expand All @@ -24,6 +30,7 @@ function addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {
const {card} = cardDetail
const index = card.fields.contentOrder.length
cardDetail.addBlock(handler, index, false)
dispatch(touchCard(card.id))
}, [cardDetail, handler])

return (
Expand All @@ -39,6 +46,7 @@ function addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {

const CardDetailContentsMenu = () => {
const intl = useIntl()
const dispatch = useAppDispatch()
return (
<div className='CardDetailContentsMenu content add-content'>
<MenuWrapper>
Expand All @@ -49,7 +57,7 @@ const CardDetailContentsMenu = () => {
/>
</Button>
<Menu position='top'>
{contentRegistry.contentTypes.map((type) => addContentMenu(intl, type))}
{contentRegistry.contentTypes.map((type) => addContentMenu(intl, type, dispatch))}
</Menu>
</MenuWrapper>
</div>
Expand Down
8 changes: 8 additions & 0 deletions webapp/src/components/cardDetail/cardDetailProperties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {Permission} from '../../constants'
import {useHasCurrentBoardPermissions} from '../../hooks/permissions'
import propRegistry from '../../properties'
import {PropertyType} from '../../properties/types'
import {touchCard} from '../../store/cards'
import {useAppDispatch} from '../../store/hooks'

type Props = {
board: Board
Expand All @@ -39,6 +41,7 @@ const CardDetailProperties = (props: Props) => {
const canEditBoardProperties = useHasCurrentBoardPermissions([Permission.ManageBoardProperties])
const canEditBoardCards = useHasCurrentBoardPermissions([Permission.ManageBoardCards])
const intl = useIntl()
const dispatch = useAppDispatch()

useEffect(() => {
const newProperty = board.cardProperties.find((property) => property.id === newTemplateId)
Expand All @@ -51,6 +54,8 @@ const CardDetailProperties = (props: Props) => {
const [showConfirmationDialog, setShowConfirmationDialog] = useState<boolean>(false)

function onPropertyChangeSetAndOpenConfirmationDialog(newType: PropertyType, newName: string, propertyTemplate: IPropertyTemplate) {
dispatch(touchCard(card.id))

const oldType = propRegistry.get(propertyTemplate.type)

// do nothing if no change
Expand Down Expand Up @@ -101,6 +106,8 @@ const CardDetailProperties = (props: Props) => {
}

function onPropertyDeleteSetAndOpenConfirmationDialog(propertyTemplate: IPropertyTemplate) {
dispatch(touchCard(card.id))

// set ConfirmationDialogBox Props
setConfirmationDialogBox({
heading: intl.formatMessage({id: 'CardDetailProperty.confirm-delete-heading', defaultMessage: 'Confirm delete property'}),
Expand Down Expand Up @@ -179,6 +186,7 @@ const CardDetailProperties = (props: Props) => {
<PropertyTypes
label={intl.formatMessage({id: 'PropertyMenu.selectType', defaultMessage: 'Select property type'})}
onTypeSelected={async (type) => {
dispatch(touchCard(card.id))
const template: IPropertyTemplate = {
id: Utils.createGuid(IDType.BlockID),
name: type.displayName(intl),
Expand Down
8 changes: 7 additions & 1 deletion webapp/src/components/cardDetail/commentsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {FormattedMessage, useIntl} from 'react-intl'

import {CommentBlock, createCommentBlock} from '../../blocks/commentBlock'
import mutator from '../../mutator'
import {useAppSelector} from '../../store/hooks'
import {useAppDispatch, useAppSelector} from '../../store/hooks'
import {Utils} from '../../utils'
import Button from '../../widgets/buttons/button'

Expand All @@ -18,6 +18,8 @@ import {Permission} from '../../constants'

import AddCommentTourStep from '../onboardingTour/addComments/addComments'

import {touchCard} from '../../store/cards'

import Comment from './comment'

import './commentsList.scss'
Expand All @@ -32,6 +34,8 @@ type Props = {
const CommentsList = (props: Props) => {
const [newComment, setNewComment] = useState('')
const me = useAppSelector<IUser|null>(getMe)
const dispatch = useAppDispatch()

const canDeleteOthersComments = useHasCurrentBoardPermissions([Permission.DeleteOthersComments])

const onSendClicked = () => {
Expand Down Expand Up @@ -64,6 +68,8 @@ const CommentsList = (props: Props) => {
text={newComment}
placeholderText={intl.formatMessage({id: 'CardDetail.new-comment-placeholder', defaultMessage: 'Add a comment...'})}
onChange={(value: string) => {
dispatch(touchCard(props.cardId))

if (newComment !== value) {
setNewComment(value)
}
Expand Down
21 changes: 17 additions & 4 deletions webapp/src/components/cardDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import {FormattedMessage, useIntl} from 'react-intl'
import {Board} from '../blocks/board'
import {BoardView} from '../blocks/boardView'
import {Card} from '../blocks/card'
import {sendFlashMessage} from '../components/flashMessages'
import mutator from '../mutator'
import octoClient from '../octoClient'
import {getCardAttachments, updateAttachments, updateUploadPrecent} from '../store/attachments'
import {getCard} from '../store/cards'
import {getCard, getTouchedCardId, touchCard} from '../store/cards'
import {getCardComments} from '../store/comments'
import {getCardContents} from '../store/contents'
import {useAppDispatch, useAppSelector} from '../store/hooks'
Expand All @@ -27,6 +26,8 @@ import {AttachmentBlock, createAttachmentBlock} from '../blocks/attachmentBlock'
import {Block, createBlock} from '../blocks/block'
import {Permission} from '../constants'

import {sendFlashMessage} from './flashMessages'

import BoardPermissionGate from './permissions/boardPermissionGate'

import CardDetail from './cardDetail/cardDetail'
Expand All @@ -44,6 +45,7 @@ type Props = {
onClose: () => void
showCard: (cardId?: string) => void
readonly: boolean
newlyCreated?: boolean
}

const CardDialog = (props: Props): JSX.Element => {
Expand All @@ -52,6 +54,7 @@ const CardDialog = (props: Props): JSX.Element => {
const contents = useAppSelector(getCardContents(props.cardId))
const comments = useAppSelector(getCardComments(props.cardId))
const attachments = useAppSelector(getCardAttachments(props.cardId))
const touchedCardId = useAppSelector(getTouchedCardId)
const intl = useIntl()
const dispatch = useAppDispatch()
const isTemplate = card && card.fields.isTemplate
Expand Down Expand Up @@ -224,12 +227,22 @@ const CardDialog = (props: Props): JSX.Element => {
)
}

const onClose = () => {
dispatch(touchCard(undefined))

if (props.newlyCreated && props.cardId !== touchedCardId) {
handleDeleteCard()
} else {
props.onClose()
}
}

return (
<>
<Dialog
title={<div/>}
className='cardDialog'
onClose={props.onClose}
onClose={onClose}
toolsMenu={!props.readonly && !card?.limited && menu}
toolbar={attachBtn()}
>
Expand All @@ -252,7 +265,7 @@ const CardDialog = (props: Props): JSX.Element => {
comments={comments}
attachments={attachments}
readonly={props.readonly}
onClose={props.onClose}
onClose={onClose}
onDelete={deleteBlock}
addAttachment={addElement}
/>}
Expand Down
7 changes: 5 additions & 2 deletions webapp/src/components/centerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ const CenterPanel = (props: Props) => {
const [selectedCardIds, setSelectedCardIds] = useState<string[]>([])
const [cardIdToFocusOnRender, setCardIdToFocusOnRender] = useState('')
const [showHiddenCardCountNotification, setShowHiddenCardCountNotification] = useState(false)
const [newlyCreatedCardId, setNewlyCreatedCardId] = useState<string|undefined>(undefined)

const onboardingTourStarted = useAppSelector(getOnboardingTourStarted)
const onboardingTourCategory = useAppSelector(getOnboardingTourCategory)
Expand Down Expand Up @@ -164,7 +165,8 @@ const CenterPanel = (props: Props) => {
}
}, [selectedCardIds, props.readonly, props.cards])

const showCard = useCallback((cardId?: string) => {
const showCard = useCallback((cardId?: string, isNew?: boolean) => {
setNewlyCreatedCardId(cardId && isNew ? cardId : undefined)
if (selectedCardIds.length > 0) {
setSelectedCardIds([])
}
Expand Down Expand Up @@ -201,7 +203,7 @@ const CenterPanel = (props: Props) => {
if (show) {
dispatch(addCardAction(createCard(block)))
dispatch(updateView({...activeView, fields: {...activeView.fields, cardOrder: [...activeView.fields.cardOrder, block.id]}}))
showCard(block.id)
showCard(block.id, true)
} else {
// Focus on this card's title inline on next render
setCardIdToFocusOnRender(block.id)
Expand Down Expand Up @@ -413,6 +415,7 @@ const CenterPanel = (props: Props) => {
onClose={() => showCard(undefined)}
showCard={(cardId) => showCard(cardId)}
readonly={props.readonly}
newlyCreated={props.shownCardId === newlyCreatedCardId}
/>
</RootPortal>}

Expand Down
4 changes: 3 additions & 1 deletion webapp/src/mutator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {UserConfigPatch, UserPreference} from './user'
import store from './store'
import {updateBoards} from './store/boards'
import {updateViews} from './store/views'
import {updateCards} from './store/cards'
import {touchCard, updateCards} from './store/cards'
import {updateAttachments} from './store/attachments'
import {updateComments} from './store/comments'
import {updateContents} from './store/contents'
Expand Down Expand Up @@ -630,6 +630,8 @@ class Mutator {
}

async changePropertyValue(boardId: string, card: Card, propertyId: string, value?: string | string[], description = 'change property') {
store.dispatch(touchCard(card.id))

const oldValue = card.fields.properties[propertyId]

// dont save anything if property value was not changed.
Expand Down
7 changes: 6 additions & 1 deletion webapp/src/store/cards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type CardsState = {
cards: {[key: string]: Card}
templates: {[key: string]: Card}
cardHiddenWarning: boolean
touchedCardId?: string
}

export const refreshCards = createAsyncThunk<Block[], number, {state: RootState}>(
Expand Down Expand Up @@ -106,6 +107,9 @@ const cardsSlice = createSlice({
}
}
},
touchCard: (state: CardsState, action: PayloadAction<string|undefined>) => {
state.touchedCardId = action.payload
},
},
extraReducers: (builder) => {
builder.addCase(refreshCards.fulfilled, (state, action) => {
Expand Down Expand Up @@ -141,7 +145,7 @@ const cardsSlice = createSlice({
},
})

export const {updateCards, addCard, addTemplate, setCurrent, setLimitTimestamp, showCardHiddenWarning} = cardsSlice.actions
export const {updateCards, addCard, addTemplate, setCurrent, setLimitTimestamp, showCardHiddenWarning, touchCard} = cardsSlice.actions
export const {reducer} = cardsSlice

export const getCards = (state: RootState): {[key: string]: Card} => state.cards.cards
Expand Down Expand Up @@ -410,3 +414,4 @@ export const getCurrentCard = createSelector(

export const getCardLimitTimestamp = (state: RootState): number => state.cards.limitTimestamp
export const getCardHiddenWarning = (state: RootState): boolean => state.cards.cardHiddenWarning
export const getTouchedCardId = (state: RootState): string|undefined => state.cards.touchedCardId