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

Improve the board creation from channels #3415

Merged
Merged
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
30 changes: 6 additions & 24 deletions mattermost-plugin/webapp/src/components/boardSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,21 @@ 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'
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 = () => {
Expand Down Expand Up @@ -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 (
Expand Down
3 changes: 2 additions & 1 deletion mattermost-plugin/webapp/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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()}/>
Expand All @@ -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()}/>
Expand All @@ -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()}/>
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ type Props = {
title?: React.ReactNode
description?: React.ReactNode
onClose?: () => void
channelId?: string
}

const BoardTemplateSelector = (props: Props) => {
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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()
}
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/components/sidebar/sidebarCategory.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/components/viewMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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()}`
}
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/components/workspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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()}`
}
Expand Down Expand Up @@ -129,6 +129,7 @@ function CenterContent(props: Props) {
}}
/>
}
channelId={match.params.channelId}
/>
)
}
Expand Down
3 changes: 2 additions & 1 deletion webapp/src/pages/boardPage/boardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ import './boardPage.scss'

type Props = {
readonly?: boolean
new?: boolean
}

const BoardPage = (props: Props): JSX.Element => {
Expand Down Expand Up @@ -202,7 +203,7 @@ const BoardPage = (props: Props): JSX.Element => {

return (
<div className='BoardPage'>
<TeamToBoardAndViewRedirect/>
{!props.new && <TeamToBoardAndViewRedirect/>}
<BackwardCompatibilityQueryParamsRedirect/>
<SetWindowTitleAndIcon/>
<UndoRedoHotKeys/>
Expand Down
5 changes: 3 additions & 2 deletions webapp/src/pages/boardPage/teamToBoardAndViewRedirect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
}
Expand Down
4 changes: 4 additions & 0 deletions webapp/src/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
1 change: 1 addition & 0 deletions webapp/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ export type SuiteWindow = Window & {
baseURL?: string
frontendBaseURL?: string
isFocalboardPlugin?: boolean
WebappUtils?: any
}
9 changes: 8 additions & 1 deletion webapp/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}>,
Expand All @@ -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)
}

Expand Down