Cross team search (#2829)
* WIP * Added test * Added snapshot * Updated server tests * Fixed server lint * Fixed webapp lint * Updated server tests
This commit is contained in:
parent
958fbd28bf
commit
50930a1cd3
14 changed files with 245 additions and 49 deletions
|
@ -3166,7 +3166,7 @@ func (a *API) handleSearchBoards(w http.ResponseWriter, r *http.Request) {
|
||||||
auditRec.AddMeta("teamID", teamID)
|
auditRec.AddMeta("teamID", teamID)
|
||||||
|
|
||||||
// retrieve boards list
|
// retrieve boards list
|
||||||
boards, err := a.app.SearchBoardsForUserAndTeam(term, userID, teamID)
|
boards, err := a.app.SearchBoardsForUser(term, userID, teamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -369,8 +369,8 @@ func (a *App) DeleteBoardMember(boardID, userID string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) SearchBoardsForUserAndTeam(term, userID, teamID string) ([]*model.Board, error) {
|
func (a *App) SearchBoardsForUser(term, userID, teamID string) ([]*model.Board, error) {
|
||||||
return a.store.SearchBoardsForUserAndTeam(term, userID, teamID)
|
return a.store.SearchBoardsForUser(term, userID, teamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) UndeleteBoard(boardID string, modifiedBy string) error {
|
func (a *App) UndeleteBoard(boardID string, modifiedBy string) error {
|
||||||
|
|
|
@ -543,25 +543,25 @@ func TestSearchBoards(t *testing.T) {
|
||||||
Name: "should return all boards where user1 is member or that are public",
|
Name: "should return all boards where user1 is member or that are public",
|
||||||
Client: th.Client,
|
Client: th.Client,
|
||||||
Term: "board",
|
Term: "board",
|
||||||
ExpectedIDs: []string{rBoard1.ID, rBoard2.ID, rBoard3.ID},
|
ExpectedIDs: []string{rBoard1.ID, rBoard2.ID, rBoard3.ID, board5.ID},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "matching a full word",
|
Name: "matching a full word",
|
||||||
Client: th.Client,
|
Client: th.Client,
|
||||||
Term: "admin",
|
Term: "admin",
|
||||||
ExpectedIDs: []string{rBoard1.ID, rBoard3.ID},
|
ExpectedIDs: []string{rBoard1.ID, rBoard3.ID, board5.ID},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "matching part of the word",
|
Name: "matching part of the word",
|
||||||
Client: th.Client,
|
Client: th.Client,
|
||||||
Term: "ubli",
|
Term: "ubli",
|
||||||
ExpectedIDs: []string{rBoard1.ID, rBoard2.ID},
|
ExpectedIDs: []string{rBoard1.ID, rBoard2.ID, board5.ID},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "case insensitive",
|
Name: "case insensitive",
|
||||||
Client: th.Client,
|
Client: th.Client,
|
||||||
Term: "UBLI",
|
Term: "UBLI",
|
||||||
ExpectedIDs: []string{rBoard1.ID, rBoard2.ID},
|
ExpectedIDs: []string{rBoard1.ID, rBoard2.ID, board5.ID},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "user2 can only see the public boards, as he's not a member of any",
|
Name: "user2 can only see the public boards, as he's not a member of any",
|
||||||
|
|
|
@ -1158,19 +1158,19 @@ func (mr *MockStoreMockRecorder) SaveMember(arg0 interface{}) *gomock.Call {
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveMember", reflect.TypeOf((*MockStore)(nil).SaveMember), arg0)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaveMember", reflect.TypeOf((*MockStore)(nil).SaveMember), arg0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchBoardsForUserAndTeam mocks base method.
|
// SearchBoardsForUser mocks base method.
|
||||||
func (m *MockStore) SearchBoardsForUserAndTeam(arg0, arg1, arg2 string) ([]*model.Board, error) {
|
func (m *MockStore) SearchBoardsForUser(arg0, arg1, arg2 string) ([]*model.Board, error) {
|
||||||
m.ctrl.T.Helper()
|
m.ctrl.T.Helper()
|
||||||
ret := m.ctrl.Call(m, "SearchBoardsForUserAndTeam", arg0, arg1, arg2)
|
ret := m.ctrl.Call(m, "SearchBoardsForUser", arg0, arg1, arg2)
|
||||||
ret0, _ := ret[0].([]*model.Board)
|
ret0, _ := ret[0].([]*model.Board)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchBoardsForUserAndTeam indicates an expected call of SearchBoardsForUserAndTeam.
|
// SearchBoardsForUser indicates an expected call of SearchBoardsForUser.
|
||||||
func (mr *MockStoreMockRecorder) SearchBoardsForUserAndTeam(arg0, arg1, arg2 interface{}) *gomock.Call {
|
func (mr *MockStoreMockRecorder) SearchBoardsForUser(arg0, arg1, arg2 interface{}) *gomock.Call {
|
||||||
mr.mock.ctrl.T.Helper()
|
mr.mock.ctrl.T.Helper()
|
||||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchBoardsForUserAndTeam", reflect.TypeOf((*MockStore)(nil).SearchBoardsForUserAndTeam), arg0, arg1, arg2)
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SearchBoardsForUser", reflect.TypeOf((*MockStore)(nil).SearchBoardsForUser), arg0, arg1, arg2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchUsersByTeam mocks base method.
|
// SearchUsersByTeam mocks base method.
|
||||||
|
|
|
@ -597,17 +597,16 @@ func (s *SQLStore) getMembersForBoard(db sq.BaseRunner, boardID string) ([]*mode
|
||||||
return s.boardMembersFromRows(rows)
|
return s.boardMembersFromRows(rows)
|
||||||
}
|
}
|
||||||
|
|
||||||
// searchBoardsForUserAndTeam returns all boards that match with the
|
// searchBoardsForUser returns all boards that match with the
|
||||||
// term that are either private and which the user is a member of, or
|
// term that are either private and which the user is a member of, or
|
||||||
// they're open, regardless of the user membership.
|
// they're open, regardless of the user membership.
|
||||||
// Search is case-insensitive.
|
// Search is case-insensitive.
|
||||||
func (s *SQLStore) searchBoardsForUserAndTeam(db sq.BaseRunner, term, userID, teamID string) ([]*model.Board, error) {
|
func (s *SQLStore) searchBoardsForUser(db sq.BaseRunner, term, userID string) ([]*model.Board, error) {
|
||||||
query := s.getQueryBuilder(db).
|
query := s.getQueryBuilder(db).
|
||||||
Select(boardFields("b.")...).
|
Select(boardFields("b.")...).
|
||||||
Distinct().
|
Distinct().
|
||||||
From(s.tablePrefix + "boards as b").
|
From(s.tablePrefix + "boards as b").
|
||||||
LeftJoin(s.tablePrefix + "board_members as bm on b.id=bm.board_id").
|
LeftJoin(s.tablePrefix + "board_members as bm on b.id=bm.board_id").
|
||||||
Where(sq.Eq{"b.team_id": teamID}).
|
|
||||||
Where(sq.Eq{"b.is_template": false}).
|
Where(sq.Eq{"b.is_template": false}).
|
||||||
Where(sq.Or{
|
Where(sq.Or{
|
||||||
sq.Eq{"b.type": model.BoardTypeOpen},
|
sq.Eq{"b.type": model.BoardTypeOpen},
|
||||||
|
@ -635,7 +634,7 @@ func (s *SQLStore) searchBoardsForUserAndTeam(db sq.BaseRunner, term, userID, te
|
||||||
|
|
||||||
rows, err := query.Query()
|
rows, err := query.Query()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.logger.Error(`searchBoardsForUserAndTeam ERROR`, mlog.Err(err))
|
s.logger.Error(`searchBoardsForUser ERROR`, mlog.Err(err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer s.CloseRows(rows)
|
defer s.CloseRows(rows)
|
||||||
|
|
|
@ -677,8 +677,8 @@ func (s *SQLStore) SaveMember(bm *model.BoardMember) (*model.BoardMember, error)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SQLStore) SearchBoardsForUserAndTeam(term string, userID string, teamID string) ([]*model.Board, error) {
|
func (s *SQLStore) SearchBoardsForUser(term string, userID string, teamID string) ([]*model.Board, error) {
|
||||||
return s.searchBoardsForUserAndTeam(s.db, term, userID, teamID)
|
return s.searchBoardsForUser(s.db, term, userID)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,7 +99,7 @@ type Store interface {
|
||||||
GetBoardMemberHistory(boardID, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error)
|
GetBoardMemberHistory(boardID, userID string, limit uint64) ([]*model.BoardMemberHistoryEntry, error)
|
||||||
GetMembersForBoard(boardID string) ([]*model.BoardMember, error)
|
GetMembersForBoard(boardID string) ([]*model.BoardMember, error)
|
||||||
GetMembersForUser(userID string) ([]*model.BoardMember, error)
|
GetMembersForUser(userID string) ([]*model.BoardMember, error)
|
||||||
SearchBoardsForUserAndTeam(term, userID, teamID string) ([]*model.Board, error)
|
SearchBoardsForUser(term, userID, teamID string) ([]*model.Board, error)
|
||||||
|
|
||||||
// @withTransaction
|
// @withTransaction
|
||||||
CreateBoardsAndBlocksWithAdmin(bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, []*model.BoardMember, error)
|
CreateBoardsAndBlocksWithAdmin(bab *model.BoardsAndBlocks, userID string) (*model.BoardsAndBlocks, []*model.BoardMember, error)
|
||||||
|
|
|
@ -68,10 +68,10 @@ func StoreTestBoardStore(t *testing.T, setup func(t *testing.T) (store.Store, fu
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
testDeleteMember(t, store)
|
testDeleteMember(t, store)
|
||||||
})
|
})
|
||||||
t.Run("SearchBoardsForUserAndTeam", func(t *testing.T) {
|
t.Run("SearchBoardsForUser", func(t *testing.T) {
|
||||||
store, tearDown := setup(t)
|
store, tearDown := setup(t)
|
||||||
defer tearDown()
|
defer tearDown()
|
||||||
testSearchBoardsForUserAndTeam(t, store)
|
testSearchBoardsForUser(t, store)
|
||||||
})
|
})
|
||||||
t.Run("GetBoardHistory", func(t *testing.T) {
|
t.Run("GetBoardHistory", func(t *testing.T) {
|
||||||
store, tearDown := setup(t)
|
store, tearDown := setup(t)
|
||||||
|
@ -683,13 +683,13 @@ func testDeleteMember(t *testing.T, store store.Store) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSearchBoardsForUserAndTeam(t *testing.T, store store.Store) {
|
func testSearchBoardsForUser(t *testing.T, store store.Store) {
|
||||||
teamID1 := "team-id-1"
|
teamID1 := "team-id-1"
|
||||||
teamID2 := "team-id-2"
|
teamID2 := "team-id-2"
|
||||||
userID := "user-id-1"
|
userID := "user-id-1"
|
||||||
|
|
||||||
t.Run("should return empty if user is not a member of any board and there are no public boards on the team", func(t *testing.T) {
|
t.Run("should return empty if user is not a member of any board and there are no public boards on the team", func(t *testing.T) {
|
||||||
boards, err := store.SearchBoardsForUserAndTeam("", userID, teamID1)
|
boards, err := store.SearchBoardsForUser("", userID, teamID1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, boards)
|
require.Empty(t, boards)
|
||||||
})
|
})
|
||||||
|
@ -751,21 +751,21 @@ func testSearchBoardsForUserAndTeam(t *testing.T, store store.Store) {
|
||||||
TeamID: teamID1,
|
TeamID: teamID1,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Term: "",
|
Term: "",
|
||||||
ExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID},
|
ExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID, board5.ID},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "should find all with term board",
|
Name: "should find all with term board",
|
||||||
TeamID: teamID1,
|
TeamID: teamID1,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Term: "board",
|
Term: "board",
|
||||||
ExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID},
|
ExpectedBoardIDs: []string{board1.ID, board2.ID, board3.ID, board5.ID},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "should find only public as per the term, wether user is a member or not",
|
Name: "should find only public as per the term, wether user is a member or not",
|
||||||
TeamID: teamID1,
|
TeamID: teamID1,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
Term: "public",
|
Term: "public",
|
||||||
ExpectedBoardIDs: []string{board1.ID, board2.ID},
|
ExpectedBoardIDs: []string{board1.ID, board2.ID, board5.ID},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "should find only private as per the term, wether user is a member or not",
|
Name: "should find only private as per the term, wether user is a member or not",
|
||||||
|
@ -774,13 +774,6 @@ func testSearchBoardsForUserAndTeam(t *testing.T, store store.Store) {
|
||||||
Term: "priv",
|
Term: "priv",
|
||||||
ExpectedBoardIDs: []string{board3.ID},
|
ExpectedBoardIDs: []string{board3.ID},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
Name: "should find the only board in team 2",
|
|
||||||
TeamID: teamID2,
|
|
||||||
UserID: userID,
|
|
||||||
Term: "",
|
|
||||||
ExpectedBoardIDs: []string{board5.ID},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Name: "should find no board in team 2 with a non matching term",
|
Name: "should find no board in team 2 with a non matching term",
|
||||||
TeamID: teamID2,
|
TeamID: teamID2,
|
||||||
|
@ -792,7 +785,7 @@ func testSearchBoardsForUserAndTeam(t *testing.T, store store.Store) {
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.Name, func(t *testing.T) {
|
t.Run(tc.Name, func(t *testing.T) {
|
||||||
boards, err := store.SearchBoardsForUserAndTeam(tc.Term, tc.UserID, tc.TeamID)
|
boards, err := store.SearchBoardsForUser(tc.Term, tc.UserID, tc.TeamID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
boardIDs := []string{}
|
boardIDs := []string{}
|
||||||
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`component/BoardSwitcherDialog base case 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="Dialog dialog-back BoardSwitcherDialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="wrapper"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="dialog"
|
||||||
|
role="dialog"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="toolbar"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="IconButton size--medium"
|
||||||
|
title="Close dialog"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-close CloseIcon"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="BoardSwitcherDialogBody"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="head"
|
||||||
|
>
|
||||||
|
<h3
|
||||||
|
class="text-heading4"
|
||||||
|
>
|
||||||
|
Find Boards
|
||||||
|
</h3>
|
||||||
|
<h5>
|
||||||
|
Type to find a board. Use
|
||||||
|
<b>
|
||||||
|
UP/DOWN
|
||||||
|
</b>
|
||||||
|
to browse.
|
||||||
|
<b>
|
||||||
|
ENTER
|
||||||
|
</b>
|
||||||
|
to select,
|
||||||
|
<b>
|
||||||
|
ESC
|
||||||
|
</b>
|
||||||
|
to dismiss
|
||||||
|
</h5>
|
||||||
|
<div
|
||||||
|
class="queryWrapper"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-magnify MagnifyIcon"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
class="searchQuery"
|
||||||
|
maxlength="100"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="searchResults"
|
||||||
|
>
|
||||||
|
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="noResults introScreen"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="iconWrapper"
|
||||||
|
>
|
||||||
|
<i
|
||||||
|
class="CompassIcon icon-magnify MagnifyIcon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h4
|
||||||
|
class="text-heading4"
|
||||||
|
>
|
||||||
|
Search for boards
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -3,6 +3,7 @@
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
.CompassIcon {
|
.CompassIcon {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
|
@ -20,4 +21,14 @@
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resultTitle {
|
||||||
|
max-width: 60%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.teamTitle {
|
||||||
|
right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
color: rgba(var(--center-channel-color-rgb), 0.56);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import {MockStoreEnhanced} from "redux-mock-store"
|
||||||
|
|
||||||
|
import {Provider as ReduxProvider} from 'react-redux'
|
||||||
|
|
||||||
|
import {render} from "@testing-library/react"
|
||||||
|
|
||||||
|
import {createMemoryHistory, History} from "history"
|
||||||
|
|
||||||
|
import {Router} from "react-router-dom"
|
||||||
|
|
||||||
|
import {Team} from "../../store/teams"
|
||||||
|
import {TestBlockFactory} from "../../test/testBlockFactory"
|
||||||
|
|
||||||
|
import {mockStateStore, wrapDNDIntl} from "../../testUtils"
|
||||||
|
|
||||||
|
import BoardSwitcherDialog from "./boardSwitcherDialog"
|
||||||
|
|
||||||
|
|
||||||
|
describe('component/BoardSwitcherDialog', () => {
|
||||||
|
const team1: Team = {
|
||||||
|
id: 'team-id-1',
|
||||||
|
title: 'Dunder Mifflin',
|
||||||
|
signupToken: '',
|
||||||
|
updateAt: 0,
|
||||||
|
modifiedBy: 'michael-scott',
|
||||||
|
}
|
||||||
|
|
||||||
|
const team2: Team = {
|
||||||
|
id: 'team-id-2',
|
||||||
|
title: 'Michael Scott Paper Company',
|
||||||
|
signupToken: '',
|
||||||
|
updateAt: 0,
|
||||||
|
modifiedBy: 'michael-scott',
|
||||||
|
}
|
||||||
|
|
||||||
|
const me = TestBlockFactory.createUser()
|
||||||
|
|
||||||
|
const state = {
|
||||||
|
users: {
|
||||||
|
me: me,
|
||||||
|
},
|
||||||
|
teams: {
|
||||||
|
allTeams: [team1, team2],
|
||||||
|
current: team1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let store:MockStoreEnhanced<unknown, unknown>
|
||||||
|
let history: History
|
||||||
|
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
store = mockStateStore([], state)
|
||||||
|
history = createMemoryHistory()
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
test('base case', () => {
|
||||||
|
const onCloseHandler = jest.fn()
|
||||||
|
const component = wrapDNDIntl(
|
||||||
|
<Router history={history}>
|
||||||
|
<ReduxProvider store={store}>
|
||||||
|
<BoardSwitcherDialog onClose={onCloseHandler}/>
|
||||||
|
</ReduxProvider>
|
||||||
|
</Router>
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container} = render(component)
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
|
@ -12,7 +12,7 @@ import SearchDialog from '../searchDialog/searchDialog'
|
||||||
import Globe from '../../widgets/icons/globe'
|
import Globe from '../../widgets/icons/globe'
|
||||||
import LockOutline from '../../widgets/icons/lockOutline'
|
import LockOutline from '../../widgets/icons/lockOutline'
|
||||||
import {useAppSelector} from '../../store/hooks'
|
import {useAppSelector} from '../../store/hooks'
|
||||||
import {getCurrentTeam} from '../../store/teams'
|
import {getAllTeams, getCurrentTeam, Team} from '../../store/teams'
|
||||||
import {getMe} from '../../store/users'
|
import {getMe} from '../../store/users'
|
||||||
import {BoardTypeOpen, BoardTypePrivate} from '../../blocks/board'
|
import {BoardTypeOpen, BoardTypePrivate} from '../../blocks/board'
|
||||||
|
|
||||||
|
@ -38,15 +38,18 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => {
|
||||||
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string}>()
|
const match = useRouteMatch<{boardId: string, viewId: string, cardId?: string}>()
|
||||||
const history = useHistory()
|
const history = useHistory()
|
||||||
|
|
||||||
const selectBoard = async (boardId: string): Promise<void> => {
|
const selectBoard = async (teamId: string, boardId: string): Promise<void> => {
|
||||||
if (!me) {
|
if (!me) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const newPath = generatePath(match.path, {...match.params, boardId, viewId: undefined})
|
const newPath = generatePath(match.path, {...match.params, teamId, boardId, viewId: undefined})
|
||||||
history.push(newPath)
|
history.push(newPath)
|
||||||
props.onClose()
|
props.onClose()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const teamsById:Record<string, Team> = {}
|
||||||
|
useAppSelector(getAllTeams).forEach((t) => teamsById[t.id] = t)
|
||||||
|
|
||||||
const searchHandler = async (query: string): Promise<Array<ReactNode>> => {
|
const searchHandler = async (query: string): Promise<Array<ReactNode>> => {
|
||||||
if (query.trim().length === 0 || !team) {
|
if (query.trim().length === 0 || !team) {
|
||||||
return []
|
return []
|
||||||
|
@ -54,17 +57,22 @@ const BoardSwitcherDialog = (props: Props): JSX.Element => {
|
||||||
|
|
||||||
const items = await octoClient.search(team.id, query)
|
const items = await octoClient.search(team.id, query)
|
||||||
const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled Board'})
|
const untitledBoardTitle = intl.formatMessage({id: 'ViewTitle.untitled-board', defaultMessage: 'Untitled Board'})
|
||||||
return items.map((item) => (
|
return items.map((item) => {
|
||||||
<div
|
const resultTitle = item.title || untitledBoardTitle
|
||||||
key={item.id}
|
const teamTitle = teamsById[item.teamId].title
|
||||||
className='blockSearchResult'
|
return (
|
||||||
onClick={() => selectBoard(item.id)}
|
<div
|
||||||
>
|
key={item.id}
|
||||||
{item.type === BoardTypeOpen && <Globe/>}
|
className='blockSearchResult'
|
||||||
{item.type === BoardTypePrivate && <LockOutline/>}
|
onClick={() => selectBoard(item.teamId, item.id)}
|
||||||
<span>{item.title || untitledBoardTitle}</span>
|
>
|
||||||
</div>
|
{item.type === BoardTypeOpen && <Globe/>}
|
||||||
))
|
{item.type === BoardTypePrivate && <LockOutline/>}
|
||||||
|
<span className='resultTitle'>{resultTitle}</span>
|
||||||
|
<span className='teamTitle'>{teamTitle}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -82,3 +82,4 @@ export const {reducer} = teamSlice
|
||||||
|
|
||||||
export const getCurrentTeam = (state: RootState): Team|null => state.teams.current
|
export const getCurrentTeam = (state: RootState): Team|null => state.teams.current
|
||||||
export const getFirstTeam = (state: RootState): Team|null => state.teams.allTeams[0]
|
export const getFirstTeam = (state: RootState): Team|null => state.teams.allTeams[0]
|
||||||
|
export const getAllTeams = (state: RootState): Array<Team> => state.teams.allTeams
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {Category, CategoryBoards} from '../store/sidebar'
|
||||||
import {Utils} from '../utils'
|
import {Utils} from '../utils'
|
||||||
import {CheckboxBlock, createCheckboxBlock} from '../blocks/checkboxBlock'
|
import {CheckboxBlock, createCheckboxBlock} from '../blocks/checkboxBlock'
|
||||||
import {Block} from '../blocks/block'
|
import {Block} from '../blocks/block'
|
||||||
|
import {IUser} from "../user"
|
||||||
|
|
||||||
class TestBlockFactory {
|
class TestBlockFactory {
|
||||||
static createBoard(): Board {
|
static createBoard(): Board {
|
||||||
|
@ -181,6 +182,18 @@ class TestBlockFactory {
|
||||||
boardIDs: [],
|
boardIDs: [],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static createUser(): IUser {
|
||||||
|
return {
|
||||||
|
id: 'user-id-1',
|
||||||
|
username: 'Dwight Schrute',
|
||||||
|
email: 'dwight.schrute@dundermifflin.com',
|
||||||
|
props: {},
|
||||||
|
create_at: Date.now(),
|
||||||
|
update_at: Date.now(),
|
||||||
|
is_bot: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {TestBlockFactory}
|
export {TestBlockFactory}
|
||||||
|
|
Loading…
Reference in a new issue