mirror of
https://github.com/mattermost/focalboard.git
synced 2025-01-23 18:34:02 +02:00
Merge branch 'main' into weblate-focalboard-webapp
This commit is contained in:
commit
0f6a9e212c
@ -17,8 +17,10 @@ import CompassIcon from '../../../../webapp/src/widgets/icons/compassIcon'
|
|||||||
|
|
||||||
import {Permission} from '../../../../webapp/src/constants'
|
import {Permission} from '../../../../webapp/src/constants'
|
||||||
|
|
||||||
import './rhsChannelBoardItem.scss'
|
|
||||||
import BoardPermissionGate from '../../../../webapp/src/components/permissions/boardPermissionGate'
|
import BoardPermissionGate from '../../../../webapp/src/components/permissions/boardPermissionGate'
|
||||||
|
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../../../webapp/src/telemetry/telemetryClient'
|
||||||
|
|
||||||
|
import './rhsChannelBoardItem.scss'
|
||||||
|
|
||||||
const windowAny = (window as SuiteWindow)
|
const windowAny = (window as SuiteWindow)
|
||||||
|
|
||||||
@ -36,6 +38,10 @@ const RHSChannelBoardItem = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleBoardClicked = (boardID: string) => {
|
const handleBoardClicked = (boardID: string) => {
|
||||||
|
// send the telemetry information for the clicked board
|
||||||
|
const extraData = {teamID: team.id, board: boardID}
|
||||||
|
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ClickChannelsRHSBoard, extraData)
|
||||||
|
|
||||||
window.open(`${windowAny.frontendBaseURL}/team/${team.id}/${boardID}`, '_blank', 'noopener')
|
window.open(`${windowAny.frontendBaseURL}/team/${team.id}/${boardID}`, '_blank', 'noopener')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,7 +227,7 @@ func jsonStringResponse(w http.ResponseWriter, code int, message string) { //nol
|
|||||||
fmt.Fprint(w, message)
|
fmt.Fprint(w, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) {
|
func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { //nolint:unparam
|
||||||
setResponseHeader(w, "Content-Type", "application/json")
|
setResponseHeader(w, "Content-Type", "application/json")
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
_, _ = w.Write(json)
|
_, _ = w.Write(json)
|
||||||
|
@ -123,37 +123,12 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) {
|
|||||||
auditRec.AddMeta("teamID", board.TeamID)
|
auditRec.AddMeta("teamID", board.TeamID)
|
||||||
auditRec.AddMeta("filename", filename)
|
auditRec.AddMeta("filename", filename)
|
||||||
|
|
||||||
fileInfo, err := a.app.GetFileInfo(filename)
|
fileInfo, fileReader, err := a.app.GetFile(board.TeamID, boardID, filename)
|
||||||
if err != nil && !model.IsErrNotFound(err) {
|
if err != nil && !model.IsErrNotFound(err) {
|
||||||
a.errorResponse(w, r, err)
|
a.errorResponse(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if fileInfo != nil && fileInfo.Archived {
|
|
||||||
fileMetadata := map[string]interface{}{
|
|
||||||
"archived": true,
|
|
||||||
"name": fileInfo.Name,
|
|
||||||
"size": fileInfo.Size,
|
|
||||||
"extension": fileInfo.Extension,
|
|
||||||
}
|
|
||||||
|
|
||||||
data, jsonErr := json.Marshal(fileMetadata)
|
|
||||||
if jsonErr != nil {
|
|
||||||
a.logger.Error("failed to marshal archived file metadata", mlog.String("filename", filename), mlog.Err(jsonErr))
|
|
||||||
a.errorResponse(w, r, jsonErr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBytesResponse(w, http.StatusBadRequest, data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fileReader, err := a.app.GetFileReader(board.TeamID, boardID, filename)
|
|
||||||
if err != nil && !errors.Is(err, app.ErrFileNotFound) {
|
|
||||||
a.errorResponse(w, r, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if errors.Is(err, app.ErrFileNotFound) && board.ChannelID != "" {
|
if errors.Is(err, app.ErrFileNotFound) && board.ChannelID != "" {
|
||||||
// prior to moving from workspaces to teams, the filepath was constructed from
|
// prior to moving from workspaces to teams, the filepath was constructed from
|
||||||
// workspaceID, which is the channel ID in plugin mode.
|
// workspaceID, which is the channel ID in plugin mode.
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/focalboard/server/model"
|
||||||
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/utils"
|
"github.com/mattermost/focalboard/server/utils"
|
||||||
@ -28,7 +29,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
|||||||
|
|
||||||
createdFilename := utils.NewID(utils.IDTypeNone)
|
createdFilename := utils.NewID(utils.IDTypeNone)
|
||||||
fullFilename := fmt.Sprintf(`%s%s`, createdFilename, fileExtension)
|
fullFilename := fmt.Sprintf(`%s%s`, createdFilename, fileExtension)
|
||||||
filePath := filepath.Join(teamID, rootID, fullFilename)
|
filePath := filepath.Join(utils.GetBaseFilePath(), fullFilename)
|
||||||
|
|
||||||
fileSize, appErr := a.filesBackend.WriteFile(reader, filePath)
|
fileSize, appErr := a.filesBackend.WriteFile(reader, filePath)
|
||||||
if appErr != nil {
|
if appErr != nil {
|
||||||
@ -45,7 +46,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
|||||||
CreateAt: now,
|
CreateAt: now,
|
||||||
UpdateAt: now,
|
UpdateAt: now,
|
||||||
DeleteAt: 0,
|
DeleteAt: 0,
|
||||||
Path: emptyString,
|
Path: filePath,
|
||||||
ThumbnailPath: emptyString,
|
ThumbnailPath: emptyString,
|
||||||
PreviewPath: emptyString,
|
PreviewPath: emptyString,
|
||||||
Name: filename,
|
Name: filename,
|
||||||
@ -59,6 +60,7 @@ func (a *App) SaveFile(reader io.Reader, teamID, rootID, filename string) (strin
|
|||||||
Content: "",
|
Content: "",
|
||||||
RemoteId: nil,
|
RemoteId: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
err := a.store.SaveFileInfo(fileInfo)
|
err := a.store.SaveFileInfo(fileInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -77,6 +79,7 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
|
|||||||
// will be the fileinfo id.
|
// will be the fileinfo id.
|
||||||
parts := strings.Split(filename, ".")
|
parts := strings.Split(filename, ".")
|
||||||
fileInfoID := parts[0][1:]
|
fileInfoID := parts[0][1:]
|
||||||
|
|
||||||
fileInfo, err := a.store.GetFileInfo(fileInfoID)
|
fileInfo, err := a.store.GetFileInfo(fileInfoID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -85,6 +88,40 @@ func (a *App) GetFileInfo(filename string) (*mmModel.FileInfo, error) {
|
|||||||
return fileInfo, nil
|
return fileInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetFile(teamID, rootID, fileName string) (*mmModel.FileInfo, filestore.ReadCloseSeeker, error) {
|
||||||
|
fileInfo, err := a.GetFileInfo(fileName)
|
||||||
|
if err != nil && !model.IsErrNotFound(err) {
|
||||||
|
a.logger.Error("111")
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath string
|
||||||
|
|
||||||
|
if fileInfo != nil && fileInfo.Path != "" {
|
||||||
|
filePath = fileInfo.Path
|
||||||
|
} else {
|
||||||
|
filePath = filepath.Join(teamID, rootID, fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := a.filesBackend.FileExists(filePath)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error(fmt.Sprintf("GetFile: Failed to check if file exists as path. Path: %s, error: %e", filePath, err))
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, nil, ErrFileNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := a.filesBackend.Reader(filePath)
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Error(fmt.Sprintf("GetFile: Failed to get file reader of existing file at path: %s, error: %e", filePath, err))
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileInfo, reader, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
|
func (a *App) GetFileReader(teamID, rootID, filename string) (filestore.ReadCloseSeeker, error) {
|
||||||
filePath := filepath.Join(teamID, rootID, filename)
|
filePath := filepath.Join(teamID, rootID, filename)
|
||||||
exists, err := a.filesBackend.FileExists(filePath)
|
exists, err := a.filesBackend.FileExists(filePath)
|
||||||
|
@ -7,6 +7,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/mock/gomock"
|
"github.com/golang/mock/gomock"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -195,8 +196,8 @@ func TestSaveFile(t *testing.T) {
|
|||||||
|
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, string(os.PathSeparator))
|
paths := strings.Split(path, string(os.PathSeparator))
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "boards", paths[0])
|
||||||
assert.Equal(t, testBoardID, paths[1])
|
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||||
fileName = paths[2]
|
fileName = paths[2]
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
@ -219,8 +220,8 @@ func TestSaveFile(t *testing.T) {
|
|||||||
|
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, string(os.PathSeparator))
|
paths := strings.Split(path, string(os.PathSeparator))
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "boards", paths[0])
|
||||||
assert.Equal(t, "test-board-id", paths[1])
|
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
@ -243,8 +244,8 @@ func TestSaveFile(t *testing.T) {
|
|||||||
|
|
||||||
writeFileFunc := func(reader io.Reader, path string) int64 {
|
writeFileFunc := func(reader io.Reader, path string) int64 {
|
||||||
paths := strings.Split(path, string(os.PathSeparator))
|
paths := strings.Split(path, string(os.PathSeparator))
|
||||||
assert.Equal(t, "1", paths[0])
|
assert.Equal(t, "boards", paths[0])
|
||||||
assert.Equal(t, "test-board-id", paths[1])
|
assert.Equal(t, time.Now().Format("20060102"), paths[1])
|
||||||
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
assert.Equal(t, "jpg", strings.Split(paths[2], ".")[1])
|
||||||
return int64(10)
|
return int64(10)
|
||||||
}
|
}
|
||||||
@ -304,3 +305,80 @@ func TestGetFileInfo(t *testing.T) {
|
|||||||
assert.Nil(t, fetchedFileInfo)
|
assert.Nil(t, fetchedFileInfo)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetFile(t *testing.T) {
|
||||||
|
th, _ := SetupTestHelper(t)
|
||||||
|
|
||||||
|
t.Run("when FileInfo exists", func(t *testing.T) {
|
||||||
|
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
|
||||||
|
Id: "fileInfoID",
|
||||||
|
Path: "/path/to/file/fileName.txt",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
|
th.App.filesBackend = mockedFileBackend
|
||||||
|
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
|
||||||
|
readerFunc := func(path string) filestore.ReadCloseSeeker {
|
||||||
|
return mockedReadCloseSeek
|
||||||
|
}
|
||||||
|
|
||||||
|
readerErrorFunc := func(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mockedFileBackend.On("Reader", "/path/to/file/fileName.txt").Return(readerFunc, readerErrorFunc)
|
||||||
|
mockedFileBackend.On("FileExists", "/path/to/file/fileName.txt").Return(true, nil)
|
||||||
|
|
||||||
|
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, fileInfo)
|
||||||
|
assert.NotNil(t, seeker)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when FileInfo doesn't exist", func(t *testing.T) {
|
||||||
|
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(nil, nil)
|
||||||
|
|
||||||
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
|
th.App.filesBackend = mockedFileBackend
|
||||||
|
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
|
||||||
|
readerFunc := func(path string) filestore.ReadCloseSeeker {
|
||||||
|
return mockedReadCloseSeek
|
||||||
|
}
|
||||||
|
|
||||||
|
readerErrorFunc := func(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
|
||||||
|
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
|
||||||
|
|
||||||
|
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Nil(t, fileInfo)
|
||||||
|
assert.NotNil(t, seeker)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("when FileInfo exists but FileInfo.Path is not set", func(t *testing.T) {
|
||||||
|
th.Store.EXPECT().GetFileInfo("fileInfoID").Return(&mmModel.FileInfo{
|
||||||
|
Id: "fileInfoID",
|
||||||
|
Path: "",
|
||||||
|
}, nil)
|
||||||
|
|
||||||
|
mockedFileBackend := &mocks.FileBackend{}
|
||||||
|
th.App.filesBackend = mockedFileBackend
|
||||||
|
mockedReadCloseSeek := &mocks.ReadCloseSeeker{}
|
||||||
|
readerFunc := func(path string) filestore.ReadCloseSeeker {
|
||||||
|
return mockedReadCloseSeek
|
||||||
|
}
|
||||||
|
|
||||||
|
readerErrorFunc := func(path string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
mockedFileBackend.On("Reader", "teamID/boardID/7fileInfoID.txt").Return(readerFunc, readerErrorFunc)
|
||||||
|
mockedFileBackend.On("FileExists", "teamID/boardID/7fileInfoID.txt").Return(true, nil)
|
||||||
|
|
||||||
|
fileInfo, seeker, err := th.App.GetFile("teamID", "boardID", "7fileInfoID.txt")
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.NotNil(t, fileInfo)
|
||||||
|
assert.NotNil(t, seeker)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -380,6 +380,8 @@ func (c *Client) GetCards(boardID string, page int, perPage int) ([]*model.Card,
|
|||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
var cards []*model.Card
|
var cards []*model.Card
|
||||||
if err := json.NewDecoder(r.Body).Decode(&cards); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&cards); err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
@ -398,6 +400,8 @@ func (c *Client) PatchCard(cardID string, cardPatch *model.CardPatch, disableNot
|
|||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
var cardNew *model.Card
|
var cardNew *model.Card
|
||||||
if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&cardNew); err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
@ -412,6 +416,8 @@ func (c *Client) GetCard(cardID string) (*model.Card, *Response) {
|
|||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
|
|
||||||
var card *model.Card
|
var card *model.Card
|
||||||
if err := json.NewDecoder(r.Body).Decode(&card); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&card); err != nil {
|
||||||
return nil, BuildErrorResponse(r, err)
|
return nil, BuildErrorResponse(r, err)
|
||||||
@ -450,6 +456,7 @@ func (c *Client) DeleteCategory(teamID, categoryID string) *Response {
|
|||||||
return BuildErrorResponse(r, err)
|
return BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
return BuildResponse(r)
|
return BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1049,6 +1056,7 @@ func (c *Client) HideBoard(teamID, categoryID, boardID string) *Response {
|
|||||||
return BuildErrorResponse(r, err)
|
return BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
return BuildResponse(r)
|
return BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1058,5 +1066,6 @@ func (c *Client) UnhideBoard(teamID, categoryID, boardID string) *Response {
|
|||||||
return BuildErrorResponse(r, err)
|
return BuildErrorResponse(r, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
defer closeBody(r)
|
||||||
return BuildResponse(r)
|
return BuildResponse(r)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
|
|||||||
"extension",
|
"extension",
|
||||||
"size",
|
"size",
|
||||||
"delete_at",
|
"delete_at",
|
||||||
|
"path",
|
||||||
"archived",
|
"archived",
|
||||||
).
|
).
|
||||||
Values(
|
Values(
|
||||||
@ -31,6 +32,7 @@ func (s *SQLStore) saveFileInfo(db sq.BaseRunner, fileInfo *mmModel.FileInfo) er
|
|||||||
fileInfo.Extension,
|
fileInfo.Extension,
|
||||||
fileInfo.Size,
|
fileInfo.Size,
|
||||||
fileInfo.DeleteAt,
|
fileInfo.DeleteAt,
|
||||||
|
fileInfo.Path,
|
||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,6 +59,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
|
|||||||
"extension",
|
"extension",
|
||||||
"size",
|
"size",
|
||||||
"archived",
|
"archived",
|
||||||
|
"path",
|
||||||
).
|
).
|
||||||
From(s.tablePrefix + "file_info").
|
From(s.tablePrefix + "file_info").
|
||||||
Where(sq.Eq{"Id": id})
|
Where(sq.Eq{"Id": id})
|
||||||
@ -73,6 +76,7 @@ func (s *SQLStore) getFileInfo(db sq.BaseRunner, id string) (*mmModel.FileInfo,
|
|||||||
&fileInfo.Extension,
|
&fileInfo.Extension,
|
||||||
&fileInfo.Size,
|
&fileInfo.Size,
|
||||||
&fileInfo.Archived,
|
&fileInfo.Archived,
|
||||||
|
&fileInfo.Path,
|
||||||
)
|
)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
SELECT 1;
|
@ -0,0 +1 @@
|
|||||||
|
{{ addColumnIfNeeded "file_info" "path" "varchar(512)" "" }}
|
@ -2,6 +2,7 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"path"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -120,3 +121,7 @@ func DedupeStringArr(arr []string) []string {
|
|||||||
|
|
||||||
return dedupedArr
|
return dedupedArr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetBaseFilePath() string {
|
||||||
|
return path.Join("boards", time.Now().Format("20060102"))
|
||||||
|
}
|
||||||
|
@ -203,6 +203,7 @@
|
|||||||
width: inherit;
|
width: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.MultiPerson.octo-propertyvalue,
|
||||||
.Person.octo-propertyvalue,
|
.Person.octo-propertyvalue,
|
||||||
.DateRange.octo-propertyvalue {
|
.DateRange.octo-propertyvalue {
|
||||||
overflow: unset;
|
overflow: unset;
|
||||||
|
@ -34,6 +34,23 @@ exports[`properties/dateRange handle clear 1`] = `
|
|||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`properties/dateRange returns component with new date after prop change 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="DateRange octo-propertyvalue"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
class="Button"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
June 15
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`properties/dateRange returns default correctly 1`] = `
|
exports[`properties/dateRange returns default correctly 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
|
@ -315,4 +315,36 @@ describe('properties/dateRange', () => {
|
|||||||
|
|
||||||
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: today}))
|
expect(mockedMutator.changePropertyValue).toHaveBeenCalledWith(board.id, card, propertyTemplate.id, JSON.stringify({from: today}))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('returns component with new date after prop change', () => {
|
||||||
|
const component = wrapIntl(
|
||||||
|
<DateProp
|
||||||
|
property={new DateProperty()}
|
||||||
|
propertyValue=''
|
||||||
|
showEmptyPlaceholder={false}
|
||||||
|
readOnly={false}
|
||||||
|
board={{...board}}
|
||||||
|
card={{...card}}
|
||||||
|
propertyTemplate={propertyTemplate}
|
||||||
|
/>,
|
||||||
|
)
|
||||||
|
|
||||||
|
const {container, rerender} = render(component)
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
wrapIntl(
|
||||||
|
<DateProp
|
||||||
|
property={new DateProperty()}
|
||||||
|
propertyValue={'{"from": ' + June15.getTime().toString() + '}'}
|
||||||
|
showEmptyPlaceholder={false}
|
||||||
|
readOnly={false}
|
||||||
|
board={{...board}}
|
||||||
|
card={{...card}}
|
||||||
|
propertyTemplate={propertyTemplate}
|
||||||
|
/>,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
import React, {useMemo, useState, useCallback} from 'react'
|
import React, {useMemo, useState, useCallback, useEffect} from 'react'
|
||||||
import {useIntl} from 'react-intl'
|
import {useIntl} from 'react-intl'
|
||||||
import {DateUtils} from 'react-day-picker'
|
import {DateUtils} from 'react-day-picker'
|
||||||
import MomentLocaleUtils from 'react-day-picker/moment'
|
import MomentLocaleUtils from 'react-day-picker/moment'
|
||||||
@ -58,6 +58,12 @@ function DateRange(props: PropertyProps): JSX.Element {
|
|||||||
const [value, setValue] = useState(propertyValue)
|
const [value, setValue] = useState(propertyValue)
|
||||||
const intl = useIntl()
|
const intl = useIntl()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (value !== propertyValue) {
|
||||||
|
setValue(propertyValue)
|
||||||
|
}
|
||||||
|
}, [propertyValue, setValue])
|
||||||
|
|
||||||
const onChange = useCallback((newValue) => {
|
const onChange = useCallback((newValue) => {
|
||||||
if (value !== newValue) {
|
if (value !== newValue) {
|
||||||
setValue(newValue)
|
setValue(newValue)
|
||||||
|
@ -50,6 +50,7 @@ export const TelemetryActions = {
|
|||||||
LimitCardLimitReached: 'limit_cardLimitReached',
|
LimitCardLimitReached: 'limit_cardLimitReached',
|
||||||
LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',
|
LimitCardLimitLinkOpen: 'limit_cardLimitLinkOpen',
|
||||||
VersionMoreInfo: 'version_more_info',
|
VersionMoreInfo: 'version_more_info',
|
||||||
|
ClickChannelsRHSBoard: 'click_board_in_channels_RHS',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IEventProps {
|
interface IEventProps {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user