diff --git a/server/api/api.go b/server/api/api.go index 40c0bac0d..10bc75d5b 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -87,14 +87,9 @@ func (a *API) RegisterRoutes(r *mux.Router) { apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}", a.sessionRequired(a.handlePatchBlock)).Methods("PATCH") apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/undelete", a.sessionRequired(a.handleUndeleteBlock)).Methods("POST") - apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/subtree", a.attachSession(a.handleGetSubTree, false)).Methods("GET") apiv1.HandleFunc("/boards/{boardID}/blocks/{blockID}/duplicate", a.attachSession(a.handleDuplicateBlock, false)).Methods("POST") apiv1.HandleFunc("/boards/{boardID}/metadata", a.sessionRequired(a.handleGetBoardMetadata)).Methods("GET") - // Import&Export APIs - apiv1.HandleFunc("/boards/{boardID}/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") - apiv1.HandleFunc("/boards/{boardID}/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") - // Member APIs apiv1.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleGetMembersForBoard)).Methods("GET") apiv1.HandleFunc("/boards/{boardID}/members", a.sessionRequired(a.handleAddMember)).Methods("POST") @@ -363,23 +358,6 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { auditRec.Success() } -func stampModificationMetadata(r *http.Request, blocks []model.Block, auditRec *audit.Record) { - userID := getUserID(r) - if userID == model.SingleUser { - userID = "" - } - - now := utils.GetMillis() - for i := range blocks { - blocks[i].ModifiedBy = userID - blocks[i].UpdateAt = now - - if auditRec != nil { - auditRec.AddMeta("block_"+strconv.FormatInt(int64(i), 10), blocks[i]) - } - } -} - func (a *API) handleCreateCategory(w http.ResponseWriter, r *http.Request) { requestBody, err := ioutil.ReadAll(r.Body) if err != nil { @@ -1219,287 +1197,6 @@ func (a *API) handlePatchBlocks(w http.ResponseWriter, r *http.Request) { auditRec.Success() } -func (a *API) handleGetSubTree(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /api/v1/boards/{boardID}/blocks/{blockID}/subtree getSubTree - // - // Returns the blocks of a subtree - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: blockID - // in: path - // description: The ID of the root block of the subtree - // required: true - // type: string - // - name: l - // in: query - // description: The number of levels to return. 2 or 3. Defaults to 2. - // required: false - // type: integer - // minimum: 2 - // maximum: 3 - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - blockID := vars["blockID"] - - if !a.hasValidReadTokenForBoard(r, boardID) && !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - - query := r.URL.Query() - levels, err := strconv.ParseInt(query.Get("l"), 10, 32) - if err != nil { - levels = 2 - } - - if levels != 2 && levels != 3 { - a.logger.Error("Invalid levels", mlog.Int64("levels", levels)) - a.errorResponse(w, r.URL.Path, http.StatusBadRequest, "invalid levels", nil) - return - } - - auditRec := a.makeAuditRecord(r, "getSubTree", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("blockID", blockID) - - blocks, err := a.app.GetSubTree(boardID, blockID, int(levels), model.QuerySubtreeOptions{}) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("GetSubTree", - mlog.Int64("levels", levels), - mlog.String("boardID", boardID), - mlog.String("blockID", blockID), - mlog.Int("block_count", len(blocks)), - ) - json, err := json.Marshal(blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - - auditRec.AddMeta("blockCount", len(blocks)) - auditRec.Success() -} - -func (a *API) handleExport(w http.ResponseWriter, r *http.Request) { - // swagger:operation GET /api/v1/boards/{boardID}/blocks/export exportBlocks - // - // Returns all blocks of a board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - - query := r.URL.Query() - rootID := query.Get("root_id") - - auditRec := a.makeAuditRecord(r, "export", audit.Fail) - defer a.audit.LogRecord(audit.LevelRead, auditRec) - auditRec.AddMeta("boardID", boardID) - auditRec.AddMeta("rootID", rootID) - - var blocks []model.Block - var err error - if rootID == "" { - blocks, err = a.app.GetBlocksForBoard(boardID) - } else { - blocks, err = a.app.GetBlocksWithBoardID(boardID) - } - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - a.logger.Debug("raw blocks", mlog.Int("block_count", len(blocks))) - auditRec.AddMeta("rawCount", len(blocks)) - - blocks = filterOrphanBlocks(blocks) - - a.logger.Debug("EXPORT filtered blocks", mlog.Int("block_count", len(blocks))) - auditRec.AddMeta("filteredCount", len(blocks)) - - json, err := json.Marshal(blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonBytesResponse(w, http.StatusOK, json) - - auditRec.Success() -} - -func filterOrphanBlocks(blocks []model.Block) (ret []model.Block) { - queue := make([]model.Block, 0) - childrenOfBlockWithID := make(map[string]*[]model.Block) - - // Build the trees from nodes - for _, block := range blocks { - if len(block.ParentID) == 0 { - // Queue root blocks to process first - queue = append(queue, block) - } else { - siblings := childrenOfBlockWithID[block.ParentID] - if siblings != nil { - *siblings = append(*siblings, block) - } else { - siblings := []model.Block{block} - childrenOfBlockWithID[block.ParentID] = &siblings - } - } - } - - // Map the trees to an array, which skips orphaned nodes - blocks = make([]model.Block, 0) - for len(queue) > 0 { - block := queue[0] - queue = queue[1:] // dequeue - blocks = append(blocks, block) - children := childrenOfBlockWithID[block.ID] - if children != nil { - queue = append(queue, *children...) - } - } - - return blocks -} - -func (a *API) handleImport(w http.ResponseWriter, r *http.Request) { - // swagger:operation POST /api/v1/boards/{boardID}/blocks/import importBlocks - // - // Import blocks on a given board - // - // --- - // produces: - // - application/json - // parameters: - // - name: boardID - // in: path - // description: Board ID - // required: true - // type: string - // - name: Body - // in: body - // description: array of blocks to import - // required: true - // schema: - // type: array - // items: - // "$ref": "#/definitions/Block" - // security: - // - BearerAuth: [] - // responses: - // '200': - // description: success - // default: - // description: internal error - // schema: - // "$ref": "#/definitions/ErrorResponse" - - userID := getUserID(r) - vars := mux.Vars(r) - boardID := vars["boardID"] - - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardCards) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to make board changes"}) - return - } - - requestBody, err := ioutil.ReadAll(r.Body) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - var blocks []model.Block - - err = json.Unmarshal(requestBody, &blocks) - if err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - auditRec := a.makeAuditRecord(r, "import", audit.Fail) - defer a.audit.LogRecord(audit.LevelModify, auditRec) - auditRec.AddMeta("boardID", boardID) - - // all blocks should now be part of the board that they're being - // imported onto - for i := range blocks { - blocks[i].BoardID = boardID - } - - stampModificationMetadata(r, blocks, auditRec) - - if _, err = a.app.InsertBlocks(model.GenerateBlockIDs(blocks, a.logger), userID, false); err != nil { - a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err) - return - } - - jsonStringResponse(w, http.StatusOK, "{}") - - a.logger.Debug("IMPORT BlockIDs", mlog.Int("block_count", len(blocks))) - auditRec.AddMeta("blockCount", len(blocks)) - auditRec.Success() -} - // Sharing func (a *API) handleGetSharing(w http.ResponseWriter, r *http.Request) { @@ -1799,6 +1496,9 @@ func (a *API) handlePostTeamRegenerateSignupToken(w http.ResponseWriter, r *http // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } team, err := a.app.GetRootTeam() if err != nil { @@ -2832,8 +2532,7 @@ func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) { return } - hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) - if userID == "" && !hasValidReadToken { + if userID == "" { a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) return } @@ -2848,17 +2547,16 @@ func (a *API) handleDuplicateBoard(w http.ResponseWriter, r *http.Request) { return } - if !hasValidReadToken { - if board.Type == model.BoardTypePrivate { - if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) - return - } - } else { - if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { - a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to team"}) - return - } + + if board.Type == model.BoardTypePrivate { + if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionViewBoard) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return + } + } else { + if board.TeamID != model.GlobalTeamID && !a.permissions.HasPermissionToTeam(userID, board.TeamID, model.PermissionViewTeam) { + a.errorResponse(w, r.URL.Path, http.StatusForbidden, "", PermissionError{"access denied to board"}) + return } } @@ -2927,8 +2625,7 @@ func (a *API) handleDuplicateBlock(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() asTemplate := query.Get("asTemplate") - hasValidReadToken := a.hasValidReadTokenForBoard(r, boardID) - if userID == "" && !hasValidReadToken { + if userID == "" { a.errorResponse(w, r.URL.Path, http.StatusUnauthorized, "", PermissionError{"access denied to board"}) return } diff --git a/server/api/archive.go b/server/api/archive.go index 372da3d09..dd5e25bf3 100644 --- a/server/api/archive.go +++ b/server/api/archive.go @@ -105,6 +105,9 @@ func (a *API) handleArchiveExportTeam(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } vars := mux.Vars(r) teamID := vars["teamID"] diff --git a/server/api/auth.go b/server/api/auth.go index b5fa26789..5b60584f6 100644 --- a/server/api/auth.go +++ b/server/api/auth.go @@ -166,6 +166,9 @@ func (a *API) handleLogin(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode @@ -228,6 +231,9 @@ func (a *API) handleLogout(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode @@ -278,6 +284,9 @@ func (a *API) handleRegister(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode @@ -377,6 +386,9 @@ func (a *API) handleChangePassword(w http.ResponseWriter, r *http.Request) { // description: internal error // schema: // "$ref": "#/definitions/ErrorResponse" + if a.MattermostAuth { + a.errorResponse(w, r.URL.Path, http.StatusNotImplemented, "not permitted in plugin mode", nil) + } if len(a.singleUserToken) > 0 { // Not permitted in single-user mode diff --git a/server/app/blocks.go b/server/app/blocks.go index 3cd480764..32c168aab 100644 --- a/server/app/blocks.go +++ b/server/app/blocks.go @@ -234,15 +234,6 @@ func (a *App) CopyCardFiles(sourceBoardID string, blocks []model.Block) error { return nil } -func (a *App) GetSubTree(boardID, blockID string, levels int, opts model.QuerySubtreeOptions) ([]model.Block, error) { - // Only 2 or 3 levels are supported for now - if levels >= 3 { - return a.store.GetSubTree3(boardID, blockID, opts) - } - - return a.store.GetSubTree2(boardID, blockID, opts) -} - func (a *App) GetBlockByID(blockID string) (*model.Block, error) { return a.store.GetBlock(blockID) } diff --git a/server/client/client.go b/server/client/client.go index 789d8f7e3..2d5547c7f 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -164,10 +164,6 @@ func (c *Client) GetBlockRoute(boardID, blockID string) string { return fmt.Sprintf("%s/%s", c.GetBlocksRoute(boardID), blockID) } -func (c *Client) GetSubtreeRoute(boardID, blockID string) string { - return fmt.Sprintf("%s/subtree", c.GetBlockRoute(boardID, blockID)) -} - func (c *Client) GetBoardsRoute() string { return "/boards" } @@ -297,16 +293,6 @@ func (c *Client) DeleteBlock(boardID, blockID string) (bool, *Response) { return true, BuildResponse(r) } -func (c *Client) GetSubtree(boardID, blockID string) ([]model.Block, *Response) { - r, err := c.DoAPIGet(c.GetSubtreeRoute(boardID, blockID), "") - if err != nil { - return nil, BuildErrorResponse(r, err) - } - defer closeBody(r) - - return model.BlocksFromJSON(r.Body), BuildResponse(r) -} - // Boards and blocks. func (c *Client) CreateBoardsAndBlocks(bab *model.BoardsAndBlocks) (*model.BoardsAndBlocks, *Response) { r, err := c.DoAPIPost(c.GetBoardsAndBlocksRoute(), toJSON(bab)) diff --git a/server/integrationtests/blocks_test.go b/server/integrationtests/blocks_test.go index 573bd1144..00eca9cfd 100644 --- a/server/integrationtests/blocks_test.go +++ b/server/integrationtests/blocks_test.go @@ -429,18 +429,4 @@ func TestGetSubtree(t *testing.T) { } require.Contains(t, blockIDs, parentBlockID) }) - - t.Run("Get subtree for parent ID", func(t *testing.T) { - blocks, resp := th.Client.GetSubtree(board.ID, parentBlockID) - require.NoError(t, resp.Error) - require.Len(t, blocks, 3) - - blockIDs := make([]string, len(blocks)) - for i, b := range blocks { - blockIDs[i] = b.ID - } - require.Contains(t, blockIDs, parentBlockID) - require.Contains(t, blockIDs, childBlockID1) - require.Contains(t, blockIDs, childBlockID2) - }) } diff --git a/webapp/cypress/integration/loginActions.ts b/webapp/cypress/integration/loginActions.ts index 34c05578c..7d5c463ca 100644 --- a/webapp/cypress/integration/loginActions.ts +++ b/webapp/cypress/integration/loginActions.ts @@ -8,10 +8,8 @@ describe('Login actions', () => { it('Can perform login/register actions', () => { // Redirects to login page - cy.log('**Redirects to error then login page**') + cy.log('**Redirects to login page (except plugin mode) **') cy.visit('/') - cy.location('pathname').should('eq', '/error') - cy.get('button').contains('Log in').click() cy.location('pathname').should('eq', '/login') cy.get('.LoginPage').contains('Log in') cy.get('#login-username').should('exist') @@ -40,7 +38,7 @@ describe('Login actions', () => { // User should not be logged in automatically cy.log('**User should not be logged in automatically**') cy.visit('/') - cy.location('pathname').should('eq', '/error') + cy.location('pathname').should('eq', '/login') // Can log in registered user cy.log('**Can log in registered user**') diff --git a/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap b/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap index d1c66a10e..044f8c043 100644 --- a/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap +++ b/webapp/src/components/__snapshots__/centerPanel.test.tsx.snap @@ -695,7 +695,20 @@ exports[`components/centerPanel return centerPanel and press touch 1 with readon
+ > +
+ +
+
+ > +
+ +
+
+ > +
+ +
+
{ e.stopPropagation() }, [selectedCardIds, props.activeView, props.cards, showCard]) + const showShareButton = !props.readonly && me?.id !== 'single-user' + const showShareLoginButton = props.readonly && me?.id !== 'single-user' + const {groupByProperty, activeView, board, views, cards} = props const {visible: visibleGroups, hidden: hiddenGroups} = useMemo( () => getVisibleAndHiddenGroups(cards, activeView.fields.visibleOptionIds, activeView.fields.hiddenOptionIds, groupByProperty), @@ -369,13 +373,14 @@ const CenterPanel = (props: Props) => { readonly={props.readonly} />
- {!props.readonly && - ( - - ) + {showShareButton && + + } + {showShareLoginButton && + }
diff --git a/webapp/src/components/shareBoard/__snapshots__/shareBoardLoginButton.test.tsx.snap b/webapp/src/components/shareBoard/__snapshots__/shareBoardLoginButton.test.tsx.snap new file mode 100644 index 000000000..f7995b53d --- /dev/null +++ b/webapp/src/components/shareBoard/__snapshots__/shareBoardLoginButton.test.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`src/components/shareBoard/shareBoardLoginButton should match snapshot 1`] = ` +
+
+ +
+
+`; diff --git a/webapp/src/components/shareBoard/shareBoardLoginButton.scss b/webapp/src/components/shareBoard/shareBoardLoginButton.scss new file mode 100644 index 000000000..b08f71489 --- /dev/null +++ b/webapp/src/components/shareBoard/shareBoardLoginButton.scss @@ -0,0 +1,4 @@ +.ShareBoardLoginButton { + margin-top: 38px; +} + diff --git a/webapp/src/components/shareBoard/shareBoardLoginButton.test.tsx b/webapp/src/components/shareBoard/shareBoardLoginButton.test.tsx new file mode 100644 index 000000000..55a5e2046 --- /dev/null +++ b/webapp/src/components/shareBoard/shareBoardLoginButton.test.tsx @@ -0,0 +1,51 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. +import {render} from '@testing-library/react' +import React from 'react' + +import {TestBlockFactory} from '../../test/testBlockFactory' +import {wrapDNDIntl} from '../../testUtils' + +import ShareBoardLoginButton from './shareBoardLoginButton' +jest.useFakeTimers() + +const boardId = '1' + +const board = TestBlockFactory.createBoard() +board.id = boardId + +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom') + + return { + ...originalModule, + useRouteMatch: jest.fn(() => { + return { + teamId: 'team1', + boardId: 'boardId1', + viewId: 'viewId1', + cardId: 'cardId1', + } + }), + } +}) + +describe('src/components/shareBoard/shareBoardLoginButton', () => { + const savedLocation = window.location + + afterEach(() => { + window.location = savedLocation + }) + + test('should match snapshot', async () => { + // delete window.location + window.location = Object.assign(new URL('https://example.org/mattermost')) + const result = render( + wrapDNDIntl( + , + )) + const renderer = result.container + + expect(renderer).toMatchSnapshot() + }) +}) diff --git a/webapp/src/components/shareBoard/shareBoardLoginButton.tsx b/webapp/src/components/shareBoard/shareBoardLoginButton.tsx new file mode 100644 index 000000000..0b3fbf4c9 --- /dev/null +++ b/webapp/src/components/shareBoard/shareBoardLoginButton.tsx @@ -0,0 +1,51 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {useCallback} from 'react' +import {FormattedMessage} from 'react-intl' +import {generatePath, useRouteMatch, useHistory} from 'react-router-dom' + +import Button from '../../widgets/buttons/button' +import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient' +import {Utils} from '../../utils' + +import './shareBoardLoginButton.scss' + +const ShareBoardLoginButton = () => { + const match = useRouteMatch<{teamId: string, boardId: string, viewId?: string, cardId?: string}>() + const history = useHistory() + + let redirectQueryParam = 'r=' + encodeURIComponent(generatePath('/:boardId?/:viewId?/:cardId?', match.params)) + if (Utils.isFocalboardLegacy()) { + redirectQueryParam = 'redirect_to=' + encodeURIComponent(generatePath('/boards/team/:teamId/:boardId?/:viewId?/:cardId?', match.params)) + } + const loginPath = '/login?' + redirectQueryParam + + const onLoginClick = useCallback(() => { + TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardLogin) + if (Utils.isFocalboardLegacy()) { + location.assign(loginPath) + } else { + history.push(loginPath) + } + + }, []) + + return ( +
+ +
+ ) +} + +export default React.memo(ShareBoardLoginButton) diff --git a/webapp/src/octoClient.test.ts b/webapp/src/octoClient.test.ts index db94d690e..dea8e06a9 100644 --- a/webapp/src/octoClient.test.ts +++ b/webapp/src/octoClient.test.ts @@ -23,10 +23,6 @@ test('OctoClient: get blocks', async () => { let boards = await octoClient.getBlocksWithType('card') expect(boards.length).toBe(blocks.length) - FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify(blocks))) - boards = await octoClient.getSubtree() - expect(boards.length).toBe(blocks.length) - FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify(blocks))) const response = await octoClient.exportArchive() expect(response.status).toBe(200) diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index a32345d30..a12cd3e02 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -201,20 +201,6 @@ class OctoClient { return (await this.getJson(response, {})) as Record } - async getSubtree(boardId?: string, levels = 2, teamID?: string): Promise { - let path = this.teamPath(teamID) + `/blocks/${encodeURIComponent(boardId || '')}/subtree?l=${levels}` - const readToken = Utils.getReadToken() - if (readToken) { - path += `&read_token=${readToken}` - } - const response = await fetch(this.getBaseURL() + path, {headers: this.headers()}) - if (response.status !== 200) { - return [] - } - const blocks = (await this.getJson(response, [])) as Block[] - return this.fixBlocks(blocks) - } - // If no boardID is provided, it will export the entire archive async exportArchive(boardID = ''): Promise { const path = `/api/v1/boards/${boardID}/archive/export` diff --git a/webapp/src/pages/errorPage.tsx b/webapp/src/pages/errorPage.tsx index 2a2063fc6..6fec8e86b 100644 --- a/webapp/src/pages/errorPage.tsx +++ b/webapp/src/pages/errorPage.tsx @@ -10,6 +10,7 @@ import Button from '../widgets/buttons/button' import './errorPage.scss' import {errorDefFromId, ErrorId} from '../errors' +import {Utils} from '../utils' const ErrorPage = () => { const history = useHistory() @@ -45,6 +46,10 @@ const ErrorPage = () => { ) }) + if (!Utils.isFocalboardPlugin() && errid === ErrorId.NotLoggedIn) { + handleButtonClick(errorDef.button1Redirect) + } + return (
diff --git a/webapp/src/pages/loginPage.tsx b/webapp/src/pages/loginPage.tsx index 7f5513d8d..9fc09603e 100644 --- a/webapp/src/pages/loginPage.tsx +++ b/webapp/src/pages/loginPage.tsx @@ -1,7 +1,8 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import React, {useState} from 'react' -import {useHistory, Link} from 'react-router-dom' + +import {Link, useLocation, useHistory} from 'react-router-dom' import {FormattedMessage} from 'react-intl' import {useAppDispatch} from '../store/hooks' @@ -15,14 +16,19 @@ const LoginPage = () => { const [username, setUsername] = useState('') const [password, setPassword] = useState('') const [errorMessage, setErrorMessage] = useState('') - const history = useHistory() const dispatch = useAppDispatch() + const queryParams = new URLSearchParams(useLocation().search) + const history = useHistory() const handleLogin = async (): Promise => { const logged = await client.login(username, password) if (logged) { await dispatch(fetchMe()) - history.push('/') + if (queryParams) { + history.push(queryParams.get('r') || '/') + } else { + history.push('/') + } } else { setErrorMessage('Login failed') } diff --git a/webapp/src/telemetry/telemetryClient.ts b/webapp/src/telemetry/telemetryClient.ts index b1234fd8d..68bc26de5 100644 --- a/webapp/src/telemetry/telemetryClient.ts +++ b/webapp/src/telemetry/telemetryClient.ts @@ -31,6 +31,7 @@ export const TelemetryActions = { AddTemplateFromCard: 'addTemplateFromCard', ViewSharedBoard: 'viewSharedBoard', ShareBoardOpenModal: 'shareBoard_openModal', + ShareBoardLogin: 'shareBoard_login', ShareLinkPublicCopy: 'shareLinkPublic_copy', ShareLinkInternalCopy: 'shareLinkInternal_copy', ImportArchive: 'settings_importArchive', diff --git a/webapp/src/utils.ts b/webapp/src/utils.ts index 3830d5987..3f527778b 100644 --- a/webapp/src/utils.ts +++ b/webapp/src/utils.ts @@ -513,7 +513,7 @@ class Utils { } static getFrontendBaseURL(absolute?: boolean): string { - let frontendBaseURL = window.frontendBaseURL || Utils.getBaseURL(absolute) + let frontendBaseURL = window.frontendBaseURL || Utils.getBaseURL() frontendBaseURL = frontendBaseURL.replace(/\/+$/, '') if (frontendBaseURL.indexOf('/') === 0) { frontendBaseURL = frontendBaseURL.slice(1)