1
0
mirror of https://github.com/mattermost/focalboard.git synced 2024-11-24 08:22:29 +02:00

Merge branch 'main' into MM-47238_boards-dev-server-url

This commit is contained in:
Harrison Healey 2022-10-20 11:00:48 -04:00
commit 1d5cf123c9
26 changed files with 183 additions and 73 deletions

View File

@ -22,6 +22,7 @@ jobs:
db:
- sqlite
- mysql
- mariadb
- postgres
steps:

View File

@ -126,7 +126,7 @@ watch-single-user: modd-precheck ## Run both server and webapp in single user mo
watch-server-test: modd-precheck ## Run server tests watching for changes
env FOCALBOARD_BUILD_TAGS='$(BUILD_TAGS)' modd -f modd-servertest.conf
server-test: server-test-sqlite server-test-mysql server-test-postgres ## Run server tests
server-test: server-test-sqlite server-test-mysql server-test-mariadb server-test-postgres ## Run server tests
server-test-sqlite: export FOCALBOARD_UNIT_TESTING=1
@ -153,6 +153,20 @@ server-test-mysql: setup-go-work ## Run server tests using mysql
cd mattermost-plugin/server; go tool cover -func plugin-mysql-profile.coverage
docker-compose -f ./docker-testing/docker-compose-mysql.yml down -v --remove-orphans
server-test-mariadb: export FOCALBOARD_UNIT_TESTING=1
server-test-mariadb: export FOCALBOARD_STORE_TEST_DB_TYPE=mariadb
server-test-mariadb: export FOCALBOARD_STORE_TEST_DOCKER_PORT=44445
server-test-mariadb: templates-archive ## Run server tests using mysql
@echo Starting docker container for mariadb
docker-compose -f ./docker-testing/docker-compose-mariadb.yml down -v --remove-orphans
docker-compose -f ./docker-testing/docker-compose-mariadb.yml run start_dependencies
cd server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=server-mariadb-profile.coverage -count=1 -timeout=30m ./...
cd server; go tool cover -func server-mariadb-profile.coverage
cd mattermost-plugin/server; go test -tags '$(BUILD_TAGS)' -race -v -coverpkg=./... -coverprofile=plugin-mariadb-profile.coverage -count=1 -timeout=30m ./...
cd mattermost-plugin/server; go tool cover -func plugin-mariadb-profile.coverage
docker-compose -f ./docker-testing/docker-compose-mariadb.yml down -v --remove-orphans
server-test-postgres: export FOCALBOARD_UNIT_TESTING=1
server-test-postgres: export FOCALBOARD_STORE_TEST_DB_TYPE=postgres
server-test-postgres: export FOCALBOARD_STORE_TEST_DOCKER_PORT=44446

View File

@ -0,0 +1,24 @@
version: '2.4'
services:
mariadb:
image: "mariadb:10.9.3"
restart: always
environment:
MARIADB_ROOT_HOST: "%"
MARIADB_ROOT_PASSWORD: mostest
MARIADB_PASSWORD: mostest
MARIADB_USER: mmuser
healthcheck:
test: ["CMD", "mariadb-admin", "ping", "-h", "localhost", "-u", "mmuser", "-pmostest"]
interval: 5s
timeout: 10s
retries: 3
tmpfs: /var/lib/mariadb
ports:
- 44445:3306
start_dependencies:
image: mattermost/mattermost-wait-for-dep:latest
depends_on:
- mariadb
command: mariadb:3306

View File

@ -5,8 +5,8 @@ This node app converts an Asana json archive into a Focalboard archive. To use:
2. Save it locally, e.g. to `asana.json`
3. Run `npm install` from within `focalboard/webapp`
4. Run `npm install` from within `focalboard/import/asana`
5. Run `npx ts-node importAsana.ts -i <asana.json> -o archive.focalboard`
6. In Focalboard, click `Settings`, then `Import archive` and select `archive.focalboard`
5. Run `npx ts-node importAsana.ts -i <asana.json> -o archive.boardarchive`
6. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`
## Import scope

View File

@ -34,7 +34,7 @@ function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFile = args['i']
const outputFile = args['o'] || 'archive.focalboard'
const outputFile = args['o'] || 'archive.boardarchive'
if (!inputFile) {
showHelp()
@ -184,7 +184,7 @@ function convert(input: Asana): [Board[], Block[]] {
}
function showHelp() {
console.log('import -i <input.json> -o [output.focalboard]')
console.log('import -i <input.json> -o [output.boardarchive]')
exit(1)
}

View File

@ -6,8 +6,8 @@ This node app converts a Jira xml export into a Focalboard archive. To use:
3. Save it locally, e.g. to `jira_export.xml`
4. Run `npm install` from within `focalboard/webapp`
5. Run `npm install` from within `focalboard/import/jira`
6. Run `npx ts-node importJira.ts -i <path-to-jira.xml> -o archive.focalboard` (also from within `focalboard/import/jira`)
7. In Focalboard, click `Settings`, then `Import archive` and select `archive.focalboard`
6. Run `npx ts-node importJira.ts -i <path-to-jira.xml> -o archive.boardarchive` (also from within `focalboard/import/jira`)
7. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`
## Import scope and known limitations

View File

@ -8,7 +8,7 @@ async function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFile = args['i']
const outputFile = args['o'] || 'archive.focalboard'
const outputFile = args['o'] || 'archive.boardarchive'
return run(inputFile, outputFile)
}

View File

@ -237,7 +237,7 @@ function optionForPropertyValue(cardProperty: IPropertyTemplate, propertyValue:
}
function showHelp() {
console.log('import -i <input.xml> -o [output.focalboard]')
console.log('import -i <input.xml> -o [output.boardarchive]')
exit(1)
}

View File

@ -4,10 +4,10 @@ This node app converts data from a Nextcloud Server with the [app Deck](https://
1. Run `npm install` from within `focalboard/webapp`
2. Run `npm install` from within `focalboard/import/nextcloud-deck`
3. Run `npx ts-node importDeck.ts -o archive.focalboard` (also from within `focalboard/import/nextcloud-deck`)
3. Run `npx ts-node importDeck.ts -o archive.boardarchive` (also from within `focalboard/import/nextcloud-deck`)
1. Enter URL and credentials (can also be provided via cli arguments)
2. Enter ID of the board to convert
4. In Focalboard, click `Settings`, then `Import archive` and select `archive.focalboard`
4. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`
## Import scope

View File

@ -47,7 +47,7 @@ async function main() {
const password = args['p'] ?? readline.question('Password: ', {hideEchoBack: true})
const boardIdString = args['b']
const outputFile = args['o'] || 'archive.focalboard'
const outputFile = args['o'] || 'archive.boardarchive'
// Create Client
const deckClient = new NextcloudDeckClient({auth: {username, password}, url})

View File

@ -6,8 +6,8 @@ This node app converts a Notion CSV and markdown export into a Focalboard archiv
3. Save it locally, and unzip the folder e.g. to `notion-export`
4. Run `npm install` from within `focalboard/webapp`
5. Run `npm install` from within `focalboard/import/notion`
6. Run `npx ts-node importNotion.ts -i <path to the notion-export folder> -o archive.focalboard`
7. In Focalboard, click `Settings`, then `Import archive` and select `archive.focalboard`
6. Run `npx ts-node importNotion.ts -i <path to the notion-export folder> -o archive.boardarchive`
7. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`
## Import scope

View File

@ -35,7 +35,7 @@ async function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFolder = args['i']
const outputFile = args['o'] || 'archive.focalboard'
const outputFile = args['o'] || 'archive.boardarchive'
if (!inputFolder) {
showHelp()
@ -217,7 +217,7 @@ function convert(input: any[], title: string): [Board[], Block[]] {
}
function showHelp() {
console.log('import -i <input.json> -o [output.focalboard]')
console.log('import -i <input.json> -o [output.boardarchive]')
exit(1)
}

View File

@ -8,5 +8,5 @@ This node app converts a Todoist json archive into a Focalboard archive. To use:
1. Note the name and location of the downloaded *json* file.
3. Run `npm install` from within `focalboard/webapp`
4. Run `npm install` from within `focalboard/import/todoist`
5. Run `npx ts-node importTodoist.ts -i <path-to-todoist.json> -o archive.focalboard` (also from within `focalboard/import/todoist`)
6. In Focalboard, click `Settings`, then `Import archive` and select `archive.focalboard`
5. Run `npx ts-node importTodoist.ts -i <path-to-todoist.json> -o archive.boardarchive` (also from within `focalboard/import/todoist`)
6. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`

View File

@ -42,7 +42,7 @@ function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFile = args['i']
const outputFile = args['o'] || 'archive.focalboard'
const outputFile = args['o'] || 'archive.boardarchive'
if (!inputFile) {
showHelp()

View File

@ -6,8 +6,8 @@ This node app converts a Trello json archive into a Focalboard archive. To use:
3. Save it locally, e.g. to `trello.json`
4. Run `npm install` from within `focalboard/webapp`
5. Run `npm install` from within `focalboard/import/trello`
6. Run `npx ts-node importTrello.ts -i <path-to-trello.json> -o archive.focalboard` (also from within `focalboard/import/trello`)
7. In Focalboard, click `Settings`, then `Import archive` and select `archive.focalboard`
6. Run `npx ts-node importTrello.ts -i <path-to-trello.json> -o archive.boardarchive` (also from within `focalboard/import/trello`)
7. In Focalboard, click `Settings`, then `Import archive` and select `archive.boardarchive`
## Import scope

View File

@ -35,7 +35,7 @@ function main() {
const args: minimist.ParsedArgs = minimist(process.argv.slice(2))
const inputFile = args['i']
const outputFile = args['o'] || 'archive.focalboard'
const outputFile = args['o'] || 'archive.boardarchive'
if (!inputFile) {
showHelp()
@ -169,7 +169,7 @@ function convert(input: Trello): [Board[], Block[]] {
}
function showHelp() {
console.log('import -i <input.json> -o [output.focalboard]')
console.log('import -i <input.json> -o [output.boardarchive]')
exit(1)
}

View File

@ -13,15 +13,15 @@ ALTER TABLE {{.prefix}}blocks_history ADD COLUMN board_id VARCHAR(36);
{{- /* cleanup incorrect data format in column calculations */ -}}
{{- /* then move from 'board' type to 'view' type*/ -}}
{{if .mysql}}
UPDATE {{.prefix}}blocks SET fields = JSON_SET(fields, '$.columnCalculations', cast('{}' as json)) WHERE fields->'$.columnCalculations' = cast('[]' as json);
UPDATE {{.prefix}}blocks SET fields = JSON_SET(fields, '$.columnCalculations', JSON_OBJECT()) WHERE JSON_EXTRACT(fields, '$.columnCalculations') = JSON_ARRAY();
UPDATE {{.prefix}}blocks b
JOIN (
SELECT id, fields->'$.columnCalculations' as board_calculations from {{.prefix}}blocks
WHERE fields -> '$.columnCalculations' <> cast('{}' as json)
SELECT id, JSON_EXTRACT(fields, '$.columnCalculations') as board_calculations from {{.prefix}}blocks
WHERE JSON_EXTRACT(fields, '$.columnCalculations') <> JSON_OBJECT()
) AS s on s.id = b.root_id
SET fields = JSON_SET(fields, '$.columnCalculations', cast(s.board_calculations as json))
WHERE b.fields->'$.viewType' = 'table'
SET fields = JSON_SET(fields, '$.columnCalculations', JSON_ARRAY(s.board_calculations))
WHERE JSON_EXTRACT(b.fields, '$.viewType') = 'table'
AND b.type = 'view';
{{end}}
{{if .postgres}}
@ -166,10 +166,10 @@ CREATE TABLE IF NOT EXISTS {{.prefix}}boards_history (
COALESCE(B.title, ''),
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),
JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.icon')),
COALESCE(B.fields->'$.showDescription', 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',
COALESCE(B.fields->'$.templateVer', 0),
'{}', B.fields->'$.cardProperties', B.create_at,
COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),
'{}', JSON_EXTRACT(B.fields, '$.cardProperties'), B.create_at,
B.update_at, B.delete_at
FROM {{.prefix}}blocks AS B
INNER JOIN Channels AS C ON C.Id=B.channel_id
@ -180,10 +180,10 @@ CREATE TABLE IF NOT EXISTS {{.prefix}}boards_history (
COALESCE(B.title, ''),
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),
JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.icon')),
COALESCE(B.fields->'$.showDescription', 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',
COALESCE(B.fields->'$.templateVer', 0),
'{}', B.fields->'$.cardProperties', B.create_at,
COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),
'{}', JSON_EXTRACT(B.fields, '$.cardProperties'), B.create_at,
B.update_at, B.delete_at
FROM {{.prefix}}blocks_history AS B
INNER JOIN Channels AS C ON C.Id=B.channel_id
@ -225,10 +225,10 @@ CREATE TABLE IF NOT EXISTS {{.prefix}}boards_history (
COALESCE(B.title, ''),
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),
JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')),
COALESCE(B.fields->'$.showDescription', 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',
COALESCE(B.fields->'$.templateVer', 0),
'{}', fields->'$.cardProperties', create_at,
COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),
'{}', JSON_EXTRACT(fields, '$.cardProperties'), create_at,
update_at, delete_at
FROM {{.prefix}}blocks AS B
WHERE type='board'
@ -238,10 +238,10 @@ CREATE TABLE IF NOT EXISTS {{.prefix}}boards_history (
COALESCE(B.title, ''),
COALESCE(JSON_UNQUOTE(JSON_EXTRACT(B.fields,'$.description')), ''),
JSON_UNQUOTE(JSON_EXTRACT(fields,'$.icon')),
COALESCE(B.fields->'$.showDescription', 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.showDescription'), 'false') = 'true',
COALESCE(JSON_EXTRACT(B.fields, '$.isTemplate'), 'false') = 'true',
COALESCE(B.fields->'$.templateVer', 0),
'{}', fields->'$.cardProperties', create_at,
COALESCE(JSON_EXTRACT(B.fields, '$.templateVer'), 0),
'{}', JSON_EXTRACT(fields, '$.cardProperties'), create_at,
update_at, delete_at
FROM {{.prefix}}blocks_history AS B
WHERE type='board'

View File

@ -1,7 +1,10 @@
INSERT INTO Channels (Id, CreateAt, UpdateAt, DeleteAt, TeamId, Type, Name, CreatorId) VALUES ('chan-id', 123, 123, 0, 'team-id', 'O', 'channel', 'user-id');
INSERT INTO focalboard_blocks
(id, workspace_id, root_id, parent_id, created_by, modified_by, type, title, create_at, update_at, delete_at)
(id, workspace_id, root_id, parent_id, created_by, modified_by, type, title, create_at, update_at, delete_at, fields)
VALUES
('board-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'board', 'My Board', 123, 123, 0),
('card-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'card', 'A card', 123, 123, 0);
('board-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'board', 'My Board', 123, 123, 0, '{"columnCalculations": {"__title":"countUniqueValue"}}'),
('card-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'card', 'A card', 123, 123, 0, '{}'),
('view-id', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'view', 'A view', 123, 123, 0, '{"viewType":"table"}'),
('view-id2', 'chan-id', 'board-id', 'board-id', 'user-id', 'user-id', 'view', 'A view2', 123, 123, 0, '{"viewType":"board"}'),
('board-id2', 'chan-id', 'board-id2', 'board-id2', 'user-id', 'user-id', 'board', 'My Board Two', 123, 123, 0, '{"description": "My Description","showDescription":true,"isTemplate":true,"templateVer":1,"columnCalculations":[]}');

View File

@ -1,6 +1,7 @@
package migrationstests
import (
"encoding/json"
"testing"
"github.com/stretchr/testify/require"
@ -18,19 +19,36 @@ func Test18AddTeamsAndBoardsSQLMigration(t *testing.T) {
ID string
Title string
Type string
Fields string
Description string
Show_Description bool
Is_Template bool
Template_Version int
}{}
// we check first that the board is inside the blocks table as
// a block of board type
err := th.f.DB().Get(&board, "SELECT id, title, type FROM focalboard_blocks WHERE id = 'board-id'")
// a block of board type and columnCalculations exists if Fields
err := th.f.DB().Get(&board, "SELECT id, title, type, fields FROM focalboard_blocks WHERE id = 'board-id'")
require.NoError(t, err)
require.Equal(t, "My Board", board.Title)
require.Equal(t, "board", board.Type)
require.Contains(t, board.Fields, "columnCalculations")
// we check another board is inside the blocks table as
// a block of board type and has several different properties in boards
err = th.f.DB().Get(&board, "SELECT id, title, type, fields FROM focalboard_blocks WHERE id = 'board-id2'")
require.NoError(t, err)
require.Equal(t, "My Board Two", board.Title)
require.Equal(t, "board", board.Type)
require.Contains(t, board.Fields, "description")
require.Contains(t, board.Fields, "showDescription")
require.Contains(t, board.Fields, "isTemplate")
require.Contains(t, board.Fields, "templateVer")
// then we run the migration
th.f.MigrateToStep(18)
// we assert that the board is now a block
// we assert that the board is now in the boards table
bErr := th.f.DB().Get(&board, "SELECT id, title, type FROM focalboard_boards WHERE id = 'board-id'")
require.NoError(t, bErr)
require.Equal(t, "My Board", board.Title)
@ -43,12 +61,46 @@ func Test18AddTeamsAndBoardsSQLMigration(t *testing.T) {
Board_ID string
}{}
// we fetch the card to ensure that the
// we fetch the card to ensure that the card is still in the blocks table
cErr := th.f.DB().Get(&card, "SELECT title, type, parent_id, board_id FROM focalboard_blocks WHERE id = 'card-id'")
require.NoError(t, cErr)
require.Equal(t, "A card", card.Title)
require.Equal(t, "card", card.Type)
require.Equal(t, board.ID, card.Parent_ID)
require.Equal(t, board.ID, card.Board_ID)
// we assert that the board is now a board and properties from JSON Fields
dErr := th.f.DB().Get(&board, "SELECT id, title, type, description, show_description, is_template, template_version FROM focalboard_boards WHERE id = 'board-id2'")
require.NoError(t, dErr)
require.Equal(t, "My Board Two", board.Title)
require.Equal(t, "O", board.Type)
require.Equal(t, "My Description", board.Description)
require.Equal(t, true, board.Show_Description)
require.Equal(t, true, board.Is_Template)
require.Equal(t, 1, board.Template_Version)
view := struct {
Title string
Type string
Parent_ID string
Board_ID string
Fields string
}{}
// we fetch the views to ensure that the calculation columns exist on views
eErr := th.f.DB().Get(&view, "SELECT title, type, parent_id, board_id, fields FROM focalboard_blocks WHERE id = 'view-id'")
require.NoError(t, eErr)
require.Contains(t, view.Fields, "columnCalculations")
var fields map[string]interface{}
// Make sure a valid JSON object
json.Unmarshal([]byte(view.Fields), &fields)
require.NotNil(t, fields["columnCalculations"])
require.NotEmpty(t, fields["columnCalculations"])
// Board View should not have columnCalculations
fErr := th.f.DB().Get(&view, "SELECT title, type, parent_id, board_id, fields FROM focalboard_blocks WHERE id = 'view-id2'")
require.NoError(t, fErr)
require.NotContains(t, view.Fields, "columnCalculations")
})
}

View File

@ -2,7 +2,6 @@ package sqlstore
import (
"database/sql"
"errors"
"fmt"
"net/url"
"strings"
@ -17,9 +16,6 @@ import (
"github.com/mattermost/mattermost-server/v6/shared/mlog"
)
//nolint:lll
var ErrInvalidMariaDB = errors.New("MariaDB database is not supported, you can find more information at https://docs.mattermost.com/install/software-hardware-requirements.html#database-software")
// SQLStore is a SQL database.
type SQLStore struct {
db *sql.DB
@ -58,10 +54,6 @@ func New(params Params) (*SQLStore, error) {
servicesAPI: params.ServicesAPI,
}
if store.IsMariaDB() {
return nil, ErrInvalidMariaDB
}
var err error
store.isBinaryParam, err = store.computeBinaryParam()
if err != nil {

View File

@ -42,6 +42,9 @@ func PrepareNewTestDatabase() (dbType string, connectionString string, err error
if dbType == "" {
dbType = model.SqliteDBType
}
if dbType == "mariadb" {
dbType = model.MysqlDBType
}
var dbName string
var rootUser string

View File

@ -12,6 +12,7 @@ import IconButton from '../../widgets/buttons/iconButton'
import OptionsIcon from '../../widgets/icons/options'
import Menu from '../../widgets/menu'
import MenuWrapper from '../../widgets/menuWrapper'
import {Utils} from '../../utils'
import ModalWrapper from '../modalWrapper'
import {sendFlashMessage} from '../flashMessages'
@ -84,6 +85,7 @@ function onExportCsvTrigger(board: Board, activeView: BoardView, cards: Card[],
})
sendFlashMessage({content: exportCompleteMessage, severity: 'normal'})
} catch (e) {
Utils.logError(`ExportCSV ERROR: ${e}`)
const exportFailedMessage = intl.formatMessage({
id: 'ViewHeader.export-failed',
defaultMessage: 'Export failed!',

View File

@ -39,7 +39,7 @@ class Constants {
static readonly versionString = '7.5.0'
static readonly versionDisplayString = 'Nov 2022'
static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/data-and-archives.html'
static readonly archiveHelpPage = 'https://docs.mattermost.com/boards/migrate-to-boards.html'
static readonly imports = [
{
id: 'trello',

View File

@ -141,11 +141,15 @@ const BoardPage = (props: Props): JSX.Element => {
}
}
const dispatchLoadAction = () => {
dispatch(loadAction(match.params.boardId))
}
Utils.log('useWEbsocket adding onChange handler')
wsClient.addOnChange(incrementalBlockUpdate, 'block')
wsClient.addOnChange(incrementalBoardUpdate, 'board')
wsClient.addOnChange(incrementalBoardMemberUpdate, 'boardMembers')
wsClient.addOnReconnect(() => dispatch(loadAction(match.params.boardId)))
wsClient.addOnReconnect(dispatchLoadAction)
wsClient.setOnFollowBlock((_: WSClient, subscription: Subscription): void => {
if (subscription.subscriberId === me?.id) {
@ -163,7 +167,7 @@ const BoardPage = (props: Props): JSX.Element => {
wsClient.removeOnChange(incrementalBlockUpdate, 'block')
wsClient.removeOnChange(incrementalBoardUpdate, 'board')
wsClient.removeOnChange(incrementalBoardMemberUpdate, 'boardMembers')
wsClient.removeOnReconnect(() => dispatch(loadAction(match.params.boardId)))
wsClient.removeOnReconnect(dispatchLoadAction)
}
}, [me?.id, activeBoardId])

View File

@ -58,7 +58,12 @@ export abstract class PropertyType {
exportValue = (value: string | string[] | undefined, card: Card, template: IPropertyTemplate, intl: IntlShape): string => {
const displayValue = this.displayValue(value, card, template, intl)
return `"${encodeText(displayValue as string)}"`
if (typeof displayValue === 'string') {
return `"${encodeText(displayValue)}"`
} else if (Array.isArray(displayValue)) {
return `"${encodeText((displayValue as string[]).join('|'))}"`
}
return ''
}
valueClassName = (readonly: boolean): string => {

View File

@ -20,7 +20,13 @@ import {RootState} from './index'
export const fetchMe = createAsyncThunk(
'users/fetchMe',
async () => client.getMe(),
async () => {
const [me, myConfig] = await Promise.all([
client.getMe(),
client.getMyConfig(),
])
return {me, myConfig}
},
)
export const versionProperty = 'version72MessageCanceled'
@ -84,12 +90,16 @@ const usersSlice = createSlice({
},
extraReducers: (builder) => {
builder.addCase(fetchMe.fulfilled, (state, action) => {
state.me = action.payload || null
state.me = action.payload.me || null
state.loggedIn = Boolean(state.me)
if (action.payload.myConfig) {
state.myConfig = parseUserProps(action.payload.myConfig)
}
})
builder.addCase(fetchMe.rejected, (state) => {
state.me = null
state.loggedIn = false
state.myConfig = {}
})
// TODO: change this when the initial load is complete