mirror of
https://github.com/mattermost/focalboard.git
synced 2024-12-21 13:38:56 +02:00
* Improve the board creation from channels
* Fixing linter problem and adding channelID to the telemetry information
* Fixing and expanding a bit the tests
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
(cherry picked from commit 93bc9de731
)
Co-authored-by: Jesús Espino <jespinog@gmail.com>
This commit is contained in:
parent
6d5fa273b6
commit
3da7ca8e74
@ -12,8 +12,7 @@ import {useWebsockets} from '../../../../webapp/src/hooks/websockets'
|
||||
import octoClient from '../../../../webapp/src/octoClient'
|
||||
import mutator from '../../../../webapp/src/mutator'
|
||||
import {getCurrentTeamId, getAllTeams, Team} from '../../../../webapp/src/store/teams'
|
||||
import {createBoard, BoardsAndBlocks, Board} from '../../../../webapp/src/blocks/board'
|
||||
import {createBoardView} from '../../../../webapp/src/blocks/boardView'
|
||||
import {createBoard, Board} from '../../../../webapp/src/blocks/board'
|
||||
import {useAppSelector, useAppDispatch} from '../../../../webapp/src/store/hooks'
|
||||
import {EmptySearch, EmptyResults} from '../../../../webapp/src/components/searchDialog/searchDialog'
|
||||
import ConfirmationDialog from '../../../../webapp/src/components/confirmationDialogBox'
|
||||
@ -21,11 +20,13 @@ import Dialog from '../../../../webapp/src/components/dialog'
|
||||
import SearchIcon from '../../../../webapp/src/widgets/icons/search'
|
||||
import Button from '../../../../webapp/src/widgets/buttons/button'
|
||||
import {getCurrentLinkToChannel, setLinkToChannel} from '../../../../webapp/src/store/boards'
|
||||
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../../../webapp/src/telemetry/telemetryClient'
|
||||
import {WSClient} from '../../../../webapp/src/wsclient'
|
||||
import {SuiteWindow} from '../../../../webapp/src/types/index'
|
||||
|
||||
import BoardSelectorItem from './boardSelectorItem'
|
||||
|
||||
const windowAny = (window as SuiteWindow)
|
||||
|
||||
import './boardSelector.scss'
|
||||
|
||||
const BoardSelector = () => {
|
||||
@ -107,27 +108,8 @@ const BoardSelector = () => {
|
||||
}
|
||||
|
||||
const newLinkedBoard = async (): Promise<void> => {
|
||||
const board = {...createBoard(), teamId, channelId: currentChannel}
|
||||
|
||||
const view = createBoardView()
|
||||
view.fields.viewType = 'board'
|
||||
view.parentId = board.id
|
||||
view.boardId = board.id
|
||||
view.title = intl.formatMessage({id: 'View.NewBoardTitle', defaultMessage: 'Board view'})
|
||||
|
||||
await mutator.createBoardsAndBlocks(
|
||||
{boards: [board], blocks: [view]},
|
||||
'add linked board',
|
||||
async (bab: BoardsAndBlocks): Promise<void> => {
|
||||
const windowAny: any = window
|
||||
const newBoard = bab.boards[0]
|
||||
// TODO: Maybe create a new event for create linked board
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoard, {board: newBoard?.id})
|
||||
windowAny.WebappUtils.browserHistory.push(`/boards/team/${teamId}/${newBoard.id}`)
|
||||
dispatch(setLinkToChannel(''))
|
||||
},
|
||||
async () => {return},
|
||||
)
|
||||
window.open(`${windowAny.frontendBaseURL}/team/${teamId}/new/${currentChannel}`, '_blank', 'noopener')
|
||||
dispatch(setLinkToChannel(''))
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -284,8 +284,9 @@ export default class Plugin {
|
||||
|
||||
const goToFocalboardTemplate = () => {
|
||||
const currentTeam = mmStore.getState().entities.teams.currentTeamId
|
||||
const currentChannel = mmStore.getState().entities.channels.currentChannelId
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelIntro, {teamID: currentTeam})
|
||||
window.open(`${windowAny.frontendBaseURL}/team/${currentTeam}`, '_blank', 'noopener')
|
||||
window.open(`${windowAny.frontendBaseURL}/team/${currentTeam}/new/${currentChannel}`, '_blank', 'noopener')
|
||||
}
|
||||
|
||||
if (registry.registerChannelIntroButtonAction) {
|
||||
|
@ -15,6 +15,7 @@ import {MemoryRouter, Router} from 'react-router-dom'
|
||||
import Mutator from '../../mutator'
|
||||
import {Utils} from '../../utils'
|
||||
import {Team} from '../../store/teams'
|
||||
import {createBoard, Board} from '../../blocks/board'
|
||||
import {IUser} from '../../user'
|
||||
import {mockDOM, mockStateStore, wrapDNDIntl} from '../../testUtils'
|
||||
|
||||
@ -227,17 +228,22 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
userEvent.click(divNewTemplate!)
|
||||
expect(mockedMutator.addEmptyBoardTemplate).toBeCalledTimes(1)
|
||||
})
|
||||
test('return BoardTemplateSelector and click empty board', () => {
|
||||
test('return BoardTemplateSelector and click empty board', async () => {
|
||||
const newBoard = createBoard({id: 'new-board'} as Board)
|
||||
mockedMutator.addEmptyBoard.mockResolvedValue({boards: [newBoard], blocks: []})
|
||||
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BoardTemplateSelector onClose={jest.fn()}/>
|
||||
</ReduxProvider>
|
||||
,
|
||||
), {wrapper: MemoryRouter})
|
||||
|
||||
const divEmptyboard = screen.getByText('Create empty board').parentElement
|
||||
expect(divEmptyboard).not.toBeNull()
|
||||
userEvent.click(divEmptyboard!)
|
||||
expect(mockedMutator.addEmptyBoard).toBeCalledTimes(1)
|
||||
await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))
|
||||
})
|
||||
test('return BoardTemplateSelector and click delete template icon', async () => {
|
||||
const root = document.createElement('div')
|
||||
@ -279,6 +285,9 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
userEvent.click(editIcon!)
|
||||
})
|
||||
test('return BoardTemplateSelector and click to add board from template', async () => {
|
||||
const newBoard = createBoard({id: 'new-board'} as Board)
|
||||
mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})
|
||||
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BoardTemplateSelector onClose={jest.fn()}/>
|
||||
@ -300,8 +309,44 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))
|
||||
await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))
|
||||
})
|
||||
|
||||
test('return BoardTemplateSelector and click to add board from template with channelId', async () => {
|
||||
const newBoard = createBoard({id: 'new-board'} as Board)
|
||||
mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})
|
||||
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BoardTemplateSelector
|
||||
onClose={jest.fn()}
|
||||
channelId='test-channel'
|
||||
/>
|
||||
</ReduxProvider>
|
||||
,
|
||||
), {wrapper: MemoryRouter})
|
||||
const divBoardToSelect = screen.getByText(template1Title).parentElement
|
||||
expect(divBoardToSelect).not.toBeNull()
|
||||
|
||||
act(() => {
|
||||
userEvent.click(divBoardToSelect!)
|
||||
})
|
||||
|
||||
const useTemplateButton = screen.getByText('Use this template').parentElement
|
||||
expect(useTemplateButton).not.toBeNull()
|
||||
act(() => {
|
||||
userEvent.click(useTemplateButton!)
|
||||
})
|
||||
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '1', team1.id))
|
||||
await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith({...newBoard, channelId: 'test-channel'}, newBoard, 'linked channel'))
|
||||
})
|
||||
|
||||
test('return BoardTemplateSelector and click to add board from global template', async () => {
|
||||
const newBoard = createBoard({id: 'new-board'} as Board)
|
||||
mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})
|
||||
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BoardTemplateSelector onClose={jest.fn()}/>
|
||||
@ -323,8 +368,12 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), 'global-1', team1.id))
|
||||
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_global'}))
|
||||
await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))
|
||||
})
|
||||
test('should start product tour on choosing welcome template', async () => {
|
||||
const newBoard = createBoard({id: 'new-board'} as Board)
|
||||
mockedMutator.addBoardFromTemplate.mockResolvedValue({boards: [newBoard], blocks: []})
|
||||
|
||||
render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<BoardTemplateSelector onClose={jest.fn()}/>
|
||||
@ -347,6 +396,7 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledTimes(1))
|
||||
await waitFor(() => expect(mockedMutator.addBoardFromTemplate).toBeCalledWith(team1.id, expect.anything(), expect.anything(), expect.anything(), '2', team1.id))
|
||||
await waitFor(() => expect(mockedTelemetry.trackEvent).toBeCalledWith('boards', 'createBoardViaTemplate', {boardTemplateId: 'template_id_2'}))
|
||||
await waitFor(() => expect(mockedMutator.updateBoard).toBeCalledWith(newBoard, newBoard, 'linked channel'))
|
||||
expect(mockedOctoClient.patchUserConfig).toBeCalledWith('user-id-1', {
|
||||
updatedFields: {
|
||||
'focalboard_onboardingTourStarted': '1',
|
||||
|
@ -34,6 +34,7 @@ type Props = {
|
||||
title?: React.ReactNode
|
||||
description?: React.ReactNode
|
||||
onClose?: () => void
|
||||
channelId?: string
|
||||
}
|
||||
|
||||
const BoardTemplateSelector = (props: Props) => {
|
||||
@ -99,10 +100,12 @@ const BoardTemplateSelector = (props: Props) => {
|
||||
|
||||
const handleUseTemplate = async () => {
|
||||
if (activeTemplate.teamId === '0') {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoardViaTemplate, {boardTemplateId: activeTemplate.properties.trackingTemplateId as string})
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.CreateBoardViaTemplate, {boardTemplateId: activeTemplate.properties.trackingTemplateId as string, channelID: props.channelId})
|
||||
}
|
||||
|
||||
await mutator.addBoardFromTemplate(currentTeam?.id || Constants.globalTeamId, intl, showBoard, () => showBoard(currentBoardId), activeTemplate.id, currentTeam?.id)
|
||||
const boardsAndBlocks = await mutator.addBoardFromTemplate(currentTeam?.id || Constants.globalTeamId, intl, showBoard, () => showBoard(currentBoardId), activeTemplate.id, currentTeam?.id)
|
||||
const board = boardsAndBlocks.boards[0]
|
||||
await mutator.updateBoard({...board, channelId: props.channelId || ''}, board, 'linked channel')
|
||||
if (activeTemplate.title === OnboardingBoardTitle) {
|
||||
resetTour()
|
||||
}
|
||||
@ -193,7 +196,11 @@ const BoardTemplateSelector = (props: Props) => {
|
||||
filled={false}
|
||||
emphasis={'secondary'}
|
||||
size={'medium'}
|
||||
onClick={() => mutator.addEmptyBoard(currentTeam?.id || '', intl, showBoard, () => showBoard(currentBoardId))}
|
||||
onClick={async () => {
|
||||
const boardsAndBlocks = await mutator.addEmptyBoard(currentTeam?.id || '', intl, showBoard, () => showBoard(currentBoardId))
|
||||
const board = boardsAndBlocks.boards[0]
|
||||
await mutator.updateBoard({...board, channelId: props.channelId || ''}, board, 'linked channel')
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='BoardTemplateSelector.create-empty-board'
|
||||
|
@ -14,6 +14,7 @@ import LockOutline from '../../widgets/icons/lockOutline'
|
||||
import {useAppSelector} from '../../store/hooks'
|
||||
import {getAllTeams, getCurrentTeam, Team} from '../../store/teams'
|
||||
import {getMe} from '../../store/users'
|
||||
import {Utils} from '../../utils'
|
||||
import {BoardTypeOpen, BoardTypePrivate} from '../../blocks/board'
|
||||
|
||||
type Props = {
|
||||
@ -42,7 +43,7 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => {
|
||||
if (!me) {
|
||||
return
|
||||
}
|
||||
const newPath = generatePath(match.path, {...match.params, teamId, boardId, viewId: undefined})
|
||||
const newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, teamId, boardId, viewId: undefined})
|
||||
history.push(newPath)
|
||||
props.onClose()
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ const SidebarCategory = (props: Props) => {
|
||||
if (boardId !== match.params.boardId && viewId !== match.params.viewId) {
|
||||
params.cardId = undefined
|
||||
}
|
||||
const newPath = generatePath(match.path, params)
|
||||
const newPath = generatePath(Utils.getBoardPagePath(match.path), params)
|
||||
history.push(newPath)
|
||||
props.hideSidebar()
|
||||
}, [match, history])
|
||||
|
@ -37,7 +37,7 @@ const ViewMenu = (props: Props) => {
|
||||
const match = useRouteMatch()
|
||||
|
||||
const showView = useCallback((viewId) => {
|
||||
let newPath = generatePath(match.path, {...match.params, viewId: viewId || ''})
|
||||
let newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, viewId: viewId || ''})
|
||||
if (props.readonly) {
|
||||
newPath += `?r=${Utils.getReadToken()}`
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ type Props = {
|
||||
function CenterContent(props: Props) {
|
||||
const team = useAppSelector(getCurrentTeam)
|
||||
const isLoading = useAppSelector(isLoadingBoard)
|
||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string}>()
|
||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string, channelId?: string}>()
|
||||
const board = useAppSelector(getCurrentBoard)
|
||||
const templates = useAppSelector(getTemplates)
|
||||
const cards = useAppSelector(getCurrentViewCardsSortedFilteredAndGrouped)
|
||||
@ -51,7 +51,7 @@ function CenterContent(props: Props) {
|
||||
|
||||
const showCard = useCallback((cardId?: string) => {
|
||||
const params = {...match.params, cardId}
|
||||
let newPath = generatePath(match.path, params)
|
||||
let newPath = generatePath(Utils.getBoardPagePath(match.path), params)
|
||||
if (props.readonly) {
|
||||
newPath += `?r=${Utils.getReadToken()}`
|
||||
}
|
||||
@ -129,6 +129,7 @@ function CenterContent(props: Props) {
|
||||
}}
|
||||
/>
|
||||
}
|
||||
channelId={match.params.channelId}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ import './boardPage.scss'
|
||||
|
||||
type Props = {
|
||||
readonly?: boolean
|
||||
new?: boolean
|
||||
}
|
||||
|
||||
const BoardPage = (props: Props): JSX.Element => {
|
||||
@ -202,7 +203,7 @@ const BoardPage = (props: Props): JSX.Element => {
|
||||
|
||||
return (
|
||||
<div className='BoardPage'>
|
||||
<TeamToBoardAndViewRedirect/>
|
||||
{!props.new && <TeamToBoardAndViewRedirect/>}
|
||||
<BackwardCompatibilityQueryParamsRedirect/>
|
||||
<SetWindowTitleAndIcon/>
|
||||
<UndoRedoHotKeys/>
|
||||
|
@ -7,6 +7,7 @@ import {getBoards, getCurrentBoardId} from '../../store/boards'
|
||||
import {setCurrent as setCurrentView, getCurrentBoardViews} from '../../store/views'
|
||||
import {useAppSelector, useAppDispatch} from '../../store/hooks'
|
||||
import {UserSettings} from '../../userSettings'
|
||||
import {Utils} from '../../utils'
|
||||
import {getSidebarCategories} from '../../store/sidebar'
|
||||
import {Constants} from "../../constants"
|
||||
|
||||
@ -49,7 +50,7 @@ const TeamToBoardAndViewRedirect = (): null => {
|
||||
}
|
||||
|
||||
if (boardID) {
|
||||
const newPath = generatePath(match.path, {...match.params, boardId: boardID, viewID: undefined})
|
||||
const newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, boardId: boardID, viewID: undefined})
|
||||
history.replace(newPath)
|
||||
|
||||
// return from here because the loadBoardData() call
|
||||
@ -77,7 +78,7 @@ const TeamToBoardAndViewRedirect = (): null => {
|
||||
}
|
||||
|
||||
if (viewID) {
|
||||
const newPath = generatePath(match.path, {...match.params, viewId: viewID})
|
||||
const newPath = generatePath(Utils.getBoardPagePath(match.path), {...match.params, viewId: viewID})
|
||||
history.replace(newPath)
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +165,10 @@ const FocalboardRouter = (props: Props): JSX.Element => {
|
||||
<ChangePasswordPage/>
|
||||
</FBRoute>}
|
||||
|
||||
<FBRoute path={['/team/:teamId/new/:channelId']}>
|
||||
<BoardPage new={true}/>
|
||||
</FBRoute>
|
||||
|
||||
<FBRoute path={['/team/:teamId/shared/:boardId?/:viewId?/:cardId?', '/shared/:boardId?/:viewId?/:cardId?']}>
|
||||
<BoardPage readonly={true}/>
|
||||
</FBRoute>
|
||||
|
1
webapp/src/types/index.d.ts
vendored
1
webapp/src/types/index.d.ts
vendored
@ -21,4 +21,5 @@ export type SuiteWindow = Window & {
|
||||
baseURL?: string
|
||||
frontendBaseURL?: string
|
||||
isFocalboardPlugin?: boolean
|
||||
WebappUtils?: any
|
||||
}
|
||||
|
@ -757,6 +757,13 @@ class Utils {
|
||||
return (Utils.isMac() && e.metaKey) || (!Utils.isMac() && e.ctrlKey && !e.altKey)
|
||||
}
|
||||
|
||||
static getBoardPagePath(currentPath: string) {
|
||||
if (currentPath == "/team/:teamId/new/:channelId") {
|
||||
return "/team/:teamId/:boardId?/:viewId?/:cardId?"
|
||||
}
|
||||
return currentPath
|
||||
}
|
||||
|
||||
static showBoard(
|
||||
boardId: string,
|
||||
match: routerMatch<{boardId: string, viewId?: string, cardId?: string, teamId?: string}>,
|
||||
@ -769,7 +776,7 @@ class Utils {
|
||||
params.viewId = undefined
|
||||
params.cardId = undefined
|
||||
}
|
||||
const newPath = generatePath(match.path, params)
|
||||
const newPath = generatePath(Utils.getBoardPagePath(match.path), params)
|
||||
history.push(newPath)
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user