mirror of
https://github.com/mattermost/focalboard.git
synced 2025-03-20 20:45:00 +02:00
New Props: Created By, Created At, Updated By, Updated At (#583)
* Added create_at column for blocks * Populating created by * Added logic for storing created by * Added GetBlock by ID to store interface * Added creayed by and modified by properties * Added created by and modified by properties * Added lastmodifiedat property * Fixed existing webapp test * Added webapp unit tests * Added webapp unit tests * Added webapp unit tests * Adding server test * Added server tests * Fixed a bug causing created by to be set empty * Avodining timezone specific test behavior * Made cypress viewport bigger to avoid out-of-viewoport issues in multiple tests * Removed a leftover comment * Added updated at/by in table view * Added updated at in card view * Fixing sort * Fixed sorting of updated by * Fixed existing tests * Added table tests * Added cardTree fix * Fixed tests * Removed unused import * Update snapshots * Added a tamper attempt test * Removed some leftover debug code * Removed sending creator from client * Fixed lint error * Fixed a build issue * Avoided setting insert query params multiple times * Multiple minor review fixes * Fixed test
This commit is contained in:
parent
16708e93cd
commit
e0ec1c03e0
@ -254,7 +254,7 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
auditRec.Success()
|
||||
}
|
||||
|
||||
func stampModifiedByUser(r *http.Request, blocks []model.Block, auditRec *audit.Record) {
|
||||
func stampModificationMetadata(r *http.Request, blocks []model.Block, auditRec *audit.Record) {
|
||||
ctx := r.Context()
|
||||
session := ctx.Value("session").(*model.Session)
|
||||
userID := session.UserID
|
||||
@ -262,8 +262,10 @@ func stampModifiedByUser(r *http.Request, blocks []model.Block, auditRec *audit.
|
||||
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])
|
||||
@ -347,9 +349,12 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) {
|
||||
auditRec := a.makeAuditRecord(r, "postBlocks", audit.Fail)
|
||||
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
||||
|
||||
stampModifiedByUser(r, blocks, auditRec)
|
||||
stampModificationMetadata(r, blocks, auditRec)
|
||||
|
||||
err = a.app.InsertBlocks(*container, blocks)
|
||||
ctx := r.Context()
|
||||
session := ctx.Value("session").(*model.Session)
|
||||
|
||||
err = a.app.InsertBlocks(*container, blocks, session.UserID)
|
||||
if err != nil {
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
@ -774,9 +779,11 @@ func (a *API) handleImport(w http.ResponseWriter, r *http.Request) {
|
||||
auditRec := a.makeAuditRecord(r, "import", audit.Fail)
|
||||
defer a.audit.LogRecord(audit.LevelModify, auditRec)
|
||||
|
||||
stampModifiedByUser(r, blocks, auditRec)
|
||||
stampModificationMetadata(r, blocks, auditRec)
|
||||
|
||||
err = a.app.InsertBlocks(*container, blocks)
|
||||
ctx := r.Context()
|
||||
session := ctx.Value("session").(*model.Session)
|
||||
err = a.app.InsertBlocks(*container, blocks, session.UserID)
|
||||
if err != nil {
|
||||
a.errorResponse(w, http.StatusInternalServerError, "", err)
|
||||
return
|
||||
|
@ -29,24 +29,24 @@ func (a *App) GetParentID(c store.Container, blockID string) (string, error) {
|
||||
return a.store.GetParentID(c, blockID)
|
||||
}
|
||||
|
||||
func (a *App) InsertBlock(c store.Container, block model.Block) error {
|
||||
err := a.store.InsertBlock(c, block)
|
||||
func (a *App) InsertBlock(c store.Container, block model.Block, userID string) error {
|
||||
err := a.store.InsertBlock(c, &block, userID)
|
||||
if err == nil {
|
||||
a.metrics.IncrementBlocksInserted(1)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *App) InsertBlocks(c store.Container, blocks []model.Block) error {
|
||||
for _, block := range blocks {
|
||||
err := a.store.InsertBlock(c, block)
|
||||
func (a *App) InsertBlocks(c store.Container, blocks []model.Block, userID string) error {
|
||||
for i := range blocks {
|
||||
err := a.store.InsertBlock(c, &blocks[i], userID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a.wsServer.BroadcastBlockChange(c.WorkspaceID, block)
|
||||
a.wsServer.BroadcastBlockChange(c.WorkspaceID, blocks[i])
|
||||
a.metrics.IncrementBlocksInserted(len(blocks))
|
||||
go a.webhook.NotifyUpdate(block)
|
||||
go a.webhook.NotifyUpdate(blocks[i])
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -2,6 +2,7 @@ package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/mattermost/focalboard/server/model"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
@ -29,3 +30,25 @@ func TestGetParentID(t *testing.T) {
|
||||
require.Equal(t, "block-not-found", err.Error())
|
||||
})
|
||||
}
|
||||
|
||||
func TestInsertBlock(t *testing.T) {
|
||||
th := SetupTestHelper(t)
|
||||
|
||||
container := st.Container{
|
||||
WorkspaceID: "0",
|
||||
}
|
||||
|
||||
t.Run("success scenerio", func(t *testing.T) {
|
||||
block := model.Block{}
|
||||
th.Store.EXPECT().InsertBlock(gomock.Eq(container), gomock.Eq(&block), gomock.Eq("user-id-1")).Return(nil)
|
||||
err := th.App.InsertBlock(container, block, "user-id-1")
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("error scenerio", func(t *testing.T) {
|
||||
block := model.Block{}
|
||||
th.Store.EXPECT().InsertBlock(gomock.Eq(container), gomock.Eq(&block), gomock.Eq("user-id-1")).Return(errors.New("dummy error"))
|
||||
err := th.App.InsertBlock(container, block, "user-id-1")
|
||||
require.Error(t, err, "dummy error")
|
||||
})
|
||||
}
|
||||
|
@ -20,6 +20,10 @@ type Block struct {
|
||||
// required: true
|
||||
RootID string `json:"rootId"`
|
||||
|
||||
// The id for user who created this block
|
||||
// required: true
|
||||
CreatedBy string `json:"createdBy"`
|
||||
|
||||
// The id for user who last modified this block
|
||||
// required: true
|
||||
ModifiedBy string `json:"modifiedBy"`
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Code generated by MockGen. DO NOT EDIT.
|
||||
// Source: github.com/mattermost/focalboard/server/services/store (interfaces: Store)
|
||||
// Source: services/store/store.go
|
||||
|
||||
// Package mockstore is a generated GoMock package.
|
||||
package mockstore
|
||||
@ -135,6 +135,21 @@ func (mr *MockStoreMockRecorder) GetAllBlocks(c interface{}) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetAllBlocks", reflect.TypeOf((*MockStore)(nil).GetAllBlocks), c)
|
||||
}
|
||||
|
||||
// GetBlock mocks base method.
|
||||
func (m *MockStore) GetBlock(c store.Container, blockID string) (*model.Block, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBlock", c, blockID)
|
||||
ret0, _ := ret[0].(*model.Block)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBlock indicates an expected call of GetBlock.
|
||||
func (mr *MockStoreMockRecorder) GetBlock(c, blockID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlock", reflect.TypeOf((*MockStore)(nil).GetBlock), c, blockID)
|
||||
}
|
||||
|
||||
// GetBlockCountsByType mocks base method.
|
||||
func (m *MockStore) GetBlockCountsByType() (map[string]int64, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@ -181,18 +196,18 @@ func (mr *MockStoreMockRecorder) GetBlocksWithParentAndType(c, parentID, blockTy
|
||||
}
|
||||
|
||||
// GetBlocksWithRootID mocks base method.
|
||||
func (m *MockStore) GetBlocksWithRootID(arg0 store.Container, arg1 string) ([]model.Block, error) {
|
||||
func (m *MockStore) GetBlocksWithRootID(c store.Container, rootID string) ([]model.Block, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetBlocksWithRootID", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "GetBlocksWithRootID", c, rootID)
|
||||
ret0, _ := ret[0].([]model.Block)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetBlocksWithRootID indicates an expected call of GetBlocksWithRootID.
|
||||
func (mr *MockStoreMockRecorder) GetBlocksWithRootID(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockStoreMockRecorder) GetBlocksWithRootID(c, rootID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithRootID", reflect.TypeOf((*MockStore)(nil).GetBlocksWithRootID), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetBlocksWithRootID", reflect.TypeOf((*MockStore)(nil).GetBlocksWithRootID), c, rootID)
|
||||
}
|
||||
|
||||
// GetBlocksWithType mocks base method.
|
||||
@ -421,32 +436,32 @@ func (mr *MockStoreMockRecorder) GetWorkspaceCount() *gomock.Call {
|
||||
}
|
||||
|
||||
// HasWorkspaceAccess mocks base method.
|
||||
func (m *MockStore) HasWorkspaceAccess(arg0, arg1 string) (bool, error) {
|
||||
func (m *MockStore) HasWorkspaceAccess(userID, workspaceID string) (bool, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "HasWorkspaceAccess", arg0, arg1)
|
||||
ret := m.ctrl.Call(m, "HasWorkspaceAccess", userID, workspaceID)
|
||||
ret0, _ := ret[0].(bool)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// HasWorkspaceAccess indicates an expected call of HasWorkspaceAccess.
|
||||
func (mr *MockStoreMockRecorder) HasWorkspaceAccess(arg0, arg1 interface{}) *gomock.Call {
|
||||
func (mr *MockStoreMockRecorder) HasWorkspaceAccess(userID, workspaceID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasWorkspaceAccess", reflect.TypeOf((*MockStore)(nil).HasWorkspaceAccess), arg0, arg1)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "HasWorkspaceAccess", reflect.TypeOf((*MockStore)(nil).HasWorkspaceAccess), userID, workspaceID)
|
||||
}
|
||||
|
||||
// InsertBlock mocks base method.
|
||||
func (m *MockStore) InsertBlock(c store.Container, block model.Block) error {
|
||||
func (m *MockStore) InsertBlock(c store.Container, block *model.Block, userID string) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertBlock", c, block)
|
||||
ret := m.ctrl.Call(m, "InsertBlock", c, block, userID)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// InsertBlock indicates an expected call of InsertBlock.
|
||||
func (mr *MockStoreMockRecorder) InsertBlock(c, block interface{}) *gomock.Call {
|
||||
func (mr *MockStoreMockRecorder) InsertBlock(c, block, userID interface{}) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBlock", reflect.TypeOf((*MockStore)(nil).InsertBlock), c, block)
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertBlock", reflect.TypeOf((*MockStore)(nil).InsertBlock), c, block, userID)
|
||||
}
|
||||
|
||||
// RefreshSession mocks base method.
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/mattermost/focalboard/server/utils"
|
||||
"time"
|
||||
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
@ -21,6 +22,7 @@ func (s *SQLStore) GetBlocksWithParentAndType(c store.Container, parentID string
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
@ -52,6 +54,7 @@ func (s *SQLStore) GetBlocksWithParent(c store.Container, parentID string) ([]mo
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
@ -82,6 +85,7 @@ func (s *SQLStore) GetBlocksWithRootID(c store.Container, rootID string) ([]mode
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
@ -112,6 +116,7 @@ func (s *SQLStore) GetBlocksWithType(c store.Container, blockType string) ([]mod
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
@ -143,6 +148,7 @@ func (s *SQLStore) GetSubTree2(c store.Container, blockID string) ([]model.Block
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
@ -174,6 +180,7 @@ func (s *SQLStore) GetSubTree3(c store.Container, blockID string) ([]model.Block
|
||||
"l3.id",
|
||||
"l3.parent_id",
|
||||
"l3.root_id",
|
||||
"l3.created_by",
|
||||
"l3.modified_by",
|
||||
"l3."+s.escapeField("schema"),
|
||||
"l3.type",
|
||||
@ -212,6 +219,7 @@ func (s *SQLStore) GetAllBlocks(c store.Container) ([]model.Block, error) {
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
@ -247,6 +255,7 @@ func (s *SQLStore) blocksFromRows(rows *sql.Rows) ([]model.Block, error) {
|
||||
&block.ID,
|
||||
&block.ParentID,
|
||||
&block.RootID,
|
||||
&block.CreatedBy,
|
||||
&modifiedBy,
|
||||
&block.Schema,
|
||||
&block.Type,
|
||||
@ -316,7 +325,7 @@ func (s *SQLStore) GetParentID(c store.Container, blockID string) (string, error
|
||||
return parentID, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) InsertBlock(c store.Container, block model.Block) error {
|
||||
func (s *SQLStore) InsertBlock(c store.Container, block *model.Block, userID string) error {
|
||||
if block.RootID == "" {
|
||||
return errors.New("rootId is nil")
|
||||
}
|
||||
@ -326,18 +335,24 @@ func (s *SQLStore) InsertBlock(c store.Container, block model.Block) error {
|
||||
return err
|
||||
}
|
||||
|
||||
existingBlock, err := s.GetBlock(c, block.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
tx, err := s.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query := s.getQueryBuilder().Insert("").
|
||||
insertQuery := s.getQueryBuilder().Insert("").
|
||||
Columns(
|
||||
"workspace_id",
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
@ -346,38 +361,74 @@ func (s *SQLStore) InsertBlock(c store.Container, block model.Block) error {
|
||||
"create_at",
|
||||
"update_at",
|
||||
"delete_at",
|
||||
).Values(
|
||||
c.WorkspaceID,
|
||||
block.ID,
|
||||
block.ParentID,
|
||||
block.RootID,
|
||||
block.ModifiedBy,
|
||||
block.Schema,
|
||||
block.Type,
|
||||
block.Title,
|
||||
fieldsJSON,
|
||||
block.CreateAt,
|
||||
block.UpdateAt,
|
||||
block.DeleteAt,
|
||||
)
|
||||
)
|
||||
|
||||
// TODO: migrate this delete/insert to an upsert
|
||||
deleteQuery := s.getQueryBuilder().
|
||||
Delete(s.tablePrefix + "blocks").
|
||||
Where(sq.Eq{"id": block.ID}).
|
||||
Where(sq.Eq{"COALESCE(workspace_id, '0')": c.WorkspaceID})
|
||||
_, err = sq.ExecContextWith(ctx, tx, deleteQuery)
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
insertQueryValues := map[string]interface{}{
|
||||
"workspace_id": c.WorkspaceID,
|
||||
"id": block.ID,
|
||||
"parent_id": block.ParentID,
|
||||
"root_id": block.RootID,
|
||||
s.escapeField("schema"): block.Schema,
|
||||
"type": block.Type,
|
||||
"title": block.Title,
|
||||
"fields": fieldsJSON,
|
||||
"delete_at": block.DeleteAt,
|
||||
"created_by": block.CreatedBy,
|
||||
"modified_by": block.ModifiedBy,
|
||||
"create_at": block.CreateAt,
|
||||
"update_at": block.UpdateAt,
|
||||
}
|
||||
|
||||
_, err = sq.ExecContextWith(ctx, tx, query.Into(s.tablePrefix+"blocks"))
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
return err
|
||||
block.UpdateAt = utils.GetMillis()
|
||||
block.ModifiedBy = userID
|
||||
|
||||
if existingBlock != nil {
|
||||
// block with ID exists, so this is an update operation
|
||||
query := s.getQueryBuilder().Update(s.tablePrefix+"blocks").
|
||||
Where(sq.Eq{"id": block.ID}).
|
||||
Set("workspace_id", c.WorkspaceID).
|
||||
Set("parent_id", block.ParentID).
|
||||
Set("root_id", block.RootID).
|
||||
Set("modified_by", block.ModifiedBy).
|
||||
Set(s.escapeField("schema"), block.Schema).
|
||||
Set("type", block.Type).
|
||||
Set("title", block.Title).
|
||||
Set("fields", fieldsJSON).
|
||||
Set("update_at", block.UpdateAt).
|
||||
Set("delete_at", block.DeleteAt)
|
||||
|
||||
q, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
s.logger.Error("InsertBlock error converting update query object to SQL", mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tx.Exec(q, args...); err != nil {
|
||||
s.logger.Error(`InsertBlock error occurred while updating existing block`, mlog.String("blockID", block.ID), mlog.Err(err))
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
block.CreatedBy = userID
|
||||
block.CreateAt = utils.GetMillis()
|
||||
block.ModifiedBy = userID
|
||||
block.UpdateAt = utils.GetMillis()
|
||||
|
||||
insertQueryValues["created_by"] = block.CreatedBy
|
||||
insertQueryValues["create_at"] = block.CreateAt
|
||||
insertQueryValues["update_at"] = block.UpdateAt
|
||||
insertQueryValues["modified_by"] = block.ModifiedBy
|
||||
|
||||
query := insertQuery.SetMap(insertQueryValues)
|
||||
_, err = sq.ExecContextWith(ctx, tx, query.Into(s.tablePrefix+"blocks"))
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// writing block history
|
||||
query := insertQuery.SetMap(insertQueryValues)
|
||||
|
||||
_, err = sq.ExecContextWith(ctx, tx, query.Into(s.tablePrefix+"blocks_history"))
|
||||
if err != nil {
|
||||
_ = tx.Rollback()
|
||||
@ -473,3 +524,38 @@ func (s *SQLStore) GetBlockCountsByType() (map[string]int64, error) {
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (s *SQLStore) GetBlock(c store.Container, blockID string) (*model.Block, error) {
|
||||
query := s.getQueryBuilder().
|
||||
Select(
|
||||
"id",
|
||||
"parent_id",
|
||||
"root_id",
|
||||
"created_by",
|
||||
"modified_by",
|
||||
s.escapeField("schema"),
|
||||
"type",
|
||||
"title",
|
||||
"COALESCE(fields, '{}')",
|
||||
"create_at",
|
||||
"update_at",
|
||||
"delete_at",
|
||||
).
|
||||
From(s.tablePrefix + "blocks").
|
||||
Where(sq.Eq{"id": blockID}).
|
||||
Where(sq.Eq{"coalesce(workspace_id, '0')": c.WorkspaceID})
|
||||
|
||||
rows, err := query.Query()
|
||||
if err != nil {
|
||||
s.logger.Error(`GetBlock ERROR`, mlog.Err(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blocks, err := s.blocksFromRows(rows)
|
||||
|
||||
if len(blocks) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return &blocks[0], nil
|
||||
}
|
||||
|
@ -39,13 +39,13 @@ func (s *SQLStore) importInitialTemplates() error {
|
||||
}
|
||||
|
||||
s.logger.Debug("Inserting blocks", mlog.Int("block_count", len(archive.Blocks)))
|
||||
for _, block := range archive.Blocks {
|
||||
for i := range archive.Blocks {
|
||||
s.logger.Trace("insert block",
|
||||
mlog.String("blockID", block.ID),
|
||||
mlog.String("block_type", block.Type),
|
||||
mlog.String("block_title", block.Title),
|
||||
mlog.String("blockID", archive.Blocks[i].ID),
|
||||
mlog.String("block_type", archive.Blocks[i].Type),
|
||||
mlog.String("block_title", archive.Blocks[i].Title),
|
||||
)
|
||||
err := s.InsertBlock(globalContainer, block)
|
||||
err := s.InsertBlock(globalContainer, &archive.Blocks[i], "system")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1,23 +1,3 @@
|
||||
// Code generated by go-bindata. DO NOT EDIT.
|
||||
// sources:
|
||||
// migrations_files/000001_init.down.sql
|
||||
// migrations_files/000001_init.up.sql
|
||||
// migrations_files/000002_system_settings_table.down.sql
|
||||
// migrations_files/000002_system_settings_table.up.sql
|
||||
// migrations_files/000003_blocks_rootid.down.sql
|
||||
// migrations_files/000003_blocks_rootid.up.sql
|
||||
// migrations_files/000004_auth_table.down.sql
|
||||
// migrations_files/000004_auth_table.up.sql
|
||||
// migrations_files/000005_blocks_modifiedby.down.sql
|
||||
// migrations_files/000005_blocks_modifiedby.up.sql
|
||||
// migrations_files/000006_sharing_table.down.sql
|
||||
// migrations_files/000006_sharing_table.up.sql
|
||||
// migrations_files/000007_workspaces_table.down.sql
|
||||
// migrations_files/000007_workspaces_table.up.sql
|
||||
// migrations_files/000008_teams.down.sql
|
||||
// migrations_files/000008_teams.up.sql
|
||||
// migrations_files/000009_blocks_history.down.sql
|
||||
// migrations_files/000009_blocks_history.up.sql
|
||||
package migrations
|
||||
|
||||
import (
|
||||
@ -25,14 +5,10 @@ import (
|
||||
"compress/gzip"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
func bindata_read(data []byte, name string) ([]byte, error) {
|
||||
gz, err := gzip.NewReader(bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
@ -40,407 +16,193 @@ func bindataRead(data []byte, name string) ([]byte, error) {
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err = io.Copy(&buf, gz)
|
||||
clErr := gz.Close()
|
||||
gz.Close()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Read %q: %v", name, err)
|
||||
}
|
||||
if clErr != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
type asset struct {
|
||||
bytes []byte
|
||||
info os.FileInfo
|
||||
}
|
||||
var __000001_init_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xb6\xe6\x02\x04\x00\x00\xff\xff\x2d\x73\xd0\xe1\x1e\x00\x00\x00")
|
||||
|
||||
type bindataFileInfo struct {
|
||||
name string
|
||||
size int64
|
||||
mode os.FileMode
|
||||
modTime time.Time
|
||||
}
|
||||
|
||||
func (fi bindataFileInfo) Name() string {
|
||||
return fi.name
|
||||
}
|
||||
func (fi bindataFileInfo) Size() int64 {
|
||||
return fi.size
|
||||
}
|
||||
func (fi bindataFileInfo) Mode() os.FileMode {
|
||||
return fi.mode
|
||||
}
|
||||
func (fi bindataFileInfo) ModTime() time.Time {
|
||||
return fi.modTime
|
||||
}
|
||||
func (fi bindataFileInfo) IsDir() bool {
|
||||
return false
|
||||
}
|
||||
func (fi bindataFileInfo) Sys() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
var __000001_initDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xb6\xe6\x02\x04\x00\x00\xff\xff\x2d\x73\xd0\xe1\x1e\x00\x00\x00")
|
||||
|
||||
func _000001_initDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000001_initDownSql,
|
||||
func _000001_init_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000001_init_down_sql,
|
||||
"000001_init.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000001_initDownSql() (*asset, error) {
|
||||
bytes, err := _000001_initDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000001_init_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\xd2\xcf\x6f\x9b\x30\x14\x07\xf0\x33\xfc\x15\xef\x82\x00\x89\xf6\xb2\x29\x9a\xba\x93\x4b\x9d\x95\x8d\x1f\x15\xb8\x6b\xb3\x0b\xa5\xf8\xb1\x59\x03\x42\xb1\x23\x2d\xb2\xfc\xbf\x4f\x64\x24\xca\x92\xf4\xc6\x7b\x58\x1f\xbf\xf7\x95\xc3\x9c\x12\x46\x81\x91\xdb\x98\x42\xb4\x84\x34\x63\x40\x9f\xa3\x82\x15\xa0\xf5\xf5\x30\x62\x23\xfe\x18\xf3\xda\xae\xeb\xdf\x12\x3c\xdb\x12\x1c\xbe\x93\x3c\xbc\x27\xb9\xf7\x61\xe1\x07\xb6\xa5\xb5\x68\xe0\x7a\x58\x4b\xf5\x73\x44\x69\x8c\xe8\x25\x8e\xaa\xac\x14\xb0\x28\xa1\x05\x23\xc9\x03\xfb\xb1\x63\xd3\xc7\x38\x86\x3b\xba\x24\x8f\x31\x83\x34\x7b\xf2\xfc\x40\x6b\xec\xb9\x31\x7b\x45\xbe\xb5\x42\xe1\xb1\x71\x47\x18\x9d\x9c\x33\xc0\x2b\x58\xbe\x9c\xfe\x78\xae\xb3\xba\x72\xba\x2b\x87\x83\x73\x7f\xe3\x24\x37\x4e\xe3\x06\xe0\xa6\xd9\x93\xeb\x9f\x5d\xd0\x6d\xe5\x5b\x7b\xc9\xf7\x16\xfe\xe5\x19\x17\x47\xc6\x50\x8d\xd8\xab\xf2\x9d\x08\x66\xfb\x45\xd6\xbf\xb0\xab\x5e\xb4\xc6\x56\xa2\x31\xff\xca\xd9\x80\xdb\xe8\x4b\x94\xb2\xc0\xb6\xd4\x76\x40\x60\xf4\x79\xf7\x2d\x54\x7b\x28\x1a\x81\x2d\x97\x70\x1a\xeb\xd7\x22\x4b\xf7\xe4\x74\x72\x06\x03\xdb\xaa\x47\xac\x14\x4e\xcb\x1c\xf0\xcd\xc0\x4f\x5b\x1c\x5b\x3c\x69\x3d\xe4\x51\x42\xf2\x15\x7c\xa3\x2b\xf0\x04\x0f\xe0\x10\x8b\x6f\xfb\xff\xed\x34\xed\x4a\x42\x46\x73\x28\x28\x83\x8d\x6a\x3e\x75\xaf\x1f\x21\xcc\xe2\x78\x7a\x3b\x73\x5d\x6e\x7a\x51\xaf\x39\x96\xb5\x98\x87\xfb\x6c\xff\x0d\x00\x00\xff\xff\x89\x6c\x55\x1c\x5e\x02\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000001_init.down.sql", size: 30, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000001_initUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x74\xd2\xcf\x6f\x9b\x30\x14\x07\xf0\x33\xfc\x15\xef\x82\x00\x89\xf6\xb2\x29\x9a\xba\x93\x4b\x9d\x95\x8d\x1f\x15\xb8\x6b\xb3\x0b\xa5\xf8\xb1\x59\x03\x42\xb1\x23\x2d\xb2\xfc\xbf\x4f\x64\x24\xca\x92\xf4\xc6\x7b\x58\x1f\xbf\xf7\x95\xc3\x9c\x12\x46\x81\x91\xdb\x98\x42\xb4\x84\x34\x63\x40\x9f\xa3\x82\x15\xa0\xf5\xf5\x30\x62\x23\xfe\x18\xf3\xda\xae\xeb\xdf\x12\x3c\xdb\x12\x1c\xbe\x93\x3c\xbc\x27\xb9\xf7\x61\xe1\x07\xb6\xa5\xb5\x68\xe0\x7a\x58\x4b\xf5\x73\x44\x69\x8c\xe8\x25\x8e\xaa\xac\x14\xb0\x28\xa1\x05\x23\xc9\x03\xfb\xb1\x63\xd3\xc7\x38\x86\x3b\xba\x24\x8f\x31\x83\x34\x7b\xf2\xfc\x40\x6b\xec\xb9\x31\x7b\x45\xbe\xb5\x42\xe1\xb1\x71\x47\x18\x9d\x9c\x33\xc0\x2b\x58\xbe\x9c\xfe\x78\xae\xb3\xba\x72\xba\x2b\x87\x83\x73\x7f\xe3\x24\x37\x4e\xe3\x06\xe0\xa6\xd9\x93\xeb\x9f\x5d\xd0\x6d\xe5\x5b\x7b\xc9\xf7\x16\xfe\xe5\x19\x17\x47\xc6\x50\x8d\xd8\xab\xf2\x9d\x08\x66\xfb\x45\xd6\xbf\xb0\xab\x5e\xb4\xc6\x56\xa2\x31\xff\xca\xd9\x80\xdb\xe8\x4b\x94\xb2\xc0\xb6\xd4\x76\x40\x60\xf4\x79\xf7\x2d\x54\x7b\x28\x1a\x81\x2d\x97\x70\x1a\xeb\xd7\x22\x4b\xf7\xe4\x74\x72\x06\x03\xdb\xaa\x47\xac\x14\x4e\xcb\x1c\xf0\xcd\xc0\x4f\x5b\x1c\x5b\x3c\x69\x3d\xe4\x51\x42\xf2\x15\x7c\xa3\x2b\xf0\x04\x0f\xe0\x10\x8b\x6f\xfb\xff\xed\x34\xed\x4a\x42\x46\x73\x28\x28\x83\x8d\x6a\x3e\x75\xaf\x1f\x21\xcc\xe2\x78\x7a\x3b\x73\x5d\x6e\x7a\x51\xaf\x39\x96\xb5\x98\x87\xfb\x6c\xff\x0d\x00\x00\xff\xff\x89\x6c\x55\x1c\x5e\x02\x00\x00")
|
||||
|
||||
func _000001_initUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000001_initUpSql,
|
||||
func _000001_init_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000001_init_up_sql,
|
||||
"000001_init.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000001_initUpSql() (*asset, error) {
|
||||
bytes, err := _000001_initUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000002_system_settings_table_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xb6\xe6\x02\x04\x00\x00\xff\xff\xd2\x63\x5d\x39\x27\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000001_init.up.sql", size: 606, mode: os.FileMode(420), modTime: time.Unix(1619132434, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000002_system_settings_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\xae\x2c\x2e\x49\xcd\x8d\x2f\x4e\x2d\x29\xc9\xcc\x4b\x2f\xb6\xe6\x02\x04\x00\x00\xff\xff\xd2\x63\x5d\x39\x27\x00\x00\x00")
|
||||
|
||||
func _000002_system_settings_tableDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000002_system_settings_tableDownSql,
|
||||
func _000002_system_settings_table_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000002_system_settings_table_down_sql,
|
||||
"000002_system_settings_table.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000002_system_settings_tableDownSql() (*asset, error) {
|
||||
bytes, err := _000002_system_settings_tableDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000002_system_settings_table_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xce\x4f\x6b\x83\x30\x18\x80\xf1\xb3\xf9\x14\xef\x51\x41\xc4\xc1\x0e\x83\x9d\xb2\xf0\x8e\xc9\xdc\x2c\xf1\xa5\xe8\x49\x5a\x13\x4b\x40\xed\x9f\xc4\x52\x09\xf9\xee\x45\xe8\xf1\x39\x3c\xf0\x13\x12\x39\x21\x10\xff\x2a\x11\x8a\x6f\xf8\xaf\x08\xb0\x29\x6a\xaa\xc1\xfb\xec\x72\xd3\x83\x79\x84\x60\x57\xeb\xf4\xd4\x59\xed\x9c\x99\x4f\x16\x62\x16\x19\x05\x7b\x2e\xc5\x0f\x97\xf1\x5b\x9e\x27\x29\x8b\xee\x87\x71\xd1\x40\xd8\x50\xca\xa2\x9d\x2c\xfe\xb8\x6c\xe1\x17\x5b\x88\x8d\x4a\x58\xe2\xbd\x19\x20\x9b\x56\x7b\x1d\x43\xd8\x3e\x2e\x08\x25\xd4\x48\xb0\xb8\xe1\x63\x3a\xbe\x83\xa8\xca\x72\xd3\xbc\xba\x5b\x66\xd3\x9f\x95\xee\x7a\xe3\xbd\x9e\x55\x08\x9f\xec\x19\x00\x00\xff\xff\x8e\xa2\x4f\x97\xb0\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000002_system_settings_table.down.sql", size: 39, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000002_system_settings_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x2c\xce\x4f\x6b\x83\x30\x18\x80\xf1\xb3\xf9\x14\xef\x51\x41\xc4\xc1\x0e\x83\x9d\xb2\xf0\x8e\xc9\xdc\x2c\xf1\xa5\xe8\x49\x5a\x13\x4b\x40\xed\x9f\xc4\x52\x09\xf9\xee\x45\xe8\xf1\x39\x3c\xf0\x13\x12\x39\x21\x10\xff\x2a\x11\x8a\x6f\xf8\xaf\x08\xb0\x29\x6a\xaa\xc1\xfb\xec\x72\xd3\x83\x79\x84\x60\x57\xeb\xf4\xd4\x59\xed\x9c\x99\x4f\x16\x62\x16\x19\x05\x7b\x2e\xc5\x0f\x97\xf1\x5b\x9e\x27\x29\x8b\xee\x87\x71\xd1\x40\xd8\x50\xca\xa2\x9d\x2c\xfe\xb8\x6c\xe1\x17\x5b\x88\x8d\x4a\x58\xe2\xbd\x19\x20\x9b\x56\x7b\x1d\x43\xd8\x3e\x2e\x08\x25\xd4\x48\xb0\xb8\xe1\x63\x3a\xbe\x83\xa8\xca\x72\xd3\xbc\xba\x5b\x66\xd3\x9f\x95\xee\x7a\xe3\xbd\x9e\x55\x08\x9f\xec\x19\x00\x00\xff\xff\x8e\xa2\x4f\x97\xb0\x00\x00\x00")
|
||||
|
||||
func _000002_system_settings_tableUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000002_system_settings_tableUpSql,
|
||||
func _000002_system_settings_table_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000002_system_settings_table_up_sql,
|
||||
"000002_system_settings_table.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000002_system_settings_tableUpSql() (*asset, error) {
|
||||
bytes, err := _000002_system_settings_tableUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000003_blocks_rootid_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\xb1\xe6\x02\x04\x00\x00\xff\xff\x51\xe5\xe2\x3a\x33\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000002_system_settings_table.up.sql", size: 176, mode: os.FileMode(420), modTime: time.Unix(1619132434, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000003_blocks_rootidDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\xb1\xe6\x02\x04\x00\x00\xff\xff\x51\xe5\xe2\x3a\x33\x00\x00\x00")
|
||||
|
||||
func _000003_blocks_rootidDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000003_blocks_rootidDownSql,
|
||||
func _000003_blocks_rootid_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000003_blocks_rootid_down_sql,
|
||||
"000003_blocks_rootid.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000003_blocks_rootidDownSql() (*asset, error) {
|
||||
bytes, err := _000003_blocks_rootidDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000003_blocks_rootid_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\x02\x04\x00\x00\xff\xff\xc2\x68\x66\x83\x3e\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000003_blocks_rootid.down.sql", size: 51, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000003_blocks_rootidUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xca\xcf\x2f\x89\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\x02\x04\x00\x00\xff\xff\xc2\x68\x66\x83\x3e\x00\x00\x00")
|
||||
|
||||
func _000003_blocks_rootidUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000003_blocks_rootidUpSql,
|
||||
func _000003_blocks_rootid_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000003_blocks_rootid_up_sql,
|
||||
"000003_blocks_rootid.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000003_blocks_rootidUpSql() (*asset, error) {
|
||||
bytes, err := _000003_blocks_rootidUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000004_auth_table_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\x2d\x4e\x2d\x2a\xb6\xe6\xc2\x2e\x59\x9c\x5a\x5c\x9c\x99\x9f\x57\x6c\xcd\x05\x08\x00\x00\xff\xff\xb6\xc1\x44\xa1\x3d\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000003_blocks_rootid.up.sql", size: 62, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000004_auth_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\x2d\x4e\x2d\x2a\xb6\xe6\xc2\x2e\x59\x9c\x5a\x5c\x9c\x99\x9f\x57\x6c\xcd\x05\x08\x00\x00\xff\xff\xb6\xc1\x44\xa1\x3d\x00\x00\x00")
|
||||
|
||||
func _000004_auth_tableDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000004_auth_tableDownSql,
|
||||
func _000004_auth_table_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000004_auth_table_down_sql,
|
||||
"000004_auth_table.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000004_auth_tableDownSql() (*asset, error) {
|
||||
bytes, err := _000004_auth_tableDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000004_auth_table_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x91\x4d\x4b\x33\x31\x14\x85\xd7\x9d\x5f\x71\x97\x2d\x94\xd2\xb7\xbc\x05\xc1\xd5\x74\x88\x3a\x5a\x5b\x99\x09\xd2\xae\x86\x38\xb9\xa3\xc1\xf9\x32\x37\xe3\x07\x21\xff\x5d\x42\x8b\x85\x4e\x05\x37\xcd\x2e\xcf\x49\x72\x6e\xce\x89\x12\x16\x72\x06\x3c\x5c\x2c\x19\xc4\x57\xb0\x5a\x73\x60\x9b\x38\xe5\x29\x58\x3b\x69\x35\x16\xea\xd3\xb9\x8e\x50\x13\x0c\x83\x81\x92\xf0\x18\x26\xd1\x4d\x98\x0c\xff\x4d\xa7\xa3\x71\x30\xf0\x52\x2d\x2a\x3c\xe6\x58\x09\x55\xfe\xc0\xd9\x7c\xee\x61\x2b\x88\x3e\x1a\xdd\x7b\xa4\x2a\x44\x46\x98\x6b\x34\xc7\x8a\xe8\xcc\x4b\x46\xa8\xdf\x55\x7e\xb0\x98\x1d\x24\x29\x8c\xe8\xb9\xe8\xa6\x25\xd8\x2d\x6b\x55\x01\x93\xb6\x21\xf3\xac\x91\x9c\xbb\x4d\xd7\x2b\x6b\xb1\x24\x74\x8e\xb3\x0d\xb7\x16\x6b\xe9\xdc\x38\x18\xe4\x1a\x85\xc1\x4c\x18\x7f\x6d\x11\x5f\xc7\x2b\xee\xbf\xd7\xca\x13\x54\x62\x89\x7d\xfa\x90\xc4\xf7\x61\xb2\x85\x3b\xb6\x85\xa1\x92\xa3\x60\xb4\x73\xaf\xbe\xe8\xad\x74\xce\x8f\x18\x46\x9c\x25\x90\x32\x0e\x9d\x29\x2e\xaa\xa7\xff\x10\xad\x97\x4b\xdf\xc0\x7e\x9f\x75\xb5\xca\x1b\x89\x59\xae\xf6\xa3\x5d\x06\xc1\xdf\x4a\x22\x24\x52\x4d\xfd\x4b\x4f\xa6\x79\xc5\xfa\x54\x79\x59\xff\xec\xf9\x03\x3c\x57\x54\xdf\x01\x00\x00\xff\xff\xdb\x28\x46\xba\xcf\x02\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000004_auth_table.down.sql", size: 61, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000004_auth_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x91\x4d\x4b\x33\x31\x14\x85\xd7\x9d\x5f\x71\x97\x2d\x94\xd2\xb7\xbc\x05\xc1\xd5\x74\x88\x3a\x5a\x5b\x99\x09\xd2\xae\x86\x38\xb9\xa3\xc1\xf9\x32\x37\xe3\x07\x21\xff\x5d\x42\x8b\x85\x4e\x05\x37\xcd\x2e\xcf\x49\x72\x6e\xce\x89\x12\x16\x72\x06\x3c\x5c\x2c\x19\xc4\x57\xb0\x5a\x73\x60\x9b\x38\xe5\x29\x58\x3b\x69\x35\x16\xea\xd3\xb9\x8e\x50\x13\x0c\x83\x81\x92\xf0\x18\x26\xd1\x4d\x98\x0c\xff\x4d\xa7\xa3\x71\x30\xf0\x52\x2d\x2a\x3c\xe6\x58\x09\x55\xfe\xc0\xd9\x7c\xee\x61\x2b\x88\x3e\x1a\xdd\x7b\xa4\x2a\x44\x46\x98\x6b\x34\xc7\x8a\xe8\xcc\x4b\x46\xa8\xdf\x55\x7e\xb0\x98\x1d\x24\x29\x8c\xe8\xb9\xe8\xa6\x25\xd8\x2d\x6b\x55\x01\x93\xb6\x21\xf3\xac\x91\x9c\xbb\x4d\xd7\x2b\x6b\xb1\x24\x74\x8e\xb3\x0d\xb7\x16\x6b\xe9\xdc\x38\x18\xe4\x1a\x85\xc1\x4c\x18\x7f\x6d\x11\x5f\xc7\x2b\xee\xbf\xd7\xca\x13\x54\x62\x89\x7d\xfa\x90\xc4\xf7\x61\xb2\x85\x3b\xb6\x85\xa1\x92\xa3\x60\xb4\x73\xaf\xbe\xe8\xad\x74\xce\x8f\x18\x46\x9c\x25\x90\x32\x0e\x9d\x29\x2e\xaa\xa7\xff\x10\xad\x97\x4b\xdf\xc0\x7e\x9f\x75\xb5\xca\x1b\x89\x59\xae\xf6\xa3\x5d\x06\xc1\xdf\x4a\x22\x24\x52\x4d\xfd\x4b\x4f\xa6\x79\xc5\xfa\x54\x79\x59\xff\xec\xf9\x03\x3c\x57\x54\xdf\x01\x00\x00\xff\xff\xdb\x28\x46\xba\xcf\x02\x00\x00")
|
||||
|
||||
func _000004_auth_tableUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000004_auth_tableUpSql,
|
||||
func _000004_auth_table_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000004_auth_table_up_sql,
|
||||
"000004_auth_table.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000004_auth_tableUpSql() (*asset, error) {
|
||||
bytes, err := _000004_auth_tableUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000005_blocks_modifiedby_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\xcd\x4f\xc9\x4c\xcb\x4c\x4d\x89\x4f\xaa\xb4\xe6\x02\x04\x00\x00\xff\xff\x6a\xfe\x38\x0a\x37\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000004_auth_table.up.sql", size: 719, mode: os.FileMode(420), modTime: time.Unix(1619132434, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000005_blocks_modifiedbyDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\xcd\x4f\xc9\x4c\xcb\x4c\x4d\x89\x4f\xaa\xb4\xe6\x02\x04\x00\x00\xff\xff\x6a\xfe\x38\x0a\x37\x00\x00\x00")
|
||||
|
||||
func _000005_blocks_modifiedbyDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000005_blocks_modifiedbyDownSql,
|
||||
func _000005_blocks_modifiedby_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000005_blocks_modifiedby_down_sql,
|
||||
"000005_blocks_modifiedby.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000005_blocks_modifiedbyDownSql() (*asset, error) {
|
||||
bytes, err := _000005_blocks_modifiedbyDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000005_blocks_modifiedby_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\xcd\x4f\xc9\x4c\xcb\x4c\x4d\x89\x4f\xaa\x54\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\x02\x04\x00\x00\xff\xff\x30\x55\xd2\xd8\x42\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000005_blocks_modifiedby.down.sql", size: 55, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000005_blocks_modifiedbyUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\xc8\xcd\x4f\xc9\x4c\xcb\x4c\x4d\x89\x4f\xaa\x54\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\x02\x04\x00\x00\xff\xff\x30\x55\xd2\xd8\x42\x00\x00\x00")
|
||||
|
||||
func _000005_blocks_modifiedbyUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000005_blocks_modifiedbyUpSql,
|
||||
func _000005_blocks_modifiedby_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000005_blocks_modifiedby_up_sql,
|
||||
"000005_blocks_modifiedby.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000005_blocks_modifiedbyUpSql() (*asset, error) {
|
||||
bytes, err := _000005_blocks_modifiedbyUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000006_sharing_table_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\xce\x48\x2c\xca\xcc\x4b\xb7\xe6\x02\x04\x00\x00\xff\xff\x7a\x74\xe5\xab\x1f\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000005_blocks_modifiedby.up.sql", size: 66, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000006_sharing_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\xce\x48\x2c\xca\xcc\x4b\xb7\xe6\x02\x04\x00\x00\xff\xff\x7a\x74\xe5\xab\x1f\x00\x00\x00")
|
||||
|
||||
func _000006_sharing_tableDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000006_sharing_tableDownSql,
|
||||
func _000006_sharing_table_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000006_sharing_table_down_sql,
|
||||
"000006_sharing_table.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000006_sharing_tableDownSql() (*asset, error) {
|
||||
bytes, err := _000006_sharing_tableDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000006_sharing_table_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xcf\x51\x4b\x84\x40\x14\x05\xe0\x67\xe7\x57\xdc\x47\x05\x59\x36\x8a\x08\x7a\x1a\x65\xaa\x21\xd3\x18\x87\x68\x9f\x44\xf7\x5e\xeb\xd2\x3a\x6e\xbb\x0a\xc9\x30\xff\x3d\x84\xe8\x61\x1f\x0f\x7c\xe7\xc0\xc9\x8d\x92\x56\x81\x95\x59\xa1\x40\x3f\x40\x59\x59\x50\xef\xba\xb6\x35\x78\xbf\x39\x9e\xa8\xe7\x9f\x10\xce\x9f\xed\x89\xdd\x07\xc4\x22\x62\x84\x37\x69\xf2\x27\x69\xe2\xeb\xdb\x24\x15\x11\xb9\xb6\x3b\x10\x42\x56\x55\x85\x92\x65\x2a\xa2\x69\xfc\x22\xf7\xaf\xae\xb6\xdb\x95\x0d\x23\x72\xcf\x84\x4d\xb7\x5c\x0c\xcc\x47\x6c\x27\x6a\xda\x09\x32\xfd\xa8\x4b\x9b\x8a\xe8\xd5\xe8\x17\x69\x76\xf0\xac\x76\x10\x33\x26\x22\xf1\x9e\x7b\xd8\x0c\xcb\xf9\xfb\x10\xc2\x5a\x96\xb9\x55\x06\x6a\x65\x61\x9e\xfa\xbb\xa1\xbb\x81\xbc\x2a\x8a\xf5\xcb\x5f\x6e\x66\xc7\xfb\x11\xa9\xd9\xb3\xf7\xe4\x30\x84\x7b\xf1\x1b\x00\x00\xff\xff\xc9\xca\x17\x02\xee\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000006_sharing_table.down.sql", size: 31, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000006_sharing_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\xcf\x51\x4b\x84\x40\x14\x05\xe0\x67\xe7\x57\xdc\x47\x05\x59\x36\x8a\x08\x7a\x1a\x65\xaa\x21\xd3\x18\x87\x68\x9f\x44\xf7\x5e\xeb\xd2\x3a\x6e\xbb\x0a\xc9\x30\xff\x3d\x84\xe8\x61\x1f\x0f\x7c\xe7\xc0\xc9\x8d\x92\x56\x81\x95\x59\xa1\x40\x3f\x40\x59\x59\x50\xef\xba\xb6\x35\x78\xbf\x39\x9e\xa8\xe7\x9f\x10\xce\x9f\xed\x89\xdd\x07\xc4\x22\x62\x84\x37\x69\xf2\x27\x69\xe2\xeb\xdb\x24\x15\x11\xb9\xb6\x3b\x10\x42\x56\x55\x85\x92\x65\x2a\xa2\x69\xfc\x22\xf7\xaf\xae\xb6\xdb\x95\x0d\x23\x72\xcf\x84\x4d\xb7\x5c\x0c\xcc\x47\x6c\x27\x6a\xda\x09\x32\xfd\xa8\x4b\x9b\x8a\xe8\xd5\xe8\x17\x69\x76\xf0\xac\x76\x10\x33\x26\x22\xf1\x9e\x7b\xd8\x0c\xcb\xf9\xfb\x10\xc2\x5a\x96\xb9\x55\x06\x6a\x65\x61\x9e\xfa\xbb\xa1\xbb\x81\xbc\x2a\x8a\xf5\xcb\x5f\x6e\x66\xc7\xfb\x11\xa9\xd9\xb3\xf7\xe4\x30\x84\x7b\xf1\x1b\x00\x00\xff\xff\xc9\xca\x17\x02\xee\x00\x00\x00")
|
||||
|
||||
func _000006_sharing_tableUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000006_sharing_tableUpSql,
|
||||
func _000006_sharing_table_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000006_sharing_table_up_sql,
|
||||
"000006_sharing_table.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000006_sharing_tableUpSql() (*asset, error) {
|
||||
bytes, err := _000006_sharing_tableUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000007_workspaces_table_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x2d\xb6\xe6\x02\x04\x00\x00\xff\xff\x1a\xe4\xe6\x36\x22\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000006_sharing_table.up.sql", size: 238, mode: os.FileMode(420), modTime: time.Unix(1619132434, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000007_workspaces_tableDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x2d\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x2d\xb6\xe6\x02\x04\x00\x00\xff\xff\x1a\xe4\xe6\x36\x22\x00\x00\x00")
|
||||
|
||||
func _000007_workspaces_tableDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000007_workspaces_tableDownSql,
|
||||
func _000007_workspaces_table_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000007_workspaces_table_down_sql,
|
||||
"000007_workspaces_table.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000007_workspaces_tableDownSql() (*asset, error) {
|
||||
bytes, err := _000007_workspaces_tableDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000007_workspaces_table_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x8f\x51\x4f\x83\x30\x14\x85\x9f\xe9\xaf\xb8\x8f\x90\x90\x65\x46\x63\x4c\x7c\xea\x48\x55\x14\xc1\x94\x6a\xb6\x27\xc2\xe8\x85\x34\x1b\x50\x69\x89\x2e\x4d\xff\xbb\x41\x17\x1f\xf6\x78\x73\xcf\x39\xdf\x39\x09\x67\x54\x30\x10\x74\x93\x31\x48\x1f\x20\x2f\x04\xb0\x6d\x5a\x8a\x12\x9c\x5b\xe9\x09\x5b\xf5\xed\xfd\xd7\x38\x1d\x8c\xae\x1b\x34\x10\x92\x40\x49\xf8\xa0\x3c\x79\xa2\x3c\xbc\xbe\x8d\x62\x12\x18\xd5\x0d\xb3\xae\xec\x78\xc0\xe1\xff\x75\xb5\x5e\x47\xbf\x71\xf9\x7b\x96\x2d\x22\xb4\x56\x0d\x9d\x01\xe7\x54\x0b\x2b\x3d\x1a\xdb\x4d\x68\xbc\x7f\x2e\x8b\xdc\x39\x3c\x1a\xf4\x5e\xb0\xad\x70\x0e\x07\xe9\x7d\x4c\x82\x7e\x94\xaa\x55\x28\xab\xfd\xe9\x82\x38\x6b\x59\x5b\xac\x6a\x0b\x9b\xf4\x31\xcd\x45\x4c\x82\x37\x9e\xbe\x52\xbe\x83\x17\xb6\x83\x50\xc9\x88\x44\x7f\xa0\xfe\x64\x3e\x8f\xde\x2f\x66\x9a\x08\xc6\xa1\x64\x02\x66\xdb\xde\xf5\xfb\x1b\x48\x8a\x2c\x5b\xf6\x9f\xef\x6a\x1e\x54\x33\x4a\xac\x1a\x75\x6e\x71\x4f\x7e\x02\x00\x00\xff\xff\x26\xb1\xb4\x1a\x22\x01\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000007_workspaces_table.down.sql", size: 34, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000007_workspaces_tableUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x5c\x8f\x51\x4f\x83\x30\x14\x85\x9f\xe9\xaf\xb8\x8f\x90\x90\x65\x46\x63\x4c\x7c\xea\x48\x55\x14\xc1\x94\x6a\xb6\x27\xc2\xe8\x85\x34\x1b\x50\x69\x89\x2e\x4d\xff\xbb\x41\x17\x1f\xf6\x78\x73\xcf\x39\xdf\x39\x09\x67\x54\x30\x10\x74\x93\x31\x48\x1f\x20\x2f\x04\xb0\x6d\x5a\x8a\x12\x9c\x5b\xe9\x09\x5b\xf5\xed\xfd\xd7\x38\x1d\x8c\xae\x1b\x34\x10\x92\x40\x49\xf8\xa0\x3c\x79\xa2\x3c\xbc\xbe\x8d\x62\x12\x18\xd5\x0d\xb3\xae\xec\x78\xc0\xe1\xff\x75\xb5\x5e\x47\xbf\x71\xf9\x7b\x96\x2d\x22\xb4\x56\x0d\x9d\x01\xe7\x54\x0b\x2b\x3d\x1a\xdb\x4d\x68\xbc\x7f\x2e\x8b\xdc\x39\x3c\x1a\xf4\x5e\xb0\xad\x70\x0e\x07\xe9\x7d\x4c\x82\x7e\x94\xaa\x55\x28\xab\xfd\xe9\x82\x38\x6b\x59\x5b\xac\x6a\x0b\x9b\xf4\x31\xcd\x45\x4c\x82\x37\x9e\xbe\x52\xbe\x83\x17\xb6\x83\x50\xc9\x88\x44\x7f\xa0\xfe\x64\x3e\x8f\xde\x2f\x66\x9a\x08\xc6\xa1\x64\x02\x66\xdb\xde\xf5\xfb\x1b\x48\x8a\x2c\x5b\xf6\x9f\xef\x6a\x1e\x54\x33\x4a\xac\x1a\x75\x6e\x71\x4f\x7e\x02\x00\x00\xff\xff\x26\xb1\xb4\x1a\x22\x01\x00\x00")
|
||||
|
||||
func _000007_workspaces_tableUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000007_workspaces_tableUpSql,
|
||||
func _000007_workspaces_table_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000007_workspaces_table_up_sql,
|
||||
"000007_workspaces_table.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000007_workspaces_tableUpSql() (*asset, error) {
|
||||
bytes, err := _000007_workspaces_tableUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000008_teams_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\xb1\xe6\xe2\xc2\xa1\xb1\x38\x23\xb1\x28\x33\x2f\x9d\x1c\x9d\xa9\xc5\xc5\x99\xf9\x79\xa8\x96\x26\x96\x96\x64\xc4\x17\xa7\x16\x95\x65\x26\xa7\x5a\x73\x01\x02\x00\x00\xff\xff\x24\x48\xc4\xb6\xad\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000007_workspaces_table.up.sql", size: 290, mode: os.FileMode(420), modTime: time.Unix(1619132434, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000008_teamsDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x09\xf2\x0f\x50\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\xb1\xe6\xe2\xc2\xa1\xb1\x38\x23\xb1\x28\x33\x2f\x9d\x1c\x9d\xa9\xc5\xc5\x99\xf9\x79\xa8\x96\x26\x96\x96\x64\xc4\x17\xa7\x16\x95\x65\x26\xa7\x5a\x73\x01\x02\x00\x00\xff\xff\x24\x48\xc4\xb6\xad\x00\x00\x00")
|
||||
|
||||
func _000008_teamsDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000008_teamsDownSql,
|
||||
func _000008_teams_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000008_teams_down_sql,
|
||||
"000008_teams.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000008_teamsDownSql() (*asset, error) {
|
||||
bytes, err := _000008_teamsDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000008_teams_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\xe2\xc2\x61\x46\x71\x46\x62\x51\x66\x5e\x3a\x85\x86\xa4\x16\x17\x67\xe6\xe7\xa1\x38\x25\xb1\xb4\x24\x23\xbe\x38\xb5\xa8\x2c\x33\x39\x15\x6e\x8a\x91\x01\xc8\x94\xd0\x00\x17\xc7\x10\x2c\x3e\x51\x08\x76\x0d\x41\xb5\xdd\x56\x41\xdd\x40\x5d\x21\xdc\xc3\x35\xc8\x15\x43\x42\x5d\xc1\x3f\x08\x55\xd0\x33\x58\xc1\x2f\xd4\xc7\xc7\x9a\x0b\x10\x00\x00\xff\xff\xab\x8d\x48\xa9\x30\x01\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000008_teams.down.sql", size: 173, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000008_teamsUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\xf4\x09\x71\x0d\x52\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xe6\x72\x74\x71\x51\x70\xf6\xf7\x09\xf5\xf5\x53\x28\xcf\x2f\xca\x2e\x2e\x48\x4c\x4e\x8d\xcf\x4c\x51\x08\x73\x0c\x72\xf6\x70\x0c\xd2\x30\x36\xd3\xb4\xe6\xe2\xc2\x61\x46\x71\x46\x62\x51\x66\x5e\x3a\x85\x86\xa4\x16\x17\x67\xe6\xe7\xa1\x38\x25\xb1\xb4\x24\x23\xbe\x38\xb5\xa8\x2c\x33\x39\x15\x6e\x8a\x91\x01\xc8\x94\xd0\x00\x17\xc7\x10\x2c\x3e\x51\x08\x76\x0d\x41\xb5\xdd\x56\x41\xdd\x40\x5d\x21\xdc\xc3\x35\xc8\x15\x43\x42\x5d\xc1\x3f\x08\x55\xd0\x33\x58\xc1\x2f\xd4\xc7\xc7\x9a\x0b\x10\x00\x00\xff\xff\xab\x8d\x48\xa9\x30\x01\x00\x00")
|
||||
|
||||
func _000008_teamsUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000008_teamsUpSql,
|
||||
func _000008_teams_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000008_teams_up_sql,
|
||||
"000008_teams.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000008_teamsUpSql() (*asset, error) {
|
||||
bytes, err := _000008_teamsUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000009_blocks_history_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xb6\xe6\x72\xf4\x09\x71\x0d\xc2\x25\x1d\x9f\x91\x59\x5c\x92\x5f\x54\xa9\x10\xe4\xea\xe7\xe8\xeb\xaa\x10\xe2\x8f\xcd\x08\x40\x00\x00\x00\xff\xff\x38\xe5\xec\x7a\x61\x00\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000008_teams.up.sql", size: 304, mode: os.FileMode(420), modTime: time.Unix(1619020233, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000009_blocks_historyDownSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x72\x09\xf2\x0f\x50\x08\x71\x74\xf2\x71\x55\xa8\xae\xd6\x2b\x28\x4a\x4d\xcb\xac\xa8\xad\x4d\xca\xc9\x4f\xce\x2e\xb6\xe6\x72\xf4\x09\x71\x0d\xc2\x25\x1d\x9f\x91\x59\x5c\x92\x5f\x54\xa9\x10\xe4\xea\xe7\xe8\xeb\xaa\x10\xe2\x8f\xcd\x08\x40\x00\x00\x00\xff\xff\x38\xe5\xec\x7a\x61\x00\x00\x00")
|
||||
|
||||
func _000009_blocks_historyDownSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000009_blocks_historyDownSql,
|
||||
func _000009_blocks_history_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000009_blocks_history_down_sql,
|
||||
"000009_blocks_history.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000009_blocks_historyDownSql() (*asset, error) {
|
||||
bytes, err := _000009_blocks_historyDownSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000009_blocks_history_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x93\xd1\x6f\x9b\x3e\x10\xc7\x9f\xc3\x5f\x71\x2f\x28\xf0\x13\xad\x7e\xd2\xa6\x68\x6a\xa4\x49\x84\x5c\x12\x36\xb0\x2b\xe3\xae\xcd\x5e\x68\x02\x66\xb1\x4a\x02\xc5\x44\x5d\x84\xf8\xdf\x27\x3a\x92\xa6\x24\xd1\x1e\xa6\xbd\xf9\x7c\xf6\xe7\xbe\x77\xfe\xda\xf6\x38\x32\xe0\xf6\xc8\x43\xa8\xaa\xeb\xbc\x10\x89\xfc\x59\xd7\xcb\x34\x8b\x9e\x14\x30\x24\xb6\x8f\xc0\xe9\x69\x2e\x5c\x49\x55\x66\xc5\x6e\xa8\x39\x0c\x6d\x8e\x2d\xc3\x9d\x00\xa1\x1c\xf0\xc1\x0d\x78\x70\x86\x68\x68\x3d\x19\xc3\x37\x9b\x39\x33\x9b\x19\x1f\x06\xa6\xa5\xf5\xaa\x4a\x26\x70\x9d\x67\xaa\xfc\x51\x08\x55\xd7\x72\xa3\x44\x51\x86\x8b\x12\xb8\xeb\x63\xc0\x6d\xff\x96\x7f\x7f\xc5\x92\x3b\xcf\x83\x31\x4e\xec\x3b\x8f\x03\xa1\xf7\x86\x69\x55\x95\xd8\xc4\x75\xbd\xa7\xa8\xe7\x54\x96\xe2\x98\x31\xb6\x39\x36\x9c\x13\x80\x11\x70\x36\x69\x32\x46\x5f\x9f\x5f\xe9\xeb\x2b\x3d\x06\x7d\x76\xa3\xfb\x37\x7a\xd2\xb7\xa0\x4f\xe8\x7d\xdf\x3c\x29\xb0\xde\xa9\xe7\xf4\x1c\xdf\x18\x98\xe7\x35\x0e\x8e\x18\xf9\xa2\x10\x9b\x32\xbc\x30\x82\x96\xfd\xa8\xa2\x95\x58\x2f\x1e\xab\x4a\xa4\x4a\xd4\xf5\xef\xb0\x65\xc0\xc8\x9d\xba\x84\x5b\x5a\xaf\xdc\xe5\x02\x38\x3e\xbc\xae\x65\x99\x1e\x82\x44\x8a\x34\x56\xd0\x1d\xeb\x97\x80\x92\x3d\xb2\x39\xd9\x02\x2d\xad\x17\x15\x62\x51\x8a\xa6\x99\x03\x7c\x9b\xc7\xdd\xad\x58\xa4\xa2\xb3\x55\x64\xd9\x99\x66\xd6\x59\x2c\x13\x29\xe2\x70\xb9\xeb\x64\x5e\xb2\xe2\x49\xe5\x8b\x48\x9c\x5e\xba\x65\xae\x6f\xb3\x39\x7c\xc5\x39\x18\xc7\xe7\x2c\x19\x9b\x9a\xf9\x6e\x40\xcd\x35\xdb\x69\x8c\x1b\x20\x87\x6d\x99\x7c\x5a\x2f\x3f\x82\x43\x3d\xaf\x31\x62\x1b\x87\xdb\x8d\x8c\xb2\x58\x84\x91\x6c\x3b\x1d\x6a\xda\x3b\x8c\xe6\x92\x00\x19\x07\x77\x4a\x28\x43\x70\xc9\x39\x9f\x83\x11\xa0\x87\x0e\x87\xff\x60\xc2\xa8\x7f\xf9\x23\x00\x65\x63\x64\x30\x9a\xc3\x91\x37\x30\x70\xcc\xa1\xb6\x7f\xfe\xee\x8b\x1c\x04\xfc\xa3\xca\x40\x09\x38\x94\x4c\x3c\xd7\xe1\x30\xa6\x8d\x3d\x67\x2e\x99\x76\x05\xed\xff\xcc\x5e\x0e\x65\x7f\x18\xc9\x5f\xea\x7a\xab\x3f\x46\x0f\x39\x5e\xc0\xc0\xcb\x4a\x14\x02\xde\x6c\xf7\x19\xfe\x1f\x6a\xbf\x02\x00\x00\xff\xff\xfe\x36\xd4\x6a\xb1\x04\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000009_blocks_history.down.sql", size: 97, mode: os.FileMode(420), modTime: time.Unix(1619132434, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
var __000009_blocks_historyUpSql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x93\xd1\x6f\x9b\x3e\x10\xc7\x9f\xc3\x5f\x71\x2f\x28\xf0\x13\xad\x7e\xd2\xa6\x68\x6a\xa4\x49\x84\x5c\x12\x36\xb0\x2b\xe3\xae\xcd\x5e\x68\x02\x66\xb1\x4a\x02\xc5\x44\x5d\x84\xf8\xdf\x27\x3a\x92\xa6\x24\xd1\x1e\xa6\xbd\xf9\x7c\xf6\xe7\xbe\x77\xfe\xda\xf6\x38\x32\xe0\xf6\xc8\x43\xa8\xaa\xeb\xbc\x10\x89\xfc\x59\xd7\xcb\x34\x8b\x9e\x14\x30\x24\xb6\x8f\xc0\xe9\x69\x2e\x5c\x49\x55\x66\xc5\x6e\xa8\x39\x0c\x6d\x8e\x2d\xc3\x9d\x00\xa1\x1c\xf0\xc1\x0d\x78\x70\x86\x68\x68\x3d\x19\xc3\x37\x9b\x39\x33\x9b\x19\x1f\x06\xa6\xa5\xf5\xaa\x4a\x26\x70\x9d\x67\xaa\xfc\x51\x08\x55\xd7\x72\xa3\x44\x51\x86\x8b\x12\xb8\xeb\x63\xc0\x6d\xff\x96\x7f\x7f\xc5\x92\x3b\xcf\x83\x31\x4e\xec\x3b\x8f\x03\xa1\xf7\x86\x69\x55\x95\xd8\xc4\x75\xbd\xa7\xa8\xe7\x54\x96\xe2\x98\x31\xb6\x39\x36\x9c\x13\x80\x11\x70\x36\x69\x32\x46\x5f\x9f\x5f\xe9\xeb\x2b\x3d\x06\x7d\x76\xa3\xfb\x37\x7a\xd2\xb7\xa0\x4f\xe8\x7d\xdf\x3c\x29\xb0\xde\xa9\xe7\xf4\x1c\xdf\x18\x98\xe7\x35\x0e\x8e\x18\xf9\xa2\x10\x9b\x32\xbc\x30\x82\x96\xfd\xa8\xa2\x95\x58\x2f\x1e\xab\x4a\xa4\x4a\xd4\xf5\xef\xb0\x65\xc0\xc8\x9d\xba\x84\x5b\x5a\xaf\xdc\xe5\x02\x38\x3e\xbc\xae\x65\x99\x1e\x82\x44\x8a\x34\x56\xd0\x1d\xeb\x97\x80\x92\x3d\xb2\x39\xd9\x02\x2d\xad\x17\x15\x62\x51\x8a\xa6\x99\x03\x7c\x9b\xc7\xdd\xad\x58\xa4\xa2\xb3\x55\x64\xd9\x99\x66\xd6\x59\x2c\x13\x29\xe2\x70\xb9\xeb\x64\x5e\xb2\xe2\x49\xe5\x8b\x48\x9c\x5e\xba\x65\xae\x6f\xb3\x39\x7c\xc5\x39\x18\xc7\xe7\x2c\x19\x9b\x9a\xf9\x6e\x40\xcd\x35\xdb\x69\x8c\x1b\x20\x87\x6d\x99\x7c\x5a\x2f\x3f\x82\x43\x3d\xaf\x31\x62\x1b\x87\xdb\x8d\x8c\xb2\x58\x84\x91\x6c\x3b\x1d\x6a\xda\x3b\x8c\xe6\x92\x00\x19\x07\x77\x4a\x28\x43\x70\xc9\x39\x9f\x83\x11\xa0\x87\x0e\x87\xff\x60\xc2\xa8\x7f\xf9\x23\x00\x65\x63\x64\x30\x9a\xc3\x91\x37\x30\x70\xcc\xa1\xb6\x7f\xfe\xee\x8b\x1c\x04\xfc\xa3\xca\x40\x09\x38\x94\x4c\x3c\xd7\xe1\x30\xa6\x8d\x3d\x67\x2e\x99\x76\x05\xed\xff\xcc\x5e\x0e\x65\x7f\x18\xc9\x5f\xea\x7a\xab\x3f\x46\x0f\x39\x5e\xc0\xc0\xcb\x4a\x14\x02\xde\x6c\xf7\x19\xfe\x1f\x6a\xbf\x02\x00\x00\xff\xff\xfe\x36\xd4\x6a\xb1\x04\x00\x00")
|
||||
|
||||
func _000009_blocks_historyUpSqlBytes() ([]byte, error) {
|
||||
return bindataRead(
|
||||
__000009_blocks_historyUpSql,
|
||||
func _000009_blocks_history_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000009_blocks_history_up_sql,
|
||||
"000009_blocks_history.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
func _000009_blocks_historyUpSql() (*asset, error) {
|
||||
bytes, err := _000009_blocks_historyUpSqlBytes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var __000010_blocks_created_by_down_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xec\x94\x51\x6f\x9b\x30\x10\xc7\xdf\xf9\x14\xf7\x82\x02\x53\x5a\x4d\xda\x54\x4d\x8d\x34\x89\x82\xd3\xb0\x81\x1d\x19\x77\x6d\xf6\x42\x13\x30\x8b\x55\x12\x28\x26\xea\x22\xc4\x77\x9f\x48\x48\x4a\x1a\x92\x6a\x5a\xfa\x32\xf5\xd1\xf6\xdd\xff\x8e\xe3\x7f\x3f\xc3\x61\x88\x02\x33\xae\x1c\x04\x45\x71\x9e\x66\x3c\x12\xbf\xcb\x72\x12\x27\xc1\x83\x04\x8a\xb0\xe1\x22\x60\x64\xff\xcd\x4f\xe2\xb0\xa7\x98\x14\x19\x0c\xd5\xf9\x76\x1f\x30\x61\x80\xee\x6c\x8f\x79\x2d\x6a\x9a\x02\x00\x20\x42\xf8\x61\x50\x73\x60\x50\xed\xd3\x85\xde\x5d\xdd\x15\x85\x88\xe0\x3c\x4d\x64\xfe\x2b\xe3\xb2\x2c\xc5\x5c\xf2\x2c\xf7\xc7\x39\x30\xdb\x45\x1e\x33\xdc\x21\xfb\xb9\x12\xc7\x37\x8e\x03\x16\xea\x1b\x37\x0e\x03\x4c\x6e\x35\xbd\x5b\x14\x7c\x1e\x96\x65\x43\x48\x3e\xc6\x22\xe7\x4d\x19\xcb\x60\xa8\x92\xda\xd3\xd0\x3c\x46\xfb\xd5\x8b\xd6\x51\x47\x67\xea\xec\x4c\x0d\x41\x1d\x5c\xaa\xee\xa5\x1a\x75\xba\xd0\xc1\xe4\xb6\xa3\xb7\xd5\x98\x2d\xe5\x63\xdc\x56\x42\xbb\xd0\xdb\x3b\xbd\xd8\x95\x49\xc7\x19\x9f\xe7\xfe\xe1\x71\xd4\x15\xee\x65\x30\xe5\xb3\xf1\x7d\x51\xf0\x58\xf2\xb2\x5c\x1f\x6b\x25\xb8\xb2\xaf\x6d\xcc\xd6\x69\xf9\x32\xe5\xc0\xd0\xdd\xe6\x28\xf2\xb8\x79\x8e\x04\x8f\x43\xb9\x37\xeb\x6f\x1e\xc1\x1b\xed\x2a\xb8\x56\x5e\xe7\x04\x19\x1f\xe7\xbc\xfa\xbc\x66\xa1\x45\x1a\xb6\xdc\x86\x3c\xe6\xfb\xb7\x59\x92\xb4\x7f\xe4\x2c\x09\x45\x24\x78\xe8\x4f\x96\xfb\x8f\x4f\x49\xf6\x20\xd3\x71\xc0\x5b\x53\x87\xd4\x76\x0d\x3a\x82\xef\x68\x04\x5a\x33\xb4\x2b\x42\x7d\x15\xa1\xef\x8c\xb0\xca\x36\xcc\xca\xe8\x1e\x62\xb0\xc8\xa3\x2f\xb3\xc9\x67\x30\x89\xe3\x54\xe6\xad\xcf\xfe\x62\x2e\x82\x24\xe4\x7e\x20\xea\x11\xf4\x14\x65\x47\x46\xb1\xb1\x87\x28\x03\xfb\x1a\x13\x8a\xc0\xc6\x6d\x7b\x01\x9a\x87\x1c\x64\x32\xf8\x00\x7d\x4a\xdc\xf6\xc5\x01\x42\x2d\x44\xe1\x6a\x04\x0d\xff\x20\xcf\xd4\x7b\xca\xc6\x22\x2f\x7f\xd3\xb6\xf8\x1b\x54\x05\x82\xc1\x24\xb8\xef\xd8\x26\x03\x8b\x54\xf6\x1d\xd8\xf8\xfa\x65\x33\x9b\xb5\xda\xb4\x42\xe8\x2b\xa3\xf8\x87\x9e\x9e\x6b\x5b\xc8\x41\x0c\x1d\x90\x80\xa7\x29\xcf\x78\xc3\x7a\x5f\xe1\x63\x4f\xb1\x28\x19\x1e\x62\xda\x9a\x5b\x8a\x72\x14\x7c\xfe\x54\xc8\x3c\xc9\x96\x47\x01\x58\xc7\xfc\x3d\x08\xb7\xea\xef\x40\x7c\x07\xe2\x7f\x0f\xc4\x67\xb7\xbf\x86\x83\xc6\x42\x9d\x1c\x90\xa7\xed\xe2\x8d\x80\xb9\x6d\xf2\x04\x3d\x36\x00\x7a\x8c\x86\x3b\x10\xfb\x13\x00\x00\xff\xff\xbd\xdd\x75\x63\x0d\x0a\x00\x00")
|
||||
|
||||
info := bindataFileInfo{name: "000009_blocks_history.up.sql", size: 1201, mode: os.FileMode(420), modTime: time.Unix(1619212874, 0)}
|
||||
a := &asset{bytes: bytes, info: info}
|
||||
return a, nil
|
||||
func _000010_blocks_created_by_down_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000010_blocks_created_by_down_sql,
|
||||
"000010_blocks_created_by.down.sql",
|
||||
)
|
||||
}
|
||||
|
||||
var __000010_blocks_created_by_up_sql = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x8e\x41\x4b\xc4\x30\x10\x85\xef\xfd\x15\x73\x6b\x0a\xb2\x20\x82\x97\x65\x0f\xb3\x49\x44\x21\x6e\x25\x4d\x05\x4f\x65\xb7\x9d\xb2\xc1\xc6\x48\x12\xd0\x52\xfa\xdf\xa5\x78\x11\xd4\xb2\x97\x39\xcc\x7b\xef\xe3\x43\x65\xa4\x06\x83\x7b\x25\x61\x9a\x36\xef\x81\x7a\xfb\x39\xcf\xa7\xc1\xb7\xaf\x11\x50\x08\xe0\xa5\xaa\x1f\x0f\xd0\x06\x3a\x26\xea\x9a\xd3\x08\xcf\xa8\xf9\x3d\x6a\x76\x73\x5b\x6c\xb3\x55\x40\x73\xb6\x31\xf9\x30\x5e\x02\xca\xea\x27\x81\xe6\x2f\x8b\x4a\x9a\x9f\xab\x1d\xf0\x12\x95\xac\xb8\x64\x87\x5a\xa9\x87\x3b\xc6\x22\x0d\xd4\x26\x70\xbe\xb3\xbd\xfd\x6e\xf5\xc1\xbb\x15\xa1\x8f\x33\x05\xfa\x3f\xdf\xd8\x0e\x76\xbf\xe3\xe5\x5d\x6a\x21\x35\xec\x5f\xd6\xc6\x6f\x91\x42\x6a\x8e\x09\xb0\xe2\x30\x58\x67\x13\x5c\x17\x57\x90\xe7\xcb\x89\x63\x4c\xe4\xf2\x62\x9b\x7d\x05\x00\x00\xff\xff\xcb\x4a\xd7\xe3\x7d\x01\x00\x00")
|
||||
|
||||
func _000010_blocks_created_by_up_sql() ([]byte, error) {
|
||||
return bindata_read(
|
||||
__000010_blocks_created_by_up_sql,
|
||||
"000010_blocks_created_by.up.sql",
|
||||
)
|
||||
}
|
||||
|
||||
// Asset loads and returns the asset for the given name.
|
||||
@ -449,41 +211,11 @@ func _000009_blocks_historyUpSql() (*asset, error) {
|
||||
func Asset(name string) ([]byte, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Asset %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.bytes, nil
|
||||
return f()
|
||||
}
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
|
||||
// MustAsset is like Asset but panics when Asset would return an error.
|
||||
// It simplifies safe initialization of global variables.
|
||||
func MustAsset(name string) []byte {
|
||||
a, err := Asset(name)
|
||||
if err != nil {
|
||||
panic("asset: Asset(" + name + "): " + err.Error())
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
// AssetInfo loads and returns the asset info for the given name.
|
||||
// It returns an error if the asset could not be found or
|
||||
// could not be loaded.
|
||||
func AssetInfo(name string) (os.FileInfo, error) {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
if f, ok := _bindata[cannonicalName]; ok {
|
||||
a, err := f()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("AssetInfo %s can't read by error: %v", name, err)
|
||||
}
|
||||
return a.info, nil
|
||||
}
|
||||
return nil, fmt.Errorf("AssetInfo %s not found", name)
|
||||
}
|
||||
|
||||
// AssetNames returns the names of the assets.
|
||||
func AssetNames() []string {
|
||||
names := make([]string, 0, len(_bindata))
|
||||
@ -494,27 +226,28 @@ func AssetNames() []string {
|
||||
}
|
||||
|
||||
// _bindata is a table, holding each asset generator, mapped to its name.
|
||||
var _bindata = map[string]func() (*asset, error){
|
||||
"000001_init.down.sql": _000001_initDownSql,
|
||||
"000001_init.up.sql": _000001_initUpSql,
|
||||
"000002_system_settings_table.down.sql": _000002_system_settings_tableDownSql,
|
||||
"000002_system_settings_table.up.sql": _000002_system_settings_tableUpSql,
|
||||
"000003_blocks_rootid.down.sql": _000003_blocks_rootidDownSql,
|
||||
"000003_blocks_rootid.up.sql": _000003_blocks_rootidUpSql,
|
||||
"000004_auth_table.down.sql": _000004_auth_tableDownSql,
|
||||
"000004_auth_table.up.sql": _000004_auth_tableUpSql,
|
||||
"000005_blocks_modifiedby.down.sql": _000005_blocks_modifiedbyDownSql,
|
||||
"000005_blocks_modifiedby.up.sql": _000005_blocks_modifiedbyUpSql,
|
||||
"000006_sharing_table.down.sql": _000006_sharing_tableDownSql,
|
||||
"000006_sharing_table.up.sql": _000006_sharing_tableUpSql,
|
||||
"000007_workspaces_table.down.sql": _000007_workspaces_tableDownSql,
|
||||
"000007_workspaces_table.up.sql": _000007_workspaces_tableUpSql,
|
||||
"000008_teams.down.sql": _000008_teamsDownSql,
|
||||
"000008_teams.up.sql": _000008_teamsUpSql,
|
||||
"000009_blocks_history.down.sql": _000009_blocks_historyDownSql,
|
||||
"000009_blocks_history.up.sql": _000009_blocks_historyUpSql,
|
||||
var _bindata = map[string]func() ([]byte, error){
|
||||
"000001_init.down.sql": _000001_init_down_sql,
|
||||
"000001_init.up.sql": _000001_init_up_sql,
|
||||
"000002_system_settings_table.down.sql": _000002_system_settings_table_down_sql,
|
||||
"000002_system_settings_table.up.sql": _000002_system_settings_table_up_sql,
|
||||
"000003_blocks_rootid.down.sql": _000003_blocks_rootid_down_sql,
|
||||
"000003_blocks_rootid.up.sql": _000003_blocks_rootid_up_sql,
|
||||
"000004_auth_table.down.sql": _000004_auth_table_down_sql,
|
||||
"000004_auth_table.up.sql": _000004_auth_table_up_sql,
|
||||
"000005_blocks_modifiedby.down.sql": _000005_blocks_modifiedby_down_sql,
|
||||
"000005_blocks_modifiedby.up.sql": _000005_blocks_modifiedby_up_sql,
|
||||
"000006_sharing_table.down.sql": _000006_sharing_table_down_sql,
|
||||
"000006_sharing_table.up.sql": _000006_sharing_table_up_sql,
|
||||
"000007_workspaces_table.down.sql": _000007_workspaces_table_down_sql,
|
||||
"000007_workspaces_table.up.sql": _000007_workspaces_table_up_sql,
|
||||
"000008_teams.down.sql": _000008_teams_down_sql,
|
||||
"000008_teams.up.sql": _000008_teams_up_sql,
|
||||
"000009_blocks_history.down.sql": _000009_blocks_history_down_sql,
|
||||
"000009_blocks_history.up.sql": _000009_blocks_history_up_sql,
|
||||
"000010_blocks_created_by.down.sql": _000010_blocks_created_by_down_sql,
|
||||
"000010_blocks_created_by.up.sql": _000010_blocks_created_by_up_sql,
|
||||
}
|
||||
|
||||
// AssetDir returns the file names below a certain
|
||||
// directory embedded in the file by go-bindata.
|
||||
// For example if you run go-bindata on data/... and data contains the
|
||||
@ -544,81 +277,55 @@ func AssetDir(name string) ([]string, error) {
|
||||
return nil, fmt.Errorf("Asset %s not found", name)
|
||||
}
|
||||
rv := make([]string, 0, len(node.Children))
|
||||
for childName := range node.Children {
|
||||
rv = append(rv, childName)
|
||||
for name := range node.Children {
|
||||
rv = append(rv, name)
|
||||
}
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
type bintree struct {
|
||||
Func func() (*asset, error)
|
||||
Children map[string]*bintree
|
||||
type _bintree_t struct {
|
||||
Func func() ([]byte, error)
|
||||
Children map[string]*_bintree_t
|
||||
}
|
||||
var _bintree = &bintree{nil, map[string]*bintree{
|
||||
"000001_init.down.sql": &bintree{_000001_initDownSql, map[string]*bintree{}},
|
||||
"000001_init.up.sql": &bintree{_000001_initUpSql, map[string]*bintree{}},
|
||||
"000002_system_settings_table.down.sql": &bintree{_000002_system_settings_tableDownSql, map[string]*bintree{}},
|
||||
"000002_system_settings_table.up.sql": &bintree{_000002_system_settings_tableUpSql, map[string]*bintree{}},
|
||||
"000003_blocks_rootid.down.sql": &bintree{_000003_blocks_rootidDownSql, map[string]*bintree{}},
|
||||
"000003_blocks_rootid.up.sql": &bintree{_000003_blocks_rootidUpSql, map[string]*bintree{}},
|
||||
"000004_auth_table.down.sql": &bintree{_000004_auth_tableDownSql, map[string]*bintree{}},
|
||||
"000004_auth_table.up.sql": &bintree{_000004_auth_tableUpSql, map[string]*bintree{}},
|
||||
"000005_blocks_modifiedby.down.sql": &bintree{_000005_blocks_modifiedbyDownSql, map[string]*bintree{}},
|
||||
"000005_blocks_modifiedby.up.sql": &bintree{_000005_blocks_modifiedbyUpSql, map[string]*bintree{}},
|
||||
"000006_sharing_table.down.sql": &bintree{_000006_sharing_tableDownSql, map[string]*bintree{}},
|
||||
"000006_sharing_table.up.sql": &bintree{_000006_sharing_tableUpSql, map[string]*bintree{}},
|
||||
"000007_workspaces_table.down.sql": &bintree{_000007_workspaces_tableDownSql, map[string]*bintree{}},
|
||||
"000007_workspaces_table.up.sql": &bintree{_000007_workspaces_tableUpSql, map[string]*bintree{}},
|
||||
"000008_teams.down.sql": &bintree{_000008_teamsDownSql, map[string]*bintree{}},
|
||||
"000008_teams.up.sql": &bintree{_000008_teamsUpSql, map[string]*bintree{}},
|
||||
"000009_blocks_history.down.sql": &bintree{_000009_blocks_historyDownSql, map[string]*bintree{}},
|
||||
"000009_blocks_history.up.sql": &bintree{_000009_blocks_historyUpSql, map[string]*bintree{}},
|
||||
var _bintree = &_bintree_t{nil, map[string]*_bintree_t{
|
||||
"000001_init.down.sql": &_bintree_t{_000001_init_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000001_init.up.sql": &_bintree_t{_000001_init_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000002_system_settings_table.down.sql": &_bintree_t{_000002_system_settings_table_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000002_system_settings_table.up.sql": &_bintree_t{_000002_system_settings_table_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000003_blocks_rootid.down.sql": &_bintree_t{_000003_blocks_rootid_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000003_blocks_rootid.up.sql": &_bintree_t{_000003_blocks_rootid_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000004_auth_table.down.sql": &_bintree_t{_000004_auth_table_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000004_auth_table.up.sql": &_bintree_t{_000004_auth_table_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000005_blocks_modifiedby.down.sql": &_bintree_t{_000005_blocks_modifiedby_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000005_blocks_modifiedby.up.sql": &_bintree_t{_000005_blocks_modifiedby_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000006_sharing_table.down.sql": &_bintree_t{_000006_sharing_table_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000006_sharing_table.up.sql": &_bintree_t{_000006_sharing_table_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000007_workspaces_table.down.sql": &_bintree_t{_000007_workspaces_table_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000007_workspaces_table.up.sql": &_bintree_t{_000007_workspaces_table_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000008_teams.down.sql": &_bintree_t{_000008_teams_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000008_teams.up.sql": &_bintree_t{_000008_teams_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000009_blocks_history.down.sql": &_bintree_t{_000009_blocks_history_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000009_blocks_history.up.sql": &_bintree_t{_000009_blocks_history_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000010_blocks_created_by.down.sql": &_bintree_t{_000010_blocks_created_by_down_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
"000010_blocks_created_by.up.sql": &_bintree_t{_000010_blocks_created_by_up_sql, map[string]*_bintree_t{
|
||||
}},
|
||||
}}
|
||||
|
||||
// RestoreAsset restores an asset under the given directory
|
||||
func RestoreAsset(dir, name string) error {
|
||||
data, err := Asset(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := AssetInfo(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.MkdirAll(_filePath(dir, filepath.Dir(name)), os.FileMode(0755))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ioutil.WriteFile(_filePath(dir, name), data, info.Mode())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Chtimes(_filePath(dir, name), info.ModTime(), info.ModTime())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RestoreAssets restores an asset under the given directory recursively
|
||||
func RestoreAssets(dir, name string) error {
|
||||
children, err := AssetDir(name)
|
||||
// File
|
||||
if err != nil {
|
||||
return RestoreAsset(dir, name)
|
||||
}
|
||||
// Dir
|
||||
for _, child := range children {
|
||||
err = RestoreAssets(dir, filepath.Join(name, child))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func _filePath(dir, name string) string {
|
||||
cannonicalName := strings.Replace(name, "\\", "/", -1)
|
||||
return filepath.Join(append([]string{dir}, strings.Split(cannonicalName, "/")...)...)
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,63 @@
|
||||
ALTER TABLE {{.prefix}}blocks RENAME TO {{.prefix}}blocks_old;
|
||||
CREATE TABLE IF NOT EXISTS {{.prefix}}blocks (
|
||||
id VARCHAR(36),
|
||||
{{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}
|
||||
{{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}
|
||||
{{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}
|
||||
parent_id VARCHAR(36),
|
||||
{{if .mysql}}`schema`{{else}}schema{{end}} BIGINT,
|
||||
type TEXT,
|
||||
title TEXT,
|
||||
fields {{if .postgres}}JSON{{else}}TEXT{{end}},
|
||||
create_at BIGINT,
|
||||
update_at BIGINT,
|
||||
delete_at BIGINT,
|
||||
root_id VARCHAR(36),
|
||||
modified_by VARCHAR(36),
|
||||
workspace_id VARCHAR(36),
|
||||
PRIMARY KEY (workspace_id,id)
|
||||
){{if .mysql}}CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci{{end}};
|
||||
|
||||
{{if .mysql}}
|
||||
INSERT IGNORE INTO {{.prefix}}blocks (SELECT * FROM {{.prefix}}blocks_old ORDER BY insert_at DESC);
|
||||
{{end}}
|
||||
{{if .postgres}}
|
||||
INSERT INTO {{.prefix}}blocks (SELECT * FROM {{.prefix}}blocks_old ORDER BY insert_at DESC) ON CONFLICT DO NOTHING;
|
||||
{{end}}
|
||||
{{if .sqlite}}
|
||||
INSERT OR IGNORE INTO {{.prefix}}blocks SELECT * FROM {{.prefix}}blocks_old ORDER BY insert_at DESC;
|
||||
{{end}}
|
||||
DELETE FROM {{.prefix}}blocks where delete_at > 0;
|
||||
DROP TABLE {{.prefix}}blocks_old;
|
||||
|
||||
|
||||
ALTER TABLE {{.prefix}}blocks_history RENAME TO {{.prefix}}blocks_history_old;
|
||||
CREATE TABLE IF NOT EXISTS {{.prefix}}blocks_history (
|
||||
id VARCHAR(36),
|
||||
{{if .postgres}}insert_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),{{end}}
|
||||
{{if .sqlite}}insert_at DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),{{end}}
|
||||
{{if .mysql}}insert_at DATETIME(6) NOT NULL DEFAULT NOW(6),{{end}}
|
||||
parent_id VARCHAR(36),
|
||||
{{if .mysql}}`schema`{{else}}schema{{end}} BIGINT,
|
||||
type TEXT,
|
||||
title TEXT,
|
||||
fields {{if .postgres}}JSON{{else}}TEXT{{end}},
|
||||
create_at BIGINT,
|
||||
update_at BIGINT,
|
||||
delete_at BIGINT,
|
||||
root_id VARCHAR(36),
|
||||
modified_by VARCHAR(36),
|
||||
workspace_id VARCHAR(36),
|
||||
PRIMARY KEY (workspace_id,id)
|
||||
){{if .mysql}}CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci{{end}};
|
||||
|
||||
{{if .mysql}}
|
||||
INSERT IGNORE INTO {{.prefix}}blocks_history (SELECT * FROM {{.prefix}}blocks_history_old ORDER BY insert_at DESC);
|
||||
{{end}}
|
||||
{{if .postgres}}
|
||||
INSERT INTO {{.prefix}}blocks_history (SELECT * FROM {{.prefix}}blocks_history_old ORDER BY insert_at DESC) ON CONFLICT DO NOTHING;
|
||||
{{end}}
|
||||
{{if .sqlite}}
|
||||
INSERT OR IGNORE INTO {{.prefix}}blocks_history SELECT * FROM {{.prefix}}blocks_history_old ORDER BY insert_at DESC;
|
||||
{{end}}
|
||||
DROP TABLE {{.prefix}}blocks_history_old;
|
@ -0,0 +1,4 @@
|
||||
ALTER TABLE {{.prefix}}blocks ADD COLUMN created_by VARCHAR(36);
|
||||
ALTER TABLE {{.prefix}}blocks_history ADD COLUMN created_by VARCHAR(36);
|
||||
|
||||
UPDATE {{.prefix}}blocks SET created_by = COALESCE(NULLIF((select modified_by from {{.prefix}}blocks_history where {{.prefix}}blocks_history.id = {{.prefix}}blocks.id ORDER BY {{.prefix}}blocks_history.insert_at ASC limit 1), ''), 'system');
|
@ -20,9 +20,10 @@ type Store interface {
|
||||
GetAllBlocks(c Container) ([]model.Block, error)
|
||||
GetRootID(c Container, blockID string) (string, error)
|
||||
GetParentID(c Container, blockID string) (string, error)
|
||||
InsertBlock(c Container, block model.Block) error
|
||||
InsertBlock(c Container, block *model.Block, userID string) error
|
||||
DeleteBlock(c Container, blockID string, modifiedBy string) error
|
||||
GetBlockCountsByType() (map[string]int64, error)
|
||||
GetBlock(c Container, blockID string) (*model.Block, error)
|
||||
|
||||
Shutdown() error
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package storetests
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -64,6 +65,11 @@ func StoreTestBlocksStore(t *testing.T, setup func(t *testing.T) (store.Store, f
|
||||
defer tearDown()
|
||||
testGetBlocksWithRootID(t, store, container)
|
||||
})
|
||||
t.Run("GetBlock", func(t *testing.T) {
|
||||
store, tearDown := setup(t)
|
||||
defer tearDown()
|
||||
testGetBlock(t, store, container)
|
||||
})
|
||||
}
|
||||
|
||||
func testInsertBlock(t *testing.T, store store.Store, container store.Container) {
|
||||
@ -80,7 +86,7 @@ func testInsertBlock(t *testing.T, store store.Store, container store.Container)
|
||||
ModifiedBy: userID,
|
||||
}
|
||||
|
||||
err := store.InsertBlock(container, block)
|
||||
err := store.InsertBlock(container, &block, "user-id-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
blocks, err := store.GetAllBlocks(container)
|
||||
@ -95,7 +101,7 @@ func testInsertBlock(t *testing.T, store store.Store, container store.Container)
|
||||
ModifiedBy: userID,
|
||||
}
|
||||
|
||||
err := store.InsertBlock(container, block)
|
||||
err := store.InsertBlock(container, &block, "user-id-1")
|
||||
require.Error(t, err)
|
||||
|
||||
blocks, err := store.GetAllBlocks(container)
|
||||
@ -111,13 +117,85 @@ func testInsertBlock(t *testing.T, store store.Store, container store.Container)
|
||||
Fields: map[string]interface{}{"no-serialiable-value": t.Run},
|
||||
}
|
||||
|
||||
err := store.InsertBlock(container, block)
|
||||
err := store.InsertBlock(container, &block, "user-id-1")
|
||||
require.Error(t, err)
|
||||
|
||||
blocks, err := store.GetAllBlocks(container)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, blocks, initialCount+1)
|
||||
})
|
||||
|
||||
t.Run("insert new block", func(t *testing.T) {
|
||||
block := model.Block{
|
||||
RootID: "root-id",
|
||||
}
|
||||
|
||||
err := store.InsertBlock(container, &block, "user-id-2")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "user-id-2", block.CreatedBy)
|
||||
})
|
||||
|
||||
t.Run("update existing block", func(t *testing.T) {
|
||||
block := model.Block{
|
||||
ID: "id-2",
|
||||
RootID: "root-id",
|
||||
Title: "Old Title",
|
||||
}
|
||||
|
||||
// inserting
|
||||
err := store.InsertBlock(container, &block, "user-id-2")
|
||||
require.NoError(t, err)
|
||||
|
||||
// created by populated from user id for new blocks
|
||||
require.Equal(t, "user-id-2", block.CreatedBy)
|
||||
|
||||
// hack to avoid multiple, quick updates to a card
|
||||
// violating block_history composite primary key constraint
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// updating
|
||||
newBlock := model.Block{
|
||||
ID: "id-2",
|
||||
RootID: "root-id",
|
||||
CreatedBy: "user-id-3",
|
||||
Title: "New Title",
|
||||
}
|
||||
err = store.InsertBlock(container, &newBlock, "user-id-4")
|
||||
require.NoError(t, err)
|
||||
// created by is not altered for existing blocks
|
||||
require.Equal(t, "user-id-3", newBlock.CreatedBy)
|
||||
require.Equal(t, "New Title", newBlock.Title)
|
||||
})
|
||||
|
||||
createdAt, err := time.Parse(time.RFC822, "01 Jan 90 01:00 IST")
|
||||
assert.NoError(t, err)
|
||||
|
||||
updateAt, err := time.Parse(time.RFC822, "02 Jan 90 01:00 IST")
|
||||
assert.NoError(t, err)
|
||||
|
||||
t.Run("data tamper attempt", func(t *testing.T) {
|
||||
block := model.Block{
|
||||
ID: "id-10",
|
||||
RootID: "root-id",
|
||||
Title: "Old Title",
|
||||
CreateAt: createdAt.Unix(),
|
||||
UpdateAt: updateAt.Unix(),
|
||||
CreatedBy: "user-id-5",
|
||||
ModifiedBy: "user-id-6",
|
||||
}
|
||||
|
||||
// inserting
|
||||
err := store.InsertBlock(container, &block, "user-id-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
retrievedBlock, err := store.GetBlock(container, "id-10")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, retrievedBlock)
|
||||
assert.Equal(t, "user-id-1", retrievedBlock.CreatedBy)
|
||||
assert.Equal(t, "user-id-1", retrievedBlock.ModifiedBy)
|
||||
assert.WithinDurationf(t, time.Now(), time.Unix(retrievedBlock.CreateAt / 1000, 0), 1 * time.Second, "create time should be current time")
|
||||
assert.WithinDurationf(t, time.Now(), time.Unix(retrievedBlock.UpdateAt / 1000, 0), 1 * time.Second, "update time should be current time")
|
||||
})
|
||||
}
|
||||
|
||||
func testGetSubTree2(t *testing.T, store store.Store, container store.Container) {
|
||||
@ -165,7 +243,7 @@ func testGetSubTree2(t *testing.T, store store.Store, container store.Container)
|
||||
},
|
||||
}
|
||||
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
blocks, err = store.GetAllBlocks(container)
|
||||
@ -241,7 +319,7 @@ func testGetSubTree3(t *testing.T, store store.Store, container store.Container)
|
||||
},
|
||||
}
|
||||
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
blocks, err = store.GetAllBlocks(container)
|
||||
@ -320,7 +398,7 @@ func testGetRootID(t *testing.T, store store.Store, container store.Container) {
|
||||
},
|
||||
}
|
||||
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
blocks, err = store.GetAllBlocks(container)
|
||||
@ -390,7 +468,7 @@ func testGetParentID(t *testing.T, store store.Store, container store.Container)
|
||||
},
|
||||
}
|
||||
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
blocks, err = store.GetAllBlocks(container)
|
||||
@ -439,7 +517,7 @@ func testDeleteBlock(t *testing.T, store store.Store, container store.Container)
|
||||
ModifiedBy: userID,
|
||||
},
|
||||
}
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
blocks, err = store.GetAllBlocks(container)
|
||||
@ -515,7 +593,7 @@ func testGetBlocksWithParentAndType(t *testing.T, store store.Store, container s
|
||||
Type: "test",
|
||||
},
|
||||
}
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
t.Run("not existing parent", func(t *testing.T) {
|
||||
@ -583,7 +661,7 @@ func testGetBlocksWithParent(t *testing.T, store store.Store, container store.Co
|
||||
Type: "test",
|
||||
},
|
||||
}
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
t.Run("not existing parent", func(t *testing.T) {
|
||||
@ -644,7 +722,7 @@ func testGetBlocksWithType(t *testing.T, store store.Store, container store.Cont
|
||||
Type: "test",
|
||||
},
|
||||
}
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
t.Run("not existing type", func(t *testing.T) {
|
||||
@ -705,7 +783,7 @@ func testGetBlocksWithRootID(t *testing.T, store store.Store, container store.Co
|
||||
Type: "test",
|
||||
},
|
||||
}
|
||||
InsertBlocks(t, store, container, blocksToInsert)
|
||||
InsertBlocks(t, store, container, blocksToInsert, "user-id-1")
|
||||
defer DeleteBlocks(t, store, container, blocksToInsert, "test")
|
||||
|
||||
t.Run("not existing parent", func(t *testing.T) {
|
||||
@ -722,3 +800,32 @@ func testGetBlocksWithRootID(t *testing.T, store store.Store, container store.Co
|
||||
require.Len(t, blocks, 4)
|
||||
})
|
||||
}
|
||||
|
||||
func testGetBlock(t *testing.T, store store.Store, container store.Container) {
|
||||
t.Run("get a block", func(t *testing.T) {
|
||||
block := model.Block{
|
||||
ID: "block-id-10",
|
||||
RootID: "root-id-1",
|
||||
ModifiedBy: "user-id-1",
|
||||
}
|
||||
|
||||
err := store.InsertBlock(container, &block, "user-id-1")
|
||||
require.NoError(t, err)
|
||||
|
||||
fetchedBlock, err := store.GetBlock(container, "block-id-10")
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, fetchedBlock)
|
||||
require.Equal(t, "block-id-10", fetchedBlock.ID)
|
||||
require.Equal(t, "root-id-1", fetchedBlock.RootID)
|
||||
require.Equal(t, "user-id-1", fetchedBlock.CreatedBy)
|
||||
require.Equal(t, "user-id-1", fetchedBlock.ModifiedBy)
|
||||
assert.WithinDurationf(t, time.Now(), time.Unix(fetchedBlock.CreateAt / 1000, 0), 1 * time.Second, "create time should be current time")
|
||||
assert.WithinDurationf(t, time.Now(), time.Unix(fetchedBlock.UpdateAt / 1000, 0), 1 * time.Second, "update time should be current time")
|
||||
})
|
||||
|
||||
t.Run("get a non-existing block", func(t *testing.T) {
|
||||
fetchedBlock, err := store.GetBlock(container, "non-existing-id")
|
||||
require.NoError(t, err)
|
||||
require.Nil(t, fetchedBlock)
|
||||
})
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func InsertBlocks(t *testing.T, s store.Store, container store.Container, blocks []model.Block) {
|
||||
func InsertBlocks(t *testing.T, s store.Store, container store.Container, blocks []model.Block, userID string) {
|
||||
for _, block := range blocks {
|
||||
err := s.InsertBlock(container, block)
|
||||
err := s.InsertBlock(container, &block, userID)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"crypto/rand"
|
||||
"fmt"
|
||||
"log"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CreateGUID returns a random GUID.
|
||||
@ -17,3 +18,8 @@ func CreateGUID() string {
|
||||
|
||||
return uuid
|
||||
}
|
||||
|
||||
// GetMillis is a convenience method to get milliseconds since epoch.
|
||||
func GetMillis() int64 {
|
||||
return time.Now().UnixNano() / int64(time.Millisecond)
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
{
|
||||
"chromeWebSecurity": false,
|
||||
"baseUrl": "http://localhost:8088",
|
||||
"video": false
|
||||
"video": false,
|
||||
"viewportWidth": 1600,
|
||||
"viewportHeight": 1200
|
||||
}
|
||||
|
@ -69,8 +69,8 @@
|
||||
"PropertyType.Select": "Select",
|
||||
"PropertyType.Text": "Text",
|
||||
"PropertyType.URL": "URL",
|
||||
"PropertyType.UpdatedBy": "Updated By",
|
||||
"PropertyType.UpdatedTime": "Updated Time",
|
||||
"PropertyType.UpdatedBy": "Last Updated By",
|
||||
"PropertyType.UpdatedTime": "Last Updated Time",
|
||||
"RegistrationLink.confirmRegenerateToken": "This will invalidate previously shared links. Continue?",
|
||||
"RegistrationLink.copiedLink": "Copied!",
|
||||
"RegistrationLink.copyLink": "Copy link",
|
||||
|
@ -11,6 +11,7 @@ interface IBlock {
|
||||
readonly id: string
|
||||
readonly parentId: string
|
||||
readonly rootId: string
|
||||
readonly createdBy: string
|
||||
readonly modifiedBy: string
|
||||
|
||||
readonly schema: number
|
||||
@ -27,6 +28,7 @@ interface IMutableBlock extends IBlock {
|
||||
id: string
|
||||
parentId: string
|
||||
rootId: string
|
||||
createdBy: string
|
||||
modifiedBy: string
|
||||
|
||||
schema: number
|
||||
@ -44,6 +46,7 @@ class MutableBlock implements IMutableBlock {
|
||||
schema: number
|
||||
parentId: string
|
||||
rootId: string
|
||||
createdBy: string
|
||||
modifiedBy: string
|
||||
type: BlockTypes
|
||||
title: string
|
||||
@ -57,6 +60,7 @@ class MutableBlock implements IMutableBlock {
|
||||
this.schema = 1
|
||||
this.parentId = block.parentId || ''
|
||||
this.rootId = block.rootId || ''
|
||||
this.createdBy = block.createdBy || ''
|
||||
this.modifiedBy = block.modifiedBy || ''
|
||||
this.type = block.type || ''
|
||||
|
||||
|
@ -52,9 +52,6 @@ const CardDetail = (props: Props): JSX.Element|null => {
|
||||
return null
|
||||
}
|
||||
|
||||
// componentWillUnmount(): void {
|
||||
// }
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className='CardDetail content'>
|
||||
|
@ -50,6 +50,7 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||
readOnly={props.readonly}
|
||||
card={card}
|
||||
boardTree={boardTree}
|
||||
cardTree={cardTree}
|
||||
propertyTemplate={propertyTemplate}
|
||||
emptyDisplayValue='Empty'
|
||||
/>
|
||||
|
@ -6,7 +6,7 @@ import {FormattedMessage, useIntl} from 'react-intl'
|
||||
import mutator from '../mutator'
|
||||
import {Utils} from '../utils'
|
||||
import {BoardTree} from '../viewModel/boardTree'
|
||||
import {CardTree, MutableCardTree} from '../viewModel/cardTree'
|
||||
import {CardTree, CardTreeContext, MutableCardTree} from '../viewModel/cardTree'
|
||||
import DeleteIcon from '../widgets/icons/delete'
|
||||
import Menu from '../widgets/menu'
|
||||
|
||||
@ -101,11 +101,13 @@ const CardDialog = (props: Props) => {
|
||||
</div>
|
||||
}
|
||||
{cardTree &&
|
||||
<CardDetail
|
||||
boardTree={props.boardTree}
|
||||
cardTree={cardTree}
|
||||
readonly={props.readonly}
|
||||
/>
|
||||
<CardTreeContext.Provider value={cardTree}>
|
||||
<CardDetail
|
||||
boardTree={props.boardTree}
|
||||
cardTree={cardTree}
|
||||
readonly={props.readonly}
|
||||
/>
|
||||
</CardTreeContext.Provider>
|
||||
}
|
||||
{(!cardTree && syncComplete) &&
|
||||
<div className='banner error'>
|
||||
|
@ -14,6 +14,7 @@ import {BoardTree} from '../viewModel/boardTree'
|
||||
import {UserSettings} from '../userSettings'
|
||||
|
||||
import './centerPanel.scss'
|
||||
|
||||
import CardDialog from './cardDialog'
|
||||
import RootPortal from './rootPortal'
|
||||
import TopBar from './topBar'
|
||||
@ -118,16 +119,16 @@ class CenterPanel extends React.Component<Props, State> {
|
||||
onKeyDown={this.keydownHandler}
|
||||
/>
|
||||
{this.state.shownCardId &&
|
||||
<RootPortal>
|
||||
<CardDialog
|
||||
key={this.state.shownCardId}
|
||||
boardTree={boardTree}
|
||||
cardId={this.state.shownCardId}
|
||||
onClose={() => this.showCard(undefined)}
|
||||
showCard={(cardId) => this.showCard(cardId)}
|
||||
readonly={this.props.readonly}
|
||||
/>
|
||||
</RootPortal>}
|
||||
<RootPortal>
|
||||
<CardDialog
|
||||
key={this.state.shownCardId}
|
||||
boardTree={boardTree}
|
||||
cardId={this.state.shownCardId}
|
||||
onClose={() => this.showCard(undefined)}
|
||||
showCard={(cardId) => this.showCard(cardId)}
|
||||
readonly={this.props.readonly}
|
||||
/>
|
||||
</RootPortal>}
|
||||
|
||||
<div className='top-head'>
|
||||
<TopBar/>
|
||||
@ -159,24 +160,24 @@ class CenterPanel extends React.Component<Props, State> {
|
||||
/>}
|
||||
|
||||
{activeView.viewType === 'table' &&
|
||||
<Table
|
||||
boardTree={boardTree}
|
||||
selectedCardIds={this.state.selectedCardIds}
|
||||
readonly={this.props.readonly}
|
||||
cardIdToFocusOnRender={this.state.cardIdToFocusOnRender}
|
||||
showCard={this.showCard}
|
||||
addCard={this.addCard}
|
||||
onCardClicked={this.cardClicked}
|
||||
/>}
|
||||
<Table
|
||||
boardTree={boardTree}
|
||||
selectedCardIds={this.state.selectedCardIds}
|
||||
readonly={this.props.readonly}
|
||||
cardIdToFocusOnRender={this.state.cardIdToFocusOnRender}
|
||||
showCard={this.showCard}
|
||||
addCard={this.addCard}
|
||||
onCardClicked={this.cardClicked}
|
||||
/>}
|
||||
|
||||
{activeView.viewType === 'gallery' &&
|
||||
<Gallery
|
||||
boardTree={boardTree}
|
||||
readonly={this.props.readonly}
|
||||
onCardClicked={this.cardClicked}
|
||||
selectedCardIds={this.state.selectedCardIds}
|
||||
addCard={(show) => this.addCard('', show)}
|
||||
/>}
|
||||
<Gallery
|
||||
boardTree={boardTree}
|
||||
readonly={this.props.readonly}
|
||||
onCardClicked={this.cardClicked}
|
||||
selectedCardIds={this.state.selectedCardIds}
|
||||
addCard={(show) => this.addCard('', show)}
|
||||
/>}
|
||||
|
||||
</div>
|
||||
)
|
||||
|
@ -24,6 +24,7 @@ describe('components/content/CheckboxElement', () => {
|
||||
type: 'checkbox',
|
||||
title: 'test-title',
|
||||
fields: {},
|
||||
createdBy: 'test-user-id',
|
||||
createAt: 0,
|
||||
updateAt: 0,
|
||||
deleteAt: 0,
|
||||
|
@ -27,7 +27,7 @@ const Gallery = (props: Props): JSX.Element => {
|
||||
const {cards, activeView} = boardTree
|
||||
const visiblePropertyTemplates = boardTree.board.cardProperties.filter((template) => boardTree.activeView.visiblePropertyIds.includes(template.id))
|
||||
const [cardTrees, setCardTrees] = useState<{[key: string]: CardTree | undefined}>({})
|
||||
const isManualSort = activeView.sortOptions.length < 1
|
||||
const isManualSort = activeView.sortOptions.length === 0
|
||||
|
||||
const onDropToCard = (srcCard: Card, dstCard: Card) => {
|
||||
Utils.log(`onDropToCard: ${dstCard.title}`)
|
||||
|
@ -117,6 +117,7 @@ const GalleryCard = React.memo((props: Props) => {
|
||||
<PropertyValueElement
|
||||
readOnly={true}
|
||||
card={cardTree.card}
|
||||
cardTree={cardTree}
|
||||
propertyTemplate={template}
|
||||
emptyDisplayValue=''
|
||||
/>
|
||||
|
@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
/* eslint-disable max-lines */
|
||||
import React from 'react'
|
||||
import React, {useRef, useState} from 'react'
|
||||
import {FormattedMessage, injectIntl, IntlShape} from 'react-intl'
|
||||
|
||||
import {IPropertyOption} from '../../blocks/board'
|
||||
@ -11,6 +11,10 @@ import {Utils} from '../../utils'
|
||||
import {BoardTree} from '../../viewModel/boardTree'
|
||||
import Button from '../../widgets/buttons/button'
|
||||
|
||||
import {CardTree, MutableCardTree} from '../../viewModel/cardTree'
|
||||
|
||||
import useCardListener from '../../hooks/cardListener'
|
||||
|
||||
import KanbanCard from './kanbanCard'
|
||||
import KanbanColumn from './kanbanColumn'
|
||||
import KanbanColumnHeader from './kanbanColumnHeader'
|
||||
@ -28,159 +32,52 @@ type Props = {
|
||||
showCard: (cardId?: string) => void
|
||||
}
|
||||
|
||||
type State = {
|
||||
draggedCards: Card[]
|
||||
draggedHeaderOption?: IPropertyOption
|
||||
}
|
||||
const Kanban = (props: Props) => {
|
||||
const {boardTree} = props
|
||||
const {cards, groupByProperty} = boardTree
|
||||
|
||||
class Kanban extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
draggedCards: [],
|
||||
}
|
||||
if (!groupByProperty) {
|
||||
Utils.assertFailure('Board views must have groupByProperty set')
|
||||
return <div/>
|
||||
}
|
||||
|
||||
shouldComponentUpdate(): boolean {
|
||||
return true
|
||||
}
|
||||
const propertyValues = groupByProperty.options || []
|
||||
Utils.log(`${propertyValues.length} propertyValues`)
|
||||
|
||||
render(): JSX.Element {
|
||||
const {boardTree} = this.props
|
||||
const {groupByProperty} = boardTree
|
||||
const {board, activeView, visibleGroups, hiddenGroups} = boardTree
|
||||
const visiblePropertyTemplates = board.cardProperties.filter((template) => activeView.visiblePropertyIds.includes(template.id))
|
||||
const isManualSort = activeView.sortOptions.length === 0
|
||||
|
||||
if (!groupByProperty) {
|
||||
Utils.assertFailure('Board views must have groupByProperty set')
|
||||
return <div/>
|
||||
}
|
||||
const [cardTrees, setCardTrees] = useState<{[key: string]: CardTree | undefined}>({})
|
||||
const cardTreeRef = useRef<{[key: string]: CardTree | undefined}>()
|
||||
cardTreeRef.current = cardTrees
|
||||
|
||||
const propertyValues = groupByProperty.options || []
|
||||
Utils.log(`${propertyValues.length} propertyValues`)
|
||||
|
||||
const {board, activeView, visibleGroups, hiddenGroups} = boardTree
|
||||
const visiblePropertyTemplates = board.cardProperties.filter((template) => activeView.visiblePropertyIds.includes(template.id))
|
||||
const isManualSort = activeView.sortOptions.length < 1
|
||||
|
||||
return (
|
||||
<div className='Kanban'>
|
||||
<div
|
||||
className='octo-board-header'
|
||||
id='mainBoardHeader'
|
||||
>
|
||||
{/* Column headers */}
|
||||
|
||||
{visibleGroups.map((group) => (
|
||||
<KanbanColumnHeader
|
||||
key={group.option.id}
|
||||
group={group}
|
||||
boardTree={boardTree}
|
||||
intl={this.props.intl}
|
||||
addCard={this.props.addCard}
|
||||
readonly={this.props.readonly}
|
||||
propertyNameChanged={this.propertyNameChanged}
|
||||
onDropToColumn={this.onDropToColumn}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Hidden column header */}
|
||||
|
||||
{hiddenGroups.length > 0 &&
|
||||
<div className='octo-board-header-cell narrow'>
|
||||
<FormattedMessage
|
||||
id='BoardComponent.hidden-columns'
|
||||
defaultMessage='Hidden columns'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
{!this.props.readonly &&
|
||||
<div className='octo-board-header-cell narrow'>
|
||||
<Button
|
||||
onClick={this.addGroupClicked}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='BoardComponent.add-a-group'
|
||||
defaultMessage='+ Add a group'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
|
||||
<div
|
||||
className='octo-board-body'
|
||||
id='mainBoardBody'
|
||||
>
|
||||
{/* Columns */}
|
||||
|
||||
{visibleGroups.map((group) => (
|
||||
<KanbanColumn
|
||||
key={group.option.id || 'empty'}
|
||||
onDrop={(card: Card) => this.onDropToColumn(group.option, card)}
|
||||
>
|
||||
{group.cards.map((card) => (
|
||||
<KanbanCard
|
||||
card={card}
|
||||
visiblePropertyTemplates={visiblePropertyTemplates}
|
||||
key={card.id}
|
||||
readonly={this.props.readonly}
|
||||
isSelected={this.props.selectedCardIds.includes(card.id)}
|
||||
onClick={(e) => {
|
||||
this.props.onCardClicked(e, card)
|
||||
}}
|
||||
onDrop={this.onDropToCard}
|
||||
showCard={this.props.showCard}
|
||||
isManualSort={isManualSort}
|
||||
/>
|
||||
))}
|
||||
{!this.props.readonly &&
|
||||
<Button
|
||||
onClick={() => {
|
||||
this.props.addCard(group.option.id, true)
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='BoardComponent.new'
|
||||
defaultMessage='+ New'
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
</KanbanColumn>
|
||||
))}
|
||||
|
||||
{/* Hidden columns */}
|
||||
|
||||
{hiddenGroups.length > 0 &&
|
||||
<div className='octo-board-column narrow'>
|
||||
{hiddenGroups.map((group) => (
|
||||
<KanbanHiddenColumnItem
|
||||
key={group.option.id}
|
||||
group={group}
|
||||
boardTree={boardTree}
|
||||
intl={this.props.intl}
|
||||
readonly={this.props.readonly}
|
||||
onDrop={(card: Card) => this.onDropToColumn(group.option, card)}
|
||||
/>
|
||||
))}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
private propertyNameChanged = async (option: IPropertyOption, text: string): Promise<void> => {
|
||||
const {boardTree} = this.props
|
||||
useCardListener(
|
||||
cards.map((c) => c.id),
|
||||
async (blocks) => {
|
||||
for (const block of blocks) {
|
||||
const cardTree = cardTreeRef.current && cardTreeRef.current[block.parentId]
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const newCardTree = cardTree ? MutableCardTree.incrementalUpdate(cardTree, blocks) : await MutableCardTree.sync(block.parentId)
|
||||
setCardTrees((oldTree) => ({...oldTree, [block.parentId]: newCardTree}))
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
cards.forEach(async (c) => {
|
||||
const newCardTree = await MutableCardTree.sync(c.id)
|
||||
setCardTrees((oldTree) => ({...oldTree, [c.id]: newCardTree}))
|
||||
})
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
const propertyNameChanged = async (option: IPropertyOption, text: string): Promise<void> => {
|
||||
await mutator.changePropertyOptionValue(boardTree, boardTree.groupByProperty!, option, text)
|
||||
}
|
||||
|
||||
private addGroupClicked = async () => {
|
||||
const addGroupClicked = async () => {
|
||||
Utils.log('onAddGroupClicked')
|
||||
|
||||
const {boardTree} = this.props
|
||||
|
||||
const option: IPropertyOption = {
|
||||
id: Utils.createGuid(),
|
||||
value: 'New group',
|
||||
@ -190,9 +87,7 @@ class Kanban extends React.Component<Props, State> {
|
||||
await mutator.insertPropertyOption(boardTree, boardTree.groupByProperty!, option, 'add group')
|
||||
}
|
||||
|
||||
private orderAfterMoveToColumn(cardIds: string[], columnId?: string): string[] {
|
||||
const {boardTree} = this.props
|
||||
const {activeView} = boardTree
|
||||
const orderAfterMoveToColumn = (cardIds: string[], columnId?: string): string[] => {
|
||||
let cardOrder = activeView.cardOrder.slice()
|
||||
const columnGroup = boardTree.visibleGroups.find((g) => g.option.id === columnId)
|
||||
const columnCards = columnGroup?.cards
|
||||
@ -207,8 +102,8 @@ class Kanban extends React.Component<Props, State> {
|
||||
return cardOrder
|
||||
}
|
||||
|
||||
private onDropToColumn = async (option: IPropertyOption, card?: Card, dstOption?: IPropertyOption) => {
|
||||
const {boardTree, selectedCardIds} = this.props
|
||||
const onDropToColumn = async (option: IPropertyOption, card?: Card, dstOption?: IPropertyOption) => {
|
||||
const {selectedCardIds} = props
|
||||
const optionId = option ? option.id : undefined
|
||||
|
||||
let draggedCardIds = selectedCardIds
|
||||
@ -220,7 +115,7 @@ class Kanban extends React.Component<Props, State> {
|
||||
|
||||
if (draggedCardIds.length > 0) {
|
||||
const orderedCards = boardTree.orderedCards()
|
||||
const cardsById: {[key: string]: Card} = orderedCards.reduce((acc: {[key: string]: Card}, c: Card): {[key: string]: Card} => {
|
||||
const cardsById: { [key: string]: Card } = orderedCards.reduce((acc: { [key: string]: Card }, c: Card): { [key: string]: Card } => {
|
||||
acc[c.id] = c
|
||||
return acc
|
||||
}, {})
|
||||
@ -235,7 +130,7 @@ class Kanban extends React.Component<Props, State> {
|
||||
awaits.push(mutator.changePropertyValue(draggedCard, boardTree.groupByProperty!.id, optionId, description))
|
||||
}
|
||||
}
|
||||
const newOrder = this.orderAfterMoveToColumn(draggedCardIds, optionId)
|
||||
const newOrder = orderAfterMoveToColumn(draggedCardIds, optionId)
|
||||
awaits.push(mutator.changeViewCardOrder(boardTree.activeView, newOrder, description))
|
||||
await Promise.all(awaits)
|
||||
})
|
||||
@ -245,7 +140,6 @@ class Kanban extends React.Component<Props, State> {
|
||||
// Move option to new index
|
||||
const visibleOptionIds = boardTree.visibleGroups.map((o) => o.option.id)
|
||||
|
||||
const {activeView} = boardTree
|
||||
const srcIndex = visibleOptionIds.indexOf(dstOption.id)
|
||||
const destIndex = visibleOptionIds.indexOf(option.id)
|
||||
|
||||
@ -256,10 +150,9 @@ class Kanban extends React.Component<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
private onDropToCard = async (srcCard: Card, dstCard: Card) => {
|
||||
const onDropToCard = async (srcCard: Card, dstCard: Card) => {
|
||||
Utils.log(`onDropToCard: ${dstCard.title}`)
|
||||
const {boardTree, selectedCardIds} = this.props
|
||||
const {activeView} = boardTree
|
||||
const {selectedCardIds} = props
|
||||
const optionId = dstCard.properties[activeView.groupById!]
|
||||
|
||||
const draggedCardIds = Array.from(new Set(selectedCardIds).add(srcCard.id))
|
||||
@ -268,7 +161,7 @@ class Kanban extends React.Component<Props, State> {
|
||||
|
||||
// Update dstCard order
|
||||
const orderedCards = boardTree.orderedCards()
|
||||
const cardsById: {[key: string]: Card} = orderedCards.reduce((acc: {[key: string]: Card}, card: Card): {[key: string]: Card} => {
|
||||
const cardsById: { [key: string]: Card } = orderedCards.reduce((acc: { [key: string]: Card }, card: Card): { [key: string]: Card } => {
|
||||
acc[card.id] = card
|
||||
return acc
|
||||
}, {})
|
||||
@ -297,6 +190,115 @@ class Kanban extends React.Component<Props, State> {
|
||||
await mutator.changeViewCardOrder(activeView, cardOrder, description)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='Kanban'>
|
||||
<div
|
||||
className='octo-board-header'
|
||||
id='mainBoardHeader'
|
||||
>
|
||||
{/* Column headers */}
|
||||
|
||||
{visibleGroups.map((group) => (
|
||||
<KanbanColumnHeader
|
||||
key={group.option.id}
|
||||
group={group}
|
||||
boardTree={boardTree}
|
||||
intl={props.intl}
|
||||
addCard={props.addCard}
|
||||
readonly={props.readonly}
|
||||
propertyNameChanged={propertyNameChanged}
|
||||
onDropToColumn={onDropToColumn}
|
||||
/>
|
||||
))}
|
||||
|
||||
{/* Hidden column header */}
|
||||
|
||||
{hiddenGroups.length > 0 &&
|
||||
<div className='octo-board-header-cell narrow'>
|
||||
<FormattedMessage
|
||||
id='BoardComponent.hidden-columns'
|
||||
defaultMessage='Hidden columns'
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
|
||||
{!props.readonly &&
|
||||
<div className='octo-board-header-cell narrow'>
|
||||
<Button
|
||||
onClick={addGroupClicked}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='BoardComponent.add-a-group'
|
||||
defaultMessage='+ Add a group'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* Main content */}
|
||||
|
||||
<div
|
||||
className='octo-board-body'
|
||||
id='mainBoardBody'
|
||||
>
|
||||
{/* Columns */}
|
||||
|
||||
{visibleGroups.map((group) => (
|
||||
<KanbanColumn
|
||||
key={group.option.id || 'empty'}
|
||||
onDrop={(card: Card) => onDropToColumn(group.option, card)}
|
||||
>
|
||||
{group.cards.map((card) => (
|
||||
<KanbanCard
|
||||
card={card}
|
||||
cardTree={cardTrees[card.id]}
|
||||
visiblePropertyTemplates={visiblePropertyTemplates}
|
||||
key={card.id}
|
||||
readonly={props.readonly}
|
||||
isSelected={props.selectedCardIds.includes(card.id)}
|
||||
onClick={(e) => {
|
||||
props.onCardClicked(e, card)
|
||||
}}
|
||||
onDrop={onDropToCard}
|
||||
showCard={props.showCard}
|
||||
isManualSort={isManualSort}
|
||||
/>
|
||||
))}
|
||||
{!props.readonly &&
|
||||
<Button
|
||||
onClick={() => {
|
||||
props.addCard(group.option.id, true)
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id='BoardComponent.new'
|
||||
defaultMessage='+ New'
|
||||
/>
|
||||
</Button>
|
||||
}
|
||||
</KanbanColumn>
|
||||
))}
|
||||
|
||||
{/* Hidden columns */}
|
||||
|
||||
{hiddenGroups.length > 0 &&
|
||||
<div className='octo-board-column narrow'>
|
||||
{hiddenGroups.map((group) => (
|
||||
<KanbanHiddenColumnItem
|
||||
key={group.option.id}
|
||||
group={group}
|
||||
boardTree={boardTree}
|
||||
intl={props.intl}
|
||||
readonly={props.readonly}
|
||||
onDrop={(card: Card) => onDropToColumn(group.option, card)}
|
||||
/>
|
||||
))}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default injectIntl(Kanban)
|
||||
|
@ -16,10 +16,12 @@ import {useSortable} from '../../hooks/sortable'
|
||||
|
||||
import './kanbanCard.scss'
|
||||
import PropertyValueElement from '../propertyValueElement'
|
||||
import {CardTree} from '../../viewModel/cardTree'
|
||||
import Tooltip from '../../widgets/tooltip'
|
||||
|
||||
type Props = {
|
||||
card: Card
|
||||
cardTree?: CardTree
|
||||
visiblePropertyTemplates: IPropertyTemplate[]
|
||||
isSelected: boolean
|
||||
onClick?: (e: React.MouseEvent<HTMLDivElement>) => void
|
||||
@ -94,6 +96,7 @@ const KanbanCard = React.memo((props: Props) => {
|
||||
<PropertyValueElement
|
||||
readOnly={true}
|
||||
card={card}
|
||||
cardTree={props.cardTree}
|
||||
propertyTemplate={template}
|
||||
emptyDisplayValue=''
|
||||
/>
|
||||
|
20
webapp/src/components/properties/createdAt/createdAt.tsx
Normal file
20
webapp/src/components/properties/createdAt/createdAt.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
const moment = require('moment')
|
||||
|
||||
type Props = {
|
||||
createAt: number
|
||||
}
|
||||
|
||||
const CreatedAt = (props: Props): JSX.Element => {
|
||||
return (
|
||||
<div className='CreatedAt octo-propertyvalue'>
|
||||
{moment(props.createAt).format('llll')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreatedAt
|
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/properties/createdBy should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="UserProperty octo-propertyvalue readonly"
|
||||
>
|
||||
username_1
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,33 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
import {IUser, WorkspaceUsersContext} from '../../../user'
|
||||
import {MutableCard} from '../../../blocks/card'
|
||||
|
||||
import CreatedBy from './createdBy'
|
||||
|
||||
describe('components/properties/createdBy', () => {
|
||||
test('should match snapshot', () => {
|
||||
const workspaceUsers = {
|
||||
users: new Array<IUser>(),
|
||||
usersById: new Map<string, IUser>(),
|
||||
}
|
||||
workspaceUsers.usersById.set('user-id-1', {username: 'username_1'} as IUser)
|
||||
|
||||
const card = new MutableCard()
|
||||
card.createdBy = 'user-id-1'
|
||||
|
||||
const component = (
|
||||
<WorkspaceUsersContext.Provider value={workspaceUsers}>
|
||||
<CreatedBy userID='user-id-1'/>
|
||||
</WorkspaceUsersContext.Provider>
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
22
webapp/src/components/properties/createdBy/createdBy.tsx
Normal file
22
webapp/src/components/properties/createdBy/createdBy.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import UserProperty from '../user/user'
|
||||
|
||||
type Props = {
|
||||
userID: string
|
||||
}
|
||||
|
||||
const CreatedBy = (props: Props): JSX.Element => {
|
||||
return (
|
||||
<UserProperty
|
||||
value={props.userID}
|
||||
readonly={true} // created by is an immutable property, so will always be readonly
|
||||
onChange={() => {}} // since created by is immutable, we don't need to handle onChange
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreatedBy
|
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`componnets/properties/lastModifiedAt should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="LastModifiedAt octo-propertyvalue"
|
||||
>
|
||||
Thu, Jun 10, 2021 4:22 PM
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,53 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
import {CardTree, CardTreeContext, MutableCardTree} from '../../../viewModel/cardTree'
|
||||
|
||||
import {MutableCard} from '../../../blocks/card'
|
||||
|
||||
import {MutableBoardTree} from '../../../viewModel/boardTree'
|
||||
import {MutableBoard} from '../../../blocks/board'
|
||||
import {MutableBlock} from '../../../blocks/block'
|
||||
|
||||
import LastModifiedAt from './lastModifiedAt'
|
||||
|
||||
describe('componnets/properties/lastModifiedAt', () => {
|
||||
test('should match snapshot', () => {
|
||||
const cardTree = new MutableCardTree(
|
||||
new MutableCard({
|
||||
updateAt: Date.parse('15 Jun 2021 16:22:00'),
|
||||
}),
|
||||
)
|
||||
|
||||
const card = new MutableCard()
|
||||
card.id = 'card-id-1'
|
||||
card.modifiedBy = 'user-id-1'
|
||||
card.updateAt = Date.parse('10 Jun 2021 16:22:00')
|
||||
|
||||
const boardTree = new MutableBoardTree(new MutableBoard([]))
|
||||
const block = new MutableBlock()
|
||||
block.modifiedBy = 'user-id-1'
|
||||
block.parentId = 'card-id-1'
|
||||
block.type = 'comment'
|
||||
block.updateAt = Date.parse('15 Jun 2021 16:22:00')
|
||||
boardTree.rawBlocks.push(block)
|
||||
|
||||
const cardTrees:{ [key: string]: CardTree | undefined } = {}
|
||||
cardTrees[card.id] = new MutableCardTree(card)
|
||||
|
||||
const component = (
|
||||
<CardTreeContext.Provider value={cardTree}>
|
||||
<LastModifiedAt
|
||||
card={card}
|
||||
cardTree={cardTree}
|
||||
/>
|
||||
</CardTreeContext.Provider>
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import {Card} from '../../../blocks/card'
|
||||
import {CardTree} from '../../../viewModel/cardTree'
|
||||
import {IBlock} from '../../../blocks/block'
|
||||
|
||||
const moment = require('moment')
|
||||
|
||||
type Props = {
|
||||
card: Card,
|
||||
cardTree?: CardTree
|
||||
}
|
||||
|
||||
const LastModifiedAt = (props: Props): JSX.Element => {
|
||||
let latestBlock: IBlock = props.card
|
||||
if (props.cardTree) {
|
||||
const sortedBlocks = props.cardTree.allBlocks.
|
||||
filter((block) => block.parentId === props.card.id || block.id === props.card.id).
|
||||
sort((a, b) => b.updateAt - a.updateAt)
|
||||
|
||||
latestBlock = sortedBlocks.length > 0 ? sortedBlocks[0] : latestBlock
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='LastModifiedAt octo-propertyvalue'>
|
||||
{moment(latestBlock.updateAt).format('llll')}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LastModifiedAt
|
@ -0,0 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/properties/lastModifiedBy should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="LastModifiedBy octo-propertyvalue"
|
||||
>
|
||||
username_1
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,61 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import {render} from '@testing-library/react'
|
||||
|
||||
import {MutableCardTree, CardTreeContext} from '../../../viewModel/cardTree'
|
||||
import {MutableCard} from '../../../blocks/card'
|
||||
import {IUser, WorkspaceUsersContext} from '../../../user'
|
||||
|
||||
import {MutableBoardTree} from '../../../viewModel/boardTree'
|
||||
|
||||
import {MutableBoard} from '../../../blocks/board'
|
||||
|
||||
import {MutableBlock} from '../../../blocks/block'
|
||||
|
||||
import LastModifiedBy from './lastModifiedBy'
|
||||
|
||||
describe('components/properties/lastModifiedBy', () => {
|
||||
test('should match snapshot', () => {
|
||||
const cardTree = new MutableCardTree(
|
||||
new MutableCard({
|
||||
updateAt: Date.parse('15 Jun 2021 16:22:00 +05:30'),
|
||||
modifiedBy: 'user-id-1',
|
||||
}),
|
||||
|
||||
)
|
||||
|
||||
const workspaceUsers = {
|
||||
users: new Array<IUser>(),
|
||||
usersById: new Map<string, IUser>(),
|
||||
}
|
||||
workspaceUsers.usersById.set('user-id-1', {username: 'username_1'} as IUser)
|
||||
|
||||
const card = new MutableCard()
|
||||
card.id = 'card-id-1'
|
||||
card.modifiedBy = 'user-id-1'
|
||||
|
||||
const boardTree = new MutableBoardTree(new MutableBoard([]))
|
||||
const block = new MutableBlock()
|
||||
block.modifiedBy = 'user-id-1'
|
||||
block.parentId = 'card-id-1'
|
||||
block.type = 'comment'
|
||||
boardTree.rawBlocks.push(block)
|
||||
|
||||
const component = (
|
||||
<WorkspaceUsersContext.Provider value={workspaceUsers}>
|
||||
<CardTreeContext.Provider value={cardTree}>
|
||||
<LastModifiedBy
|
||||
card={card}
|
||||
boardTree={boardTree}
|
||||
/>
|
||||
</CardTreeContext.Provider>
|
||||
</WorkspaceUsersContext.Provider>
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
@ -0,0 +1,35 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useContext} from 'react'
|
||||
|
||||
import {WorkspaceUsersContext, WorkspaceUsers} from '../../../user'
|
||||
import {Card} from '../../../blocks/card'
|
||||
import {BoardTree} from '../../../viewModel/boardTree'
|
||||
import {IBlock} from '../../../blocks/block'
|
||||
|
||||
type Props = {
|
||||
card: Card,
|
||||
boardTree?: BoardTree,
|
||||
}
|
||||
|
||||
const LastModifiedBy = (props: Props): JSX.Element => {
|
||||
let latestBlock: IBlock = props.card
|
||||
if (props.boardTree) {
|
||||
const sortedBlocks = props.boardTree?.allBlocks.
|
||||
filter((block) => block.parentId === props.card.id || block.id === props.card.id).
|
||||
sort((a, b) => b.updateAt - a.updateAt)
|
||||
|
||||
latestBlock = sortedBlocks.length > 0 ? sortedBlocks[0] : latestBlock
|
||||
}
|
||||
|
||||
const workspaceUsers = useContext<WorkspaceUsers>(WorkspaceUsersContext)
|
||||
|
||||
return (
|
||||
<div className='LastModifiedBy octo-propertyvalue'>
|
||||
{workspaceUsers?.usersById.get(latestBlock.modifiedBy)?.username || latestBlock.modifiedBy}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default LastModifiedBy
|
@ -79,7 +79,7 @@ exports[`components/properties/user not readonly 1`] = `
|
||||
exports[`components/properties/user readonly view 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="UserProperty octo-propertyvalue"
|
||||
class="UserProperty octo-propertyvalue readonly"
|
||||
>
|
||||
user-id-1
|
||||
</div>
|
||||
|
@ -1,4 +1,10 @@
|
||||
.UserProperty {
|
||||
margin-right: 20px;
|
||||
min-width: 120px;
|
||||
|
||||
&.readonly {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
min-width: unset;
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@
|
||||
import React, {useContext} from 'react'
|
||||
import Select from 'react-select'
|
||||
|
||||
import {IUser, WorkspaceUsersContext, WorkspaceUsersContextData} from '../../../user'
|
||||
import {IUser, WorkspaceUsersContext, WorkspaceUsers} from '../../../user'
|
||||
|
||||
import './user.scss'
|
||||
import {getSelectBaseStyle} from '../../../theme'
|
||||
@ -16,10 +16,10 @@ type Props = {
|
||||
}
|
||||
|
||||
const UserProperty = (props: Props): JSX.Element => {
|
||||
const workspaceUsers = useContext<WorkspaceUsersContextData>(WorkspaceUsersContext)
|
||||
const workspaceUsers = useContext<WorkspaceUsers>(WorkspaceUsersContext)
|
||||
|
||||
if (props.readonly) {
|
||||
return (<div className='UserProperty octo-propertyvalue'>{workspaceUsers.usersById.get(props.value)?.username || props.value}</div>)
|
||||
return (<div className='UserProperty octo-propertyvalue readonly'>{workspaceUsers.usersById.get(props.value)?.username || props.value}</div>)
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -18,14 +18,20 @@ import Label from '../widgets/label'
|
||||
import EditableDayPicker from '../widgets/editableDayPicker'
|
||||
import Switch from '../widgets/switch'
|
||||
|
||||
import {CardTree} from '../viewModel/cardTree'
|
||||
import {UserSettings} from '../userSettings'
|
||||
|
||||
import UserProperty from './properties/user/user'
|
||||
import MultiSelectProperty from './properties/multiSelect'
|
||||
import URLProperty from './properties/link/link'
|
||||
import LastModifiedBy from './properties/lastModifiedBy/lastModifiedBy'
|
||||
import LastModifiedAt from './properties/lastModifiedAt/lastModifiedAt'
|
||||
import CreatedAt from './properties/createdAt/createdAt'
|
||||
import CreatedBy from './properties/createdBy/createdBy'
|
||||
|
||||
type Props = {
|
||||
boardTree?: BoardTree
|
||||
cardTree?: CardTree
|
||||
readOnly: boolean
|
||||
card: Card
|
||||
propertyTemplate: IPropertyTemplate
|
||||
@ -35,7 +41,7 @@ type Props = {
|
||||
const PropertyValueElement = (props:Props): JSX.Element => {
|
||||
const [value, setValue] = useState(props.card.properties[props.propertyTemplate.id])
|
||||
|
||||
const {card, propertyTemplate, readOnly, emptyDisplayValue, boardTree} = props
|
||||
const {card, propertyTemplate, readOnly, emptyDisplayValue, boardTree, cardTree} = props
|
||||
const intl = useIntl()
|
||||
const propertyValue = card.properties[propertyTemplate.id]
|
||||
const displayValue = OctoUtils.propertyDisplayValue(card, propertyValue, propertyTemplate, intl)
|
||||
@ -168,9 +174,7 @@ const PropertyValueElement = (props:Props): JSX.Element => {
|
||||
validator={(newValue) => validateProp(propertyTemplate.type, newValue)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (propertyTemplate.type === 'checkbox') {
|
||||
} else if (propertyTemplate.type === 'checkbox') {
|
||||
return (
|
||||
<Switch
|
||||
isOn={Boolean(propertyValue)}
|
||||
@ -181,6 +185,28 @@ const PropertyValueElement = (props:Props): JSX.Element => {
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
)
|
||||
} else if (propertyTemplate.type === 'createdBy') {
|
||||
return (
|
||||
<CreatedBy userID={card.createdBy}/>
|
||||
)
|
||||
} else if (propertyTemplate.type === 'updatedBy') {
|
||||
return (
|
||||
<LastModifiedBy
|
||||
card={card}
|
||||
boardTree={boardTree}
|
||||
/>
|
||||
)
|
||||
} else if (propertyTemplate.type === 'createdTime') {
|
||||
return (
|
||||
<CreatedAt createAt={card.createAt}/>
|
||||
)
|
||||
} else if (propertyTemplate.type === 'updatedTime') {
|
||||
return (
|
||||
<LastModifiedAt
|
||||
card={card}
|
||||
cardTree={cardTree}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const editableFields: Array<PropertyType> = ['text', 'number', 'email', 'url', 'phone']
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -15,6 +15,10 @@ import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||
import {FetchMock} from '../../test/fetchMock'
|
||||
import {MutableBoardTree} from '../../viewModel/boardTree'
|
||||
|
||||
import {IUser, WorkspaceUsersContext} from '../../user'
|
||||
|
||||
import {Utils} from '../../utils'
|
||||
|
||||
import Table from './table'
|
||||
|
||||
global.fetch = FetchMock.fn
|
||||
@ -48,6 +52,7 @@ describe('components/table/Table', () => {
|
||||
test('should match snapshot', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
@ -55,7 +60,7 @@ describe('components/table/Table', () => {
|
||||
fail('sync')
|
||||
}
|
||||
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
expect(boardTree.cards).toBeDefined()
|
||||
expect(boardTree.cards).toEqual([card])
|
||||
|
||||
@ -80,10 +85,11 @@ describe('components/table/Table', () => {
|
||||
test('should match snapshot, read-only', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const callback = jest.fn()
|
||||
const addCard = jest.fn()
|
||||
@ -108,6 +114,7 @@ describe('components/table/Table', () => {
|
||||
// Sync
|
||||
view.groupById = 'property1'
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
@ -115,7 +122,7 @@ describe('components/table/Table', () => {
|
||||
fail('sync')
|
||||
}
|
||||
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
expect(boardTree.cards).toBeDefined()
|
||||
expect(boardTree.cards).toEqual([card])
|
||||
|
||||
@ -137,3 +144,245 @@ describe('components/table/Table', () => {
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
describe('components/table/Table extended', () => {
|
||||
test('should match snapshot with CreatedBy', async () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
|
||||
const dateCreatedId = Utils.createGuid()
|
||||
board.cardProperties.push({
|
||||
id: dateCreatedId,
|
||||
name: 'Date Created',
|
||||
type: 'createdTime',
|
||||
options: [],
|
||||
})
|
||||
|
||||
const card1 = TestBlockFactory.createCard(board)
|
||||
card1.createAt = Date.parse('15 Jun 2021 16:22:00')
|
||||
|
||||
const card2 = TestBlockFactory.createCard(board)
|
||||
card2.createAt = Date.parse('15 Jun 2021 16:22:00')
|
||||
|
||||
const view = TestBlockFactory.createBoardView(board)
|
||||
view.viewType = 'table'
|
||||
view.groupById = undefined
|
||||
view.visiblePropertyIds = ['property1', 'property2', dateCreatedId]
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, card1, card2, view])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
fail('sync')
|
||||
}
|
||||
|
||||
const callback = jest.fn()
|
||||
const addCard = jest.fn()
|
||||
|
||||
const component = wrapProviders(
|
||||
<Table
|
||||
boardTree={boardTree!}
|
||||
selectedCardIds={[]}
|
||||
readonly={false}
|
||||
cardIdToFocusOnRender=''
|
||||
showCard={callback}
|
||||
addCard={addCard}
|
||||
onCardClicked={jest.fn()}
|
||||
/>,
|
||||
)
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should match snapshot with UpdatedAt', async () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
|
||||
const dateUpdatedId = Utils.createGuid()
|
||||
board.cardProperties.push({
|
||||
id: dateUpdatedId,
|
||||
name: 'Date Updated',
|
||||
type: 'updatedTime',
|
||||
options: [],
|
||||
})
|
||||
|
||||
const card1 = TestBlockFactory.createCard(board)
|
||||
card1.updateAt = Date.parse('20 Jun 2021 12:22:00')
|
||||
|
||||
const card2 = TestBlockFactory.createCard(board)
|
||||
card2.updateAt = Date.parse('20 Jun 2021 12:22:00')
|
||||
|
||||
const card2Comment = TestBlockFactory.createCard(board)
|
||||
card2Comment.parentId = card2.id
|
||||
card2Comment.type = 'comment'
|
||||
card2Comment.updateAt = Date.parse('21 Jun 2021 15:23:00')
|
||||
|
||||
const card2Text = TestBlockFactory.createCard(board)
|
||||
card2Text.parentId = card2.id
|
||||
card2Text.type = 'text'
|
||||
card2Text.updateAt = Date.parse('22 Jun 2021 11:23:00')
|
||||
|
||||
const view = TestBlockFactory.createBoardView(board)
|
||||
view.viewType = 'table'
|
||||
view.groupById = undefined
|
||||
view.visiblePropertyIds = ['property1', 'property2', dateUpdatedId]
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, card1, card2, view, card2Comment, card2Text])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
fail('sync')
|
||||
}
|
||||
|
||||
const callback = jest.fn()
|
||||
const addCard = jest.fn()
|
||||
|
||||
const component = wrapProviders(
|
||||
<Table
|
||||
boardTree={boardTree!}
|
||||
selectedCardIds={[]}
|
||||
readonly={false}
|
||||
cardIdToFocusOnRender=''
|
||||
showCard={callback}
|
||||
addCard={addCard}
|
||||
onCardClicked={jest.fn()}
|
||||
/>,
|
||||
)
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should match snapshot with CreatedBy', async () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
|
||||
const createdById = Utils.createGuid()
|
||||
board.cardProperties.push({
|
||||
id: createdById,
|
||||
name: 'Created By',
|
||||
type: 'createdBy',
|
||||
options: [],
|
||||
})
|
||||
|
||||
const card1 = TestBlockFactory.createCard(board)
|
||||
card1.createdBy = 'user-id-1'
|
||||
|
||||
const card2 = TestBlockFactory.createCard(board)
|
||||
card2.createdBy = 'user-id-2'
|
||||
|
||||
const view = TestBlockFactory.createBoardView(board)
|
||||
view.viewType = 'table'
|
||||
view.groupById = undefined
|
||||
view.visiblePropertyIds = ['property1', 'property2', createdById]
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, card1, card2, view])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
fail('sync')
|
||||
}
|
||||
|
||||
const callback = jest.fn()
|
||||
const addCard = jest.fn()
|
||||
|
||||
const workspaceUsers = {
|
||||
users: new Array<IUser>(),
|
||||
usersById: new Map<string, IUser>(),
|
||||
}
|
||||
workspaceUsers.usersById.set('user-id-1', {username: 'username_1'} as IUser)
|
||||
workspaceUsers.usersById.set('user-id-2', {username: 'username_2'} as IUser)
|
||||
|
||||
const component = wrapProviders(
|
||||
<WorkspaceUsersContext.Provider value={workspaceUsers}>
|
||||
<Table
|
||||
boardTree={boardTree!}
|
||||
selectedCardIds={[]}
|
||||
readonly={false}
|
||||
cardIdToFocusOnRender=''
|
||||
showCard={callback}
|
||||
addCard={addCard}
|
||||
onCardClicked={jest.fn()}
|
||||
/>
|
||||
</WorkspaceUsersContext.Provider>,
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should match snapshot with UpdatedBy', async () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
|
||||
const modifiedById = Utils.createGuid()
|
||||
board.cardProperties.push({
|
||||
id: modifiedById,
|
||||
name: 'Last Modified By',
|
||||
type: 'updatedBy',
|
||||
options: [],
|
||||
})
|
||||
|
||||
const card1 = TestBlockFactory.createCard(board)
|
||||
card1.modifiedBy = 'user-id-1'
|
||||
card1.updateAt = Date.parse('15 Jun 2021 16:22:00')
|
||||
|
||||
const card1Text = TestBlockFactory.createCard(board)
|
||||
card1Text.parentId = card1.id
|
||||
card1Text.type = 'text'
|
||||
card1Text.modifiedBy = 'user-id-4'
|
||||
card1Text.updateAt = Date.parse('16 Jun 2021 16:22:00')
|
||||
|
||||
const card2 = TestBlockFactory.createCard(board)
|
||||
card2.modifiedBy = 'user-id-2'
|
||||
card2.updateAt = Date.parse('15 Jun 2021 16:22:00')
|
||||
|
||||
const card2Comment = TestBlockFactory.createCard(board)
|
||||
card2Comment.parentId = card2.id
|
||||
card2Comment.type = 'comment'
|
||||
card2Comment.modifiedBy = 'user-id-3'
|
||||
card2.updateAt = Date.parse('16 Jun 2021 16:22:00')
|
||||
|
||||
const view = TestBlockFactory.createBoardView(board)
|
||||
view.viewType = 'table'
|
||||
view.groupById = undefined
|
||||
view.visiblePropertyIds = ['property1', 'property2', modifiedById]
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, card1, card2, view, card2Comment, card1Text])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_3'}, {username: 'username_4'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
fail('sync')
|
||||
}
|
||||
|
||||
const callback = jest.fn()
|
||||
const addCard = jest.fn()
|
||||
|
||||
const workspaceUsers = {
|
||||
users: new Array<IUser>(),
|
||||
usersById: new Map<string, IUser>(),
|
||||
}
|
||||
workspaceUsers.usersById.set('user-id-3', {username: 'username_3'} as IUser)
|
||||
workspaceUsers.usersById.set('user-id-4', {username: 'username_4'} as IUser)
|
||||
|
||||
const component = wrapProviders(
|
||||
<WorkspaceUsersContext.Provider value={workspaceUsers}>
|
||||
<Table
|
||||
boardTree={boardTree!}
|
||||
selectedCardIds={[]}
|
||||
readonly={false}
|
||||
cardIdToFocusOnRender=''
|
||||
showCard={callback}
|
||||
addCard={addCard}
|
||||
onCardClicked={jest.fn()}
|
||||
/>
|
||||
</WorkspaceUsersContext.Provider>,
|
||||
)
|
||||
|
||||
const {container} = render(component)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
@ -1,9 +1,9 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
import React, {useRef, useState} from 'react'
|
||||
|
||||
import {FormattedMessage, useIntl} from 'react-intl'
|
||||
import {useDrop, useDragLayer} from 'react-dnd'
|
||||
import {useDragLayer, useDrop} from 'react-dnd'
|
||||
|
||||
import {IPropertyOption, IPropertyTemplate} from '../../blocks/board'
|
||||
import {MutableBoardView} from '../../blocks/boardView'
|
||||
@ -14,9 +14,13 @@ import {Utils} from '../../utils'
|
||||
|
||||
import {BoardTree} from '../../viewModel/boardTree'
|
||||
|
||||
import {OctoUtils} from './../../octoUtils'
|
||||
import {OctoUtils} from '../../octoUtils'
|
||||
|
||||
import './table.scss'
|
||||
import {CardTree, MutableCardTree} from '../../viewModel/cardTree'
|
||||
|
||||
import useCardListener from '../../hooks/cardListener'
|
||||
|
||||
import TableHeader from './tableHeader'
|
||||
import TableRows from './tableRows'
|
||||
import TableGroup from './tableGroup'
|
||||
@ -34,9 +38,38 @@ type Props = {
|
||||
const Table = (props: Props) => {
|
||||
const {boardTree} = props
|
||||
const {board, cards, activeView, visibleGroups} = boardTree
|
||||
const isManualSort = activeView.sortOptions.length < 1
|
||||
const isManualSort = activeView.sortOptions.length === 0
|
||||
const intl = useIntl()
|
||||
|
||||
const [cardTrees, setCardTrees] = useState<{[key: string]: CardTree | undefined}>({a: undefined})
|
||||
const cardTreeRef = useRef<{[key: string]: CardTree | undefined}>()
|
||||
cardTreeRef.current = cardTrees
|
||||
|
||||
useCardListener(
|
||||
cards.map((c) => c.id),
|
||||
async (blocks) => {
|
||||
for (const block of blocks) {
|
||||
const cardTree = cardTreeRef.current && cardTreeRef.current[block.parentId]
|
||||
if (cardTree) {
|
||||
const newCardTree = MutableCardTree.incrementalUpdate(cardTree, blocks)
|
||||
setCardTrees((oldTree) => ({...oldTree, [block.parentId]: newCardTree}))
|
||||
} else {
|
||||
MutableCardTree.sync(block.parentId).
|
||||
then((newCardTree) => {
|
||||
setCardTrees((oldTree) => ({...oldTree, [block.parentId]: newCardTree}))
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
async () => {
|
||||
cards.forEach(async (c) => {
|
||||
const newCardTree = await MutableCardTree.sync(c.id)
|
||||
setCardTrees((oldTree) => ({...oldTree, [c.id]: newCardTree}))
|
||||
})
|
||||
},
|
||||
false,
|
||||
)
|
||||
|
||||
const {offset, resizingColumn} = useDragLayer((monitor) => {
|
||||
if (monitor.getItemType() === 'horizontalGrip') {
|
||||
return {
|
||||
@ -54,7 +87,7 @@ const Table = (props: Props) => {
|
||||
|
||||
const [, drop] = useDrop(() => ({
|
||||
accept: 'horizontalGrip',
|
||||
drop: (item: {id: string}, monitor) => {
|
||||
drop: (item: { id: string }, monitor) => {
|
||||
const columnWidths = {...activeView.columnWidths}
|
||||
const finalOffset = monitor.getDifferenceFromInitialOffset()?.x || 0
|
||||
const newWidth = Math.max(Constants.minColumnWidth, (columnWidths[item.id] || 0) + (finalOffset || 0))
|
||||
@ -103,8 +136,8 @@ const Table = (props: Props) => {
|
||||
})
|
||||
|
||||
const hideGroup = (groupById: string): void => {
|
||||
const index : number = activeView.collapsedOptionIds.indexOf(groupById)
|
||||
const newValue : string[] = [...activeView.collapsedOptionIds]
|
||||
const index: number = activeView.collapsedOptionIds.indexOf(groupById)
|
||||
const newValue: string[] = [...activeView.collapsedOptionIds]
|
||||
if (index > -1) {
|
||||
newValue.splice(index, 1)
|
||||
} else if (groupById !== '') {
|
||||
@ -156,7 +189,7 @@ const Table = (props: Props) => {
|
||||
|
||||
if (activeView.groupById !== undefined) {
|
||||
const orderedCards = boardTree.orderedCards()
|
||||
const cardsById: {[key: string]: Card} = orderedCards.reduce((acc: {[key: string]: Card}, card: Card): {[key: string]: Card} => {
|
||||
const cardsById: { [key: string]: Card } = orderedCards.reduce((acc: { [key: string]: Card }, card: Card): { [key: string]: Card } => {
|
||||
acc[card.id] = card
|
||||
return acc
|
||||
}, {})
|
||||
@ -247,70 +280,70 @@ const Table = (props: Props) => {
|
||||
|
||||
{/* Table header row */}
|
||||
|
||||
{board.cardProperties.
|
||||
filter((template) => activeView.visiblePropertyIds.includes(template.id)).
|
||||
map((template) => {
|
||||
let sorted: 'up' | 'down' | 'none' = 'none'
|
||||
const sortOption = activeView.sortOptions.find((o) => o.propertyId === template.id)
|
||||
if (sortOption) {
|
||||
sorted = sortOption.reversed ? 'down' : 'up'
|
||||
}
|
||||
{board.cardProperties.filter((template) => activeView.visiblePropertyIds.includes(template.id)).map((template) => {
|
||||
let sorted: 'up' | 'down' | 'none' = 'none'
|
||||
const sortOption = activeView.sortOptions.find((o) => o.propertyId === template.id)
|
||||
if (sortOption) {
|
||||
sorted = sortOption.reversed ? 'down' : 'up'
|
||||
}
|
||||
|
||||
return (
|
||||
<TableHeader
|
||||
name={template.name}
|
||||
sorted={sorted}
|
||||
readonly={props.readonly}
|
||||
boardTree={boardTree}
|
||||
template={template}
|
||||
key={template.id}
|
||||
offset={resizingColumn === template.id ? offset : 0}
|
||||
onDrop={onDropToColumn}
|
||||
onAutoSizeColumn={onAutoSizeColumn}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
return (
|
||||
<TableHeader
|
||||
name={template.name}
|
||||
sorted={sorted}
|
||||
readonly={props.readonly}
|
||||
boardTree={boardTree}
|
||||
template={template}
|
||||
key={template.id}
|
||||
offset={resizingColumn === template.id ? offset : 0}
|
||||
onDrop={onDropToColumn}
|
||||
onAutoSizeColumn={onAutoSizeColumn}
|
||||
/>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Table header row */}
|
||||
<div className='table-row-container'>
|
||||
{activeView.groupById &&
|
||||
visibleGroups.map((group) => {
|
||||
return (
|
||||
<TableGroup
|
||||
key={group.option.id}
|
||||
boardTree={boardTree}
|
||||
group={group}
|
||||
readonly={props.readonly}
|
||||
columnRefs={columnRefs}
|
||||
selectedCardIds={props.selectedCardIds}
|
||||
cardIdToFocusOnRender={props.cardIdToFocusOnRender}
|
||||
hideGroup={hideGroup}
|
||||
addCard={props.addCard}
|
||||
showCard={props.showCard}
|
||||
propertyNameChanged={propertyNameChanged}
|
||||
onCardClicked={props.onCardClicked}
|
||||
onDropToGroupHeader={onDropToGroupHeader}
|
||||
onDropToCard={onDropToCard}
|
||||
onDropToGroup={onDropToGroup}
|
||||
/>)
|
||||
})
|
||||
visibleGroups.map((group) => {
|
||||
return (
|
||||
<TableGroup
|
||||
key={group.option.id}
|
||||
boardTree={boardTree}
|
||||
cardTrees={cardTrees}
|
||||
group={group}
|
||||
readonly={props.readonly}
|
||||
columnRefs={columnRefs}
|
||||
selectedCardIds={props.selectedCardIds}
|
||||
cardIdToFocusOnRender={props.cardIdToFocusOnRender}
|
||||
hideGroup={hideGroup}
|
||||
addCard={props.addCard}
|
||||
showCard={props.showCard}
|
||||
propertyNameChanged={propertyNameChanged}
|
||||
onCardClicked={props.onCardClicked}
|
||||
onDropToGroupHeader={onDropToGroupHeader}
|
||||
onDropToCard={onDropToCard}
|
||||
onDropToGroup={onDropToGroup}
|
||||
/>)
|
||||
})
|
||||
}
|
||||
|
||||
{/* No Grouping, Rows, one per card */}
|
||||
{!activeView.groupById &&
|
||||
<TableRows
|
||||
boardTree={boardTree}
|
||||
columnRefs={columnRefs}
|
||||
cards={boardTree.cards}
|
||||
selectedCardIds={props.selectedCardIds}
|
||||
readonly={props.readonly}
|
||||
cardIdToFocusOnRender={props.cardIdToFocusOnRender}
|
||||
showCard={props.showCard}
|
||||
addCard={props.addCard}
|
||||
onCardClicked={props.onCardClicked}
|
||||
onDrop={onDropToCard}
|
||||
/>
|
||||
<TableRows
|
||||
boardTree={boardTree}
|
||||
cardTrees={cardTrees}
|
||||
columnRefs={columnRefs}
|
||||
cards={boardTree.cards}
|
||||
selectedCardIds={props.selectedCardIds}
|
||||
readonly={props.readonly}
|
||||
cardIdToFocusOnRender={props.cardIdToFocusOnRender}
|
||||
showCard={props.showCard}
|
||||
addCard={props.addCard}
|
||||
onCardClicked={props.onCardClicked}
|
||||
onDrop={onDropToCard}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
@ -9,11 +9,14 @@ import {IPropertyOption} from '../../blocks/board'
|
||||
import {Card} from '../../blocks/card'
|
||||
import {BoardTree, BoardTreeGroup} from '../../viewModel/boardTree'
|
||||
|
||||
import {CardTree} from '../../viewModel/cardTree'
|
||||
|
||||
import TableGroupHeaderRow from './tableGroupHeaderRow'
|
||||
import TableRows from './tableRows'
|
||||
|
||||
type Props = {
|
||||
boardTree: BoardTree
|
||||
cardTrees: { [key: string]: CardTree | undefined }
|
||||
group: BoardTreeGroup
|
||||
readonly: boolean
|
||||
columnRefs: Map<string, React.RefObject<HTMLDivElement>>
|
||||
@ -69,6 +72,7 @@ const TableGroup = React.memo((props: Props): JSX.Element => {
|
||||
{(group.cards.length > 0) &&
|
||||
<TableRows
|
||||
boardTree={boardTree}
|
||||
cardTrees={props.cardTrees}
|
||||
columnRefs={props.columnRefs}
|
||||
cards={group.cards}
|
||||
selectedCardIds={props.selectedCardIds}
|
||||
|
@ -66,9 +66,10 @@ const boardTreeGroup = {
|
||||
test('should match snapshot, no groups', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableGroupHeaderRowElement
|
||||
@ -88,10 +89,11 @@ test('should match snapshot, no groups', async () => {
|
||||
test('should match snapshot with Group', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableGroupHeaderRowElement
|
||||
@ -111,10 +113,11 @@ test('should match snapshot with Group', async () => {
|
||||
test('should match snapshot on read only', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableGroupHeaderRowElement
|
||||
@ -134,13 +137,14 @@ test('should match snapshot on read only', async () => {
|
||||
test('should match snapshot, hide group', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const hideGroup = jest.fn()
|
||||
|
||||
view.collapsedOptionIds = [boardTreeGroup.option.id]
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableGroupHeaderRowElement
|
||||
@ -168,12 +172,13 @@ test('should match snapshot, hide group', async () => {
|
||||
test('should match snapshot, add new', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const addNew = jest.fn()
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableGroupHeaderRowElement
|
||||
@ -202,10 +207,11 @@ test('should match snapshot, add new', async () => {
|
||||
test('should match snapshot, edit title', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableGroupHeaderRowElement
|
||||
|
@ -44,10 +44,11 @@ describe('components/table/TableHeaderMenu', () => {
|
||||
test('should match snapshot, title column', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
const onAutoSizeColumn = jest.fn()
|
||||
const component = wrapProviders(
|
||||
<TableHeader
|
||||
|
@ -50,10 +50,11 @@ describe('components/table/TableHeaderMenu', () => {
|
||||
test('should match snapshot, title column', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
const component = wrapIntl(
|
||||
<TableHeaderMenu
|
||||
templateId={Constants.titleColumnId}
|
||||
@ -80,10 +81,11 @@ describe('components/table/TableHeaderMenu', () => {
|
||||
test('should match snapshot, other column', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
const component = wrapIntl(
|
||||
<TableHeaderMenu
|
||||
templateId={'property 1'}
|
||||
|
@ -15,6 +15,8 @@ import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||
import {FetchMock} from '../../test/fetchMock'
|
||||
import {MutableBoardTree} from '../../viewModel/boardTree'
|
||||
|
||||
import {MutableCardTree} from '../../viewModel/cardTree'
|
||||
|
||||
import TableRow from './tableRow'
|
||||
|
||||
global.fetch = FetchMock.fn
|
||||
@ -45,13 +47,18 @@ describe('components/table/TableRow', () => {
|
||||
test('should match snapshot', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const cardTree = new MutableCardTree(card)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableRow
|
||||
boardTree={boardTree!}
|
||||
cardTree={cardTree}
|
||||
card={card}
|
||||
isSelected={false}
|
||||
focusOnMount={false}
|
||||
@ -72,13 +79,18 @@ describe('components/table/TableRow', () => {
|
||||
test('should match snapshot, read-only', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const cardTree = new MutableCardTree(card)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableRow
|
||||
boardTree={boardTree!}
|
||||
cardTree={cardTree}
|
||||
card={card}
|
||||
isSelected={false}
|
||||
focusOnMount={false}
|
||||
@ -99,13 +111,18 @@ describe('components/table/TableRow', () => {
|
||||
test('should match snapshot, isSelected', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const cardTree = new MutableCardTree(card)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableRow
|
||||
boardTree={boardTree!}
|
||||
cardTree={cardTree}
|
||||
card={card}
|
||||
isSelected={true}
|
||||
focusOnMount={false}
|
||||
@ -129,13 +146,17 @@ describe('components/table/TableRow', () => {
|
||||
view.hiddenOptionIds = []
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
const cardTree = new MutableCardTree(card)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableRow
|
||||
boardTree={boardTree!}
|
||||
cardTree={cardTree}
|
||||
card={card}
|
||||
isSelected={false}
|
||||
focusOnMount={false}
|
||||
@ -158,13 +179,16 @@ describe('components/table/TableRow', () => {
|
||||
view.visiblePropertyIds = ['property1', 'property2']
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
const cardTree = new MutableCardTree(card)
|
||||
const component = wrapProviders(
|
||||
<TableRow
|
||||
boardTree={boardTree!}
|
||||
cardTree={cardTree}
|
||||
card={card}
|
||||
isSelected={false}
|
||||
focusOnMount={false}
|
||||
@ -186,13 +210,16 @@ describe('components/table/TableRow', () => {
|
||||
view.visiblePropertyIds = ['property1', 'property2']
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
const cardTree = new MutableCardTree(card)
|
||||
const component = wrapProviders(
|
||||
<TableRow
|
||||
boardTree={boardTree!}
|
||||
cardTree={cardTree}
|
||||
card={card}
|
||||
isSelected={false}
|
||||
focusOnMount={false}
|
||||
|
@ -13,10 +13,12 @@ import {useSortable} from '../../hooks/sortable'
|
||||
|
||||
import PropertyValueElement from '../propertyValueElement'
|
||||
import './tableRow.scss'
|
||||
import {CardTree} from '../../viewModel/cardTree'
|
||||
|
||||
type Props = {
|
||||
boardTree: BoardTree
|
||||
card: Card
|
||||
cardTree?: CardTree
|
||||
isSelected: boolean
|
||||
focusOnMount: boolean
|
||||
onSaveWithEnter: () => void
|
||||
@ -36,7 +38,7 @@ const TableRow = React.memo((props: Props) => {
|
||||
const titleRef = useRef<{focus(selectAll?: boolean): void}>(null)
|
||||
const [title, setTitle] = useState(props.card.title)
|
||||
const {card} = props
|
||||
const isManualSort = activeView.sortOptions.length < 1
|
||||
const isManualSort = activeView.sortOptions.length === 0
|
||||
const isGrouped = Boolean(activeView.groupById)
|
||||
const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly && (isManualSort || isGrouped), props.onDrop)
|
||||
|
||||
@ -131,6 +133,7 @@ const TableRow = React.memo((props: Props) => {
|
||||
<PropertyValueElement
|
||||
readOnly={props.readonly}
|
||||
card={card}
|
||||
cardTree={props.cardTree}
|
||||
boardTree={boardTree}
|
||||
propertyTemplate={template}
|
||||
emptyDisplayValue=''
|
||||
|
@ -19,6 +19,8 @@ import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||
import {FetchMock} from '../../test/fetchMock'
|
||||
import {MutableBoardTree} from '../../viewModel/boardTree'
|
||||
|
||||
import {CardTree, MutableCardTree} from '../../viewModel/cardTree'
|
||||
|
||||
import TableRows from './tableRows'
|
||||
|
||||
global.fetch = FetchMock.fn
|
||||
@ -49,17 +51,22 @@ describe('components/table/TableRows', () => {
|
||||
test('should match snapshot, fire events', async () => {
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).toBeDefined()
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
|
||||
const callback = jest.fn()
|
||||
const addCard = jest.fn()
|
||||
|
||||
const cardTrees:{ [key: string]: CardTree | undefined } = {}
|
||||
cardTrees[card.id] = new MutableCardTree(card)
|
||||
|
||||
const component = wrapProviders(
|
||||
<TableRows
|
||||
boardTree={boardTree!}
|
||||
cardTrees={cardTrees}
|
||||
columnRefs={new Map()}
|
||||
cards={[card]}
|
||||
selectedCardIds={[]}
|
||||
|
@ -8,10 +8,13 @@ import {Card} from '../../blocks/card'
|
||||
import {BoardTree} from '../../viewModel/boardTree'
|
||||
|
||||
import './table.scss'
|
||||
import {CardTree} from '../../viewModel/cardTree'
|
||||
|
||||
import TableRow from './tableRow'
|
||||
|
||||
type Props = {
|
||||
boardTree: BoardTree
|
||||
cardTrees: { [key: string]: CardTree | undefined }
|
||||
columnRefs: Map<string, React.RefObject<HTMLDivElement>>
|
||||
cards: readonly Card[]
|
||||
selectedCardIds: string[]
|
||||
@ -48,6 +51,7 @@ const TableRows = (props: Props) => {
|
||||
key={card.id + card.updateAt}
|
||||
boardTree={boardTree}
|
||||
card={card}
|
||||
cardTree={props.cardTrees[card.id]}
|
||||
isSelected={props.selectedCardIds.includes(card.id)}
|
||||
focusOnMount={props.cardIdToFocusOnRender === card.id}
|
||||
onSaveWithEnter={() => {
|
||||
|
@ -7,7 +7,7 @@ import octoClient from '../octoClient'
|
||||
import {OctoListener} from '../octoListener'
|
||||
import {Utils} from '../utils'
|
||||
|
||||
export default function useCardListener(cardIds: string[], onChange: (blocks: IBlock[]) => void, onReconnect: () => void): void {
|
||||
export default function useCardListener(cardIds: string[], onChange: (blocks: IBlock[]) => void, onReconnect: () => void, initialCall = true): void {
|
||||
let cardListener: OctoListener | null = null
|
||||
|
||||
const deleteListener = () => {
|
||||
@ -28,7 +28,10 @@ export default function useCardListener(cardIds: string[], onChange: (blocks: IB
|
||||
}
|
||||
|
||||
const createCardTreeAndSync = async () => {
|
||||
onReconnect()
|
||||
if (initialCall) {
|
||||
onReconnect()
|
||||
}
|
||||
|
||||
createListener()
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
import {IBlock, IMutableBlock} from './blocks/block'
|
||||
import {ISharing} from './blocks/sharing'
|
||||
import {IWorkspace} from './blocks/workspace'
|
||||
import {IUser} from './user'
|
||||
import {IUser, WorkspaceUsers} from './user'
|
||||
import {Utils} from './utils'
|
||||
|
||||
//
|
||||
@ -360,13 +360,27 @@ class OctoClient {
|
||||
return URL.createObjectURL(blob)
|
||||
}
|
||||
|
||||
async getWorkspaceUsers(): Promise<Array<IUser>> {
|
||||
async getWorkspaceUsers(): Promise<WorkspaceUsers> {
|
||||
const path = this.workspacePath() + '/users'
|
||||
const response = await fetch(this.serverUrl + path, {headers: this.headers()})
|
||||
if (response.status !== 200) {
|
||||
return []
|
||||
return {
|
||||
users: new Array<IUser>(),
|
||||
usersById: new Map<string, IUser>(),
|
||||
}
|
||||
}
|
||||
return (await this.getJson(response, [])) as IUser[]
|
||||
const workspaceUsers = (await this.getJson(response, [])) as IUser[]
|
||||
return {
|
||||
users: workspaceUsers,
|
||||
usersById: this.getIdToWorkspaceUsers(workspaceUsers),
|
||||
}
|
||||
}
|
||||
|
||||
private getIdToWorkspaceUsers(users: Array<IUser>): Map<string, IUser> {
|
||||
return users.reduce((acc: Map<string, IUser>, user: IUser) => {
|
||||
acc.set(user.id, user)
|
||||
return acc
|
||||
}, new Map())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ import {Utils} from '../utils'
|
||||
import {BoardTree, MutableBoardTree} from '../viewModel/boardTree'
|
||||
import {MutableWorkspaceTree, WorkspaceTree} from '../viewModel/workspaceTree'
|
||||
import './boardPage.scss'
|
||||
import {IUser, WorkspaceUsersContext, WorkspaceUsersContextData} from '../user'
|
||||
import {IUser, WorkspaceUsersContext, WorkspaceUsers} from '../user'
|
||||
|
||||
type Props = RouteComponentProps<{workspaceId?: string}> & {
|
||||
readonly?: boolean
|
||||
@ -32,7 +32,7 @@ type State = {
|
||||
syncFailed?: boolean
|
||||
websocketClosedTimeOutId?: ReturnType<typeof setTimeout>
|
||||
websocketClosed?: boolean
|
||||
workspaceUsers: WorkspaceUsersContextData
|
||||
workspaceUsers: WorkspaceUsers
|
||||
}
|
||||
|
||||
class BoardPage extends React.Component<Props, State> {
|
||||
@ -107,10 +107,7 @@ class BoardPage extends React.Component<Props, State> {
|
||||
|
||||
// storing workspaceUsersById in state to avoid re-computation in each render cycle
|
||||
this.setState({
|
||||
workspaceUsers: {
|
||||
users: workspaceUsers,
|
||||
usersById: this.getIdToWorkspaceUsers(workspaceUsers),
|
||||
},
|
||||
workspaceUsers,
|
||||
})
|
||||
}
|
||||
|
||||
@ -349,7 +346,7 @@ class BoardPage extends React.Component<Props, State> {
|
||||
|
||||
let newBoardTree: BoardTree | undefined
|
||||
if (boardTree) {
|
||||
newBoardTree = MutableBoardTree.incrementalUpdate(boardTree, blocks)
|
||||
newBoardTree = await MutableBoardTree.incrementalUpdate(boardTree, blocks)
|
||||
} else if (this.state.boardId) {
|
||||
// Corner case: When the page is viewing a deleted board, that is subsequently un-deleted on another client
|
||||
newBoardTree = await MutableBoardTree.sync(this.state.boardId, this.state.viewId)
|
||||
@ -385,11 +382,11 @@ class BoardPage extends React.Component<Props, State> {
|
||||
this.attachToBoard(boardId)
|
||||
}
|
||||
|
||||
showView(viewId: string, boardId: string = this.state.boardId): void {
|
||||
async showView(viewId: string, boardId: string = this.state.boardId): Promise<void> {
|
||||
localStorage.setItem('lastViewId', viewId)
|
||||
|
||||
if (this.state.boardTree && this.state.boardId === boardId) {
|
||||
const newBoardTree = this.state.boardTree.copyWithView(viewId)
|
||||
const newBoardTree = await this.state.boardTree.copyWithView(viewId)
|
||||
this.setState({boardTree: newBoardTree, viewId})
|
||||
} else {
|
||||
this.attachToBoard(boardId, viewId)
|
||||
@ -401,13 +398,13 @@ class BoardPage extends React.Component<Props, State> {
|
||||
window.history.pushState({path: newUrl.toString()}, '', newUrl.toString())
|
||||
}
|
||||
|
||||
setSearchText(text?: string): void {
|
||||
async setSearchText(text?: string): Promise<void> {
|
||||
if (!this.state.boardTree) {
|
||||
Utils.assertFailure('setSearchText: boardTree')
|
||||
return
|
||||
}
|
||||
|
||||
const newBoardTree = this.state.boardTree.copyWithSearchText(text)
|
||||
const newBoardTree = await this.state.boardTree.copyWithSearchText(text)
|
||||
this.setState({boardTree: newBoardTree})
|
||||
}
|
||||
}
|
||||
|
@ -19,9 +19,9 @@ interface IUser {
|
||||
updateAt: number,
|
||||
}
|
||||
|
||||
type WorkspaceUsersContextData = {
|
||||
type WorkspaceUsers = {
|
||||
users: Array<IUser>
|
||||
usersById: Map<string, IUser>
|
||||
}
|
||||
|
||||
export {IUser, UserContext, WorkspaceUsersContext, WorkspaceUsersContextData}
|
||||
export {IUser, UserContext, WorkspaceUsersContext, WorkspaceUsers}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// Disable console log
|
||||
|
||||
console.log = jest.fn()
|
||||
console.error = jest.fn()
|
||||
|
||||
@ -37,17 +38,19 @@ test('BoardTree', async () => {
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board, view, view2, card, cardTemplate])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
boardTree = await MutableBoardTree.sync(board.id, view.id)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
fail('sync')
|
||||
}
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
expect(FetchMock.fn).toBeCalledTimes(3)
|
||||
expect(boardTree.board).toEqual(board)
|
||||
expect(boardTree.views).toEqual([view, view2])
|
||||
expect(boardTree.allCards).toEqual([card])
|
||||
expect(boardTree.orderedCards()).toEqual([card])
|
||||
expect(boardTree.cardTemplates).toEqual([cardTemplate])
|
||||
console.log(boardTree.rawBlocks)
|
||||
expect(boardTree.allBlocks).toEqual([board, view, view2, card, cardTemplate])
|
||||
|
||||
// Group / filter with sort
|
||||
@ -55,12 +58,12 @@ test('BoardTree', async () => {
|
||||
expect(boardTree.cards).toEqual([card])
|
||||
|
||||
// Group / filter without sort
|
||||
boardTree = boardTree.copyWithView(view2.id)
|
||||
boardTree = await boardTree.copyWithView(view2.id)
|
||||
expect(boardTree.activeView).toEqual(view2)
|
||||
expect(boardTree.cards).toEqual([card])
|
||||
|
||||
// Invalid view, defaults to first view
|
||||
boardTree = boardTree.copyWithView('invalid id')
|
||||
boardTree = await boardTree.copyWithView('invalid id')
|
||||
expect(boardTree.activeView).toEqual(view)
|
||||
|
||||
// Incremental update
|
||||
@ -70,7 +73,7 @@ test('BoardTree', async () => {
|
||||
cardTemplate2.isTemplate = true
|
||||
|
||||
let originalBoardTree = boardTree
|
||||
boardTree = MutableBoardTree.incrementalUpdate(boardTree, [view3, card2, cardTemplate2])
|
||||
boardTree = await MutableBoardTree.incrementalUpdate(boardTree, [view3, card2, cardTemplate2])
|
||||
expect(boardTree).not.toBe(originalBoardTree)
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
@ -82,7 +85,7 @@ test('BoardTree', async () => {
|
||||
|
||||
// Group / filter with sort
|
||||
originalBoardTree = boardTree
|
||||
boardTree = boardTree.copyWithView(view.id)
|
||||
boardTree = await boardTree.copyWithView(view.id)
|
||||
expect(boardTree).not.toBe(originalBoardTree)
|
||||
expect(boardTree.activeView).toEqual(view)
|
||||
expect(boardTree.cards).toEqual([card, card2])
|
||||
@ -90,7 +93,7 @@ test('BoardTree', async () => {
|
||||
|
||||
// Group / filter without sort
|
||||
originalBoardTree = boardTree
|
||||
boardTree = boardTree.copyWithView(view2.id)
|
||||
boardTree = await boardTree.copyWithView(view2.id)
|
||||
expect(boardTree).not.toBe(originalBoardTree)
|
||||
expect(boardTree.activeView).toEqual(view2)
|
||||
expect(boardTree.cards).toEqual([card, card2])
|
||||
@ -99,7 +102,7 @@ test('BoardTree', async () => {
|
||||
const anotherBoard = TestBlockFactory.createBoard()
|
||||
const card4 = TestBlockFactory.createCard(anotherBoard)
|
||||
originalBoardTree = boardTree
|
||||
boardTree = MutableBoardTree.incrementalUpdate(boardTree, [anotherBoard, card4])
|
||||
boardTree = await MutableBoardTree.incrementalUpdate(boardTree, [anotherBoard, card4])
|
||||
expect(boardTree).toBe(originalBoardTree) // Expect same value on no change
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
@ -117,7 +120,7 @@ test('BoardTree', async () => {
|
||||
// Search text
|
||||
const searchText = 'search text'
|
||||
expect(boardTree.getSearchText()).toBeUndefined()
|
||||
boardTree = boardTree.copyWithSearchText(searchText)
|
||||
boardTree = await boardTree.copyWithSearchText(searchText)
|
||||
expect(boardTree.getSearchText()).toBe(searchText)
|
||||
})
|
||||
|
||||
@ -127,13 +130,15 @@ test('BoardTree: defaults', async () => {
|
||||
|
||||
// Sync
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([board])))
|
||||
FetchMock.fn.mockReturnValue(FetchMock.jsonResponse(JSON.stringify([{username: 'username_1'}, {username: 'username_2'}])))
|
||||
|
||||
const boardTree = await MutableBoardTree.sync(board.id, 'noView')
|
||||
expect(boardTree).not.toBeUndefined()
|
||||
if (!boardTree) {
|
||||
fail('sync')
|
||||
}
|
||||
|
||||
expect(FetchMock.fn).toBeCalledTimes(1)
|
||||
expect(FetchMock.fn).toBeCalledTimes(2)
|
||||
expect(boardTree.board).not.toBeUndefined()
|
||||
expect(boardTree.activeView).not.toBeUndefined()
|
||||
expect(boardTree.views.length).toEqual(1)
|
||||
|
@ -9,6 +9,7 @@ import {Constants} from '../constants'
|
||||
import octoClient from '../octoClient'
|
||||
import {OctoUtils} from '../octoUtils'
|
||||
import {Utils} from '../utils'
|
||||
import {IUser, WorkspaceUsers} from '../user'
|
||||
|
||||
type Group = {
|
||||
option: IPropertyOption
|
||||
@ -29,11 +30,13 @@ interface BoardTree {
|
||||
readonly activeView: BoardView
|
||||
readonly groupByProperty?: IPropertyTemplate
|
||||
|
||||
readonly rawBlocks: IBlock[]
|
||||
|
||||
getSearchText(): string | undefined
|
||||
orderedCards(): Card[]
|
||||
|
||||
copyWithView(viewId: string): BoardTree
|
||||
copyWithSearchText(searchText?: string): BoardTree
|
||||
copyWithView(viewId: string): Promise<BoardTree>
|
||||
copyWithSearchText(searchText?: string): Promise<BoardTree>
|
||||
}
|
||||
|
||||
class MutableBoardTree implements BoardTree {
|
||||
@ -50,33 +53,44 @@ class MutableBoardTree implements BoardTree {
|
||||
|
||||
private searchText?: string
|
||||
allCards: MutableCard[] = []
|
||||
rawBlocks: IBlock[] = []
|
||||
|
||||
workspaceUsers: WorkspaceUsers = {
|
||||
users: new Array<IUser>(),
|
||||
usersById: new Map<string, IUser>(),
|
||||
}
|
||||
|
||||
get allBlocks(): IBlock[] {
|
||||
return [this.board, ...this.views, ...this.allCards, ...this.cardTemplates]
|
||||
return [this.board, ...this.views, ...this.allCards, ...this.cardTemplates, ...this.rawBlocks]
|
||||
}
|
||||
|
||||
constructor(board: MutableBoard) {
|
||||
this.board = board
|
||||
}
|
||||
|
||||
public async initWorkSpaceUsers() {
|
||||
this.workspaceUsers = await octoClient.getWorkspaceUsers()
|
||||
}
|
||||
|
||||
// Factory methods
|
||||
|
||||
static async sync(boardId: string, viewId: string): Promise<BoardTree | undefined> {
|
||||
const rawBlocks = await octoClient.getSubtree(boardId)
|
||||
const newBoardTree = this.buildTree(boardId, rawBlocks)
|
||||
const rawBlocks = await octoClient.getSubtree(boardId, 3)
|
||||
const newBoardTree = await this.buildTree(boardId, rawBlocks)
|
||||
if (newBoardTree) {
|
||||
newBoardTree.setActiveView(viewId)
|
||||
}
|
||||
return newBoardTree
|
||||
}
|
||||
|
||||
static incrementalUpdate(boardTree: BoardTree, updatedBlocks: IBlock[]): BoardTree | undefined {
|
||||
static async incrementalUpdate(boardTree: BoardTree, updatedBlocks: IBlock[]): Promise<BoardTree | undefined> {
|
||||
const relevantBlocks = updatedBlocks.filter((block) => block.deleteAt !== 0 || block.id === boardTree.board.id || block.parentId === boardTree.board.id)
|
||||
if (relevantBlocks.length < 1) {
|
||||
// No change
|
||||
return boardTree
|
||||
}
|
||||
const rawBlocks = OctoUtils.mergeBlocks(boardTree.allBlocks, relevantBlocks)
|
||||
const newBoardTree = this.buildTree(boardTree.board.id, rawBlocks)
|
||||
const newBoardTree = await this.buildTree(boardTree.board.id, rawBlocks)
|
||||
newBoardTree?.setSearchText(boardTree.getSearchText())
|
||||
if (newBoardTree && boardTree.activeView) {
|
||||
newBoardTree.setActiveView(boardTree.activeView.id)
|
||||
@ -84,19 +98,21 @@ class MutableBoardTree implements BoardTree {
|
||||
return newBoardTree
|
||||
}
|
||||
|
||||
private static buildTree(boardId: string, sourceBlocks: readonly IBlock[]): MutableBoardTree | undefined {
|
||||
private static async buildTree(boardId: string, sourceBlocks: readonly IBlock[]): Promise<MutableBoardTree | undefined> {
|
||||
const blocks = OctoUtils.hydrateBlocks(sourceBlocks)
|
||||
const board = blocks.find((block) => block.type === 'board' && block.id === boardId) as MutableBoard
|
||||
if (!board) {
|
||||
return undefined
|
||||
}
|
||||
const boardTree = new MutableBoardTree(board)
|
||||
await boardTree.initWorkSpaceUsers()
|
||||
boardTree.views = blocks.filter((block) => block.type === 'view').
|
||||
sort((a, b) => a.title.localeCompare(b.title)) as MutableBoardView[]
|
||||
boardTree.allCards = blocks.filter((block) => block.type === 'card' && !(block as Card).isTemplate) as MutableCard[]
|
||||
boardTree.cardTemplates = blocks.filter((block) => block.type === 'card' && (block as Card).isTemplate).
|
||||
sort((a, b) => a.title.localeCompare(b.title)) as MutableCard[]
|
||||
boardTree.cards = []
|
||||
boardTree.rawBlocks = blocks.filter((block) => block.type !== 'view' && block.type !== 'card' && block.id !== boardId)
|
||||
|
||||
boardTree.ensureMinimumSchema()
|
||||
return boardTree
|
||||
@ -384,6 +400,15 @@ class MutableBoardTree implements BoardTree {
|
||||
|
||||
let aValue = a.properties[sortPropertyId] || ''
|
||||
let bValue = b.properties[sortPropertyId] || ''
|
||||
|
||||
if (template.type === 'createdBy') {
|
||||
aValue = this.workspaceUsers.usersById.get(a.createdBy)?.username || ''
|
||||
bValue = this.workspaceUsers.usersById.get(b.createdBy)?.username || ''
|
||||
} else if (template.type === 'updatedBy') {
|
||||
aValue = this.workspaceUsers.usersById.get(a.modifiedBy)?.username || ''
|
||||
bValue = this.workspaceUsers.usersById.get(b.modifiedBy)?.username || ''
|
||||
}
|
||||
|
||||
let result = 0
|
||||
if (template.type === 'number' || template.type === 'date') {
|
||||
// Always put empty values at the bottom
|
||||
@ -448,18 +473,19 @@ class MutableBoardTree implements BoardTree {
|
||||
return cards
|
||||
}
|
||||
|
||||
private mutableCopy(): MutableBoardTree {
|
||||
return MutableBoardTree.buildTree(this.board.id, this.allBlocks)!
|
||||
private async mutableCopy(): Promise<BoardTree> {
|
||||
const x = await MutableBoardTree.buildTree(this.board.id, this.allBlocks)
|
||||
return x!
|
||||
}
|
||||
|
||||
copyWithView(viewId: string): BoardTree {
|
||||
const boardTree = this.mutableCopy()
|
||||
async copyWithView(viewId: string): Promise<BoardTree> {
|
||||
const boardTree = await this.mutableCopy() as MutableBoardTree
|
||||
boardTree.setActiveView(viewId)
|
||||
return boardTree
|
||||
}
|
||||
|
||||
copyWithSearchText(searchText?: string): BoardTree {
|
||||
const boardTree = this.mutableCopy()
|
||||
async copyWithSearchText(searchText?: string): Promise<BoardTree> {
|
||||
const boardTree = await this.mutableCopy() as MutableBoardTree
|
||||
if (this.activeView) {
|
||||
boardTree.setActiveView(this.activeView.id)
|
||||
}
|
||||
|
@ -79,9 +79,8 @@ test('CardTree', async () => {
|
||||
fail('incrementalUpdate')
|
||||
}
|
||||
|
||||
// Copy
|
||||
// const cardTree2 = cardTree.mutableCopy()
|
||||
// expect(cardTree2.card).toEqual(cardTree.card)
|
||||
// expect(cardTree2.comments).toEqual(cardTree.comments)
|
||||
// expect(cardTree2.contents).toEqual(cardTree.contents)
|
||||
FetchMock.fn.mockReturnValueOnce(FetchMock.jsonResponse(JSON.stringify([card, comment, text, image, divider])))
|
||||
cardTree = await MutableCardTree.sync(card.id)
|
||||
expect(cardTree).not.toBeUndefined()
|
||||
expect(cardTree?.latestBlock.type).toEqual('divider')
|
||||
})
|
||||
|
@ -1,5 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React from 'react'
|
||||
|
||||
import {ContentBlockTypes, contentBlockTypes, IBlock} from '../blocks/block'
|
||||
import {Card, MutableCard} from '../blocks/card'
|
||||
import {CommentBlock} from '../blocks/commentBlock'
|
||||
@ -12,12 +14,14 @@ interface CardTree {
|
||||
readonly comments: readonly CommentBlock[]
|
||||
readonly contents: readonly IContentBlock[]
|
||||
readonly allBlocks: readonly IBlock[]
|
||||
readonly latestBlock: IBlock
|
||||
}
|
||||
|
||||
class MutableCardTree implements CardTree {
|
||||
card: MutableCard
|
||||
comments: CommentBlock[] = []
|
||||
contents: IContentBlock[] = []
|
||||
latestBlock: IBlock
|
||||
|
||||
get allBlocks(): IBlock[] {
|
||||
return [this.card, ...this.comments, ...this.contents]
|
||||
@ -25,6 +29,7 @@ class MutableCardTree implements CardTree {
|
||||
|
||||
constructor(card: MutableCard) {
|
||||
this.card = card
|
||||
this.latestBlock = card
|
||||
}
|
||||
|
||||
// Factory methods
|
||||
@ -59,12 +64,24 @@ class MutableCardTree implements CardTree {
|
||||
const contentBlocks = blocks.filter((block) => contentBlockTypes.includes(block.type as ContentBlockTypes)) as IContentBlock[]
|
||||
cardTree.contents = OctoUtils.getBlockOrder(card.contentOrder, contentBlocks)
|
||||
|
||||
cardTree.latestBlock = MutableCardTree.getMostRecentBlock(cardTree)
|
||||
|
||||
return cardTree
|
||||
}
|
||||
|
||||
// private mutableCopy(): MutableCardTree {
|
||||
// return MutableCardTree.buildTree(this.card.id, this.allBlocks)!
|
||||
// }
|
||||
public static getMostRecentBlock(cardTree: CardTree): IBlock {
|
||||
let latestBlock: IBlock = cardTree.card
|
||||
cardTree.allBlocks.forEach((block) => {
|
||||
if (latestBlock) {
|
||||
latestBlock = block.updateAt > latestBlock.updateAt ? block : latestBlock
|
||||
} else {
|
||||
latestBlock = block
|
||||
}
|
||||
})
|
||||
return latestBlock
|
||||
}
|
||||
}
|
||||
|
||||
export {MutableCardTree, CardTree}
|
||||
const CardTreeContext = React.createContext<CardTree | undefined>(undefined)
|
||||
|
||||
export {MutableCardTree, CardTree, CardTreeContext}
|
||||
|
@ -241,7 +241,37 @@ exports[`widgets/PropertyMenu should match snapshot 1`] = `
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Updated Time
|
||||
Created By
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="MenuOption TextOption menu-option"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Last Updated Time
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="MenuOption TextOption menu-option"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Last Updated By
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
|
@ -1,6 +1,5 @@
|
||||
.Editable {
|
||||
cursor: text;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
border: 1px solid transparent;
|
||||
@ -19,5 +18,6 @@
|
||||
}
|
||||
&.readonly {
|
||||
background-color: transparent;
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
@ -31,8 +31,8 @@ function typeDisplayName(intl: IntlShape, type: PropertyType): string {
|
||||
case 'phone': return intl.formatMessage({id: 'PropertyType.Phone', defaultMessage: 'Phone'})
|
||||
case 'createdTime': return intl.formatMessage({id: 'PropertyType.CreatedTime', defaultMessage: 'Created Time'})
|
||||
case 'createdBy': return intl.formatMessage({id: 'PropertyType.CreatedBy', defaultMessage: 'Created By'})
|
||||
case 'updatedTime': return intl.formatMessage({id: 'PropertyType.UpdatedTime', defaultMessage: 'Updated Time'})
|
||||
case 'updatedBy': return intl.formatMessage({id: 'PropertyType.UpdatedBy', defaultMessage: 'Updated By'})
|
||||
case 'updatedTime': return intl.formatMessage({id: 'PropertyType.UpdatedTime', defaultMessage: 'Last Updated Time'})
|
||||
case 'updatedBy': return intl.formatMessage({id: 'PropertyType.UpdatedBy', defaultMessage: 'Last Updated By'})
|
||||
case 'date': return intl.formatMessage({id: 'PropertyType.Date', defaultMessage: 'Date'})
|
||||
default: {
|
||||
Utils.assertFailure(`typeDisplayName, unhandled type: ${type}`)
|
||||
@ -61,6 +61,23 @@ const PropertyMenu = React.memo((props: Props) => {
|
||||
nameTextbox.current?.setSelectionRange(0, name.length)
|
||||
}, [])
|
||||
|
||||
const propertyTypes = [
|
||||
{type: 'text'},
|
||||
{type: 'number'},
|
||||
{type: 'email'},
|
||||
{type: 'phone'},
|
||||
{type: 'url'},
|
||||
{type: 'select'},
|
||||
{type: 'multiSelect'},
|
||||
{type: 'date'},
|
||||
{type: 'person'},
|
||||
{type: 'checkbox'},
|
||||
{type: 'createdTime'},
|
||||
{type: 'createdBy'},
|
||||
{type: 'updatedTime'},
|
||||
{type: 'updatedBy'},
|
||||
]
|
||||
|
||||
return (
|
||||
<Menu>
|
||||
<input
|
||||
@ -91,66 +108,16 @@ const PropertyMenu = React.memo((props: Props) => {
|
||||
|
||||
<Menu.Separator/>
|
||||
|
||||
<Menu.Text
|
||||
id='text'
|
||||
name={typeDisplayName(intl, 'text')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('text')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='number'
|
||||
name={typeDisplayName(intl, 'number')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('number')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='email'
|
||||
name={typeDisplayName(intl, 'email')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('email')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='phone'
|
||||
name={typeDisplayName(intl, 'phone')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('phone')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='url'
|
||||
name={typeDisplayName(intl, 'url')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('url')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='select'
|
||||
name={typeDisplayName(intl, 'select')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('select')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='multiSelect'
|
||||
name={typeDisplayName(intl, 'multiSelect')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('multiSelect')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='date'
|
||||
name={typeDisplayName(intl, 'date')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('date')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='person'
|
||||
name={typeDisplayName(intl, 'person')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('person')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='checkbox'
|
||||
name={typeDisplayName(intl, 'checkbox')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('checkbox')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='createdTime'
|
||||
name={typeDisplayName(intl, 'createdTime')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('createdTime')()}
|
||||
/>
|
||||
<Menu.Text
|
||||
id='updatedTime'
|
||||
name={typeDisplayName(intl, 'updatedTime')}
|
||||
onClick={() => debouncedOnTypeAndNameChanged('updatedTime')()}
|
||||
/>
|
||||
{
|
||||
propertyTypes.map((propertyType) => (
|
||||
<Menu.Text
|
||||
key={propertyType.type}
|
||||
id={propertyType.type}
|
||||
name={typeDisplayName(intl, propertyType.type as PropertyType)}
|
||||
onClick={() => debouncedOnTypeAndNameChanged(propertyType.type as PropertyType)()}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</Menu.SubMenu>
|
||||
<Menu.Text
|
||||
id='delete'
|
||||
|
Loading…
x
Reference in New Issue
Block a user