snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 1s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
267
server/public/model/view.go
Normal file
267
server/public/model/view.go
Normal file
@@ -0,0 +1,267 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"maps"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type ViewType string
|
||||
|
||||
const (
|
||||
ViewTypeKanban ViewType = "kanban"
|
||||
|
||||
ViewTitleMaxRunes = 256
|
||||
ViewDescriptionMaxRunes = 1024
|
||||
MaxViewsPerChannel = 50
|
||||
|
||||
BoardsPropertyGroupName = "boards"
|
||||
BoardsPropertyFieldNameBoard = "board"
|
||||
BoardsPropertyFieldAssignee = "assignee"
|
||||
BoardsPropertyFieldStatus = "status"
|
||||
|
||||
BoardsStatusOptionTodo = "Todo"
|
||||
BoardsStatusOptionInProgress = "In Progress"
|
||||
BoardsStatusOptionComplete = "Complete"
|
||||
|
||||
// Default colours seeded for the protected Status field. Tokens map to the
|
||||
// webapp's `colorTokenMap` in board_attributes_values.tsx; gray for unstarted
|
||||
// work, blue for active, green for done — standard kanban convention.
|
||||
BoardsStatusColorTodo = "default"
|
||||
BoardsStatusColorInProgress = "blue"
|
||||
BoardsStatusColorComplete = "green"
|
||||
|
||||
MaxKanbanColumns = 100
|
||||
)
|
||||
|
||||
// KanbanColumn represents a single column in a kanban view.
|
||||
// Each column maps to one or more option IDs from the grouped property field.
|
||||
type KanbanColumn struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
OptionIDs []string `json:"option_ids"`
|
||||
}
|
||||
|
||||
// KanbanGroupBy defines how a kanban view groups cards into columns.
|
||||
type KanbanGroupBy struct {
|
||||
FieldID string `json:"field_id"`
|
||||
Columns []KanbanColumn `json:"columns"`
|
||||
}
|
||||
|
||||
// KanbanProps is the typed representation of View.Props for kanban views.
|
||||
type KanbanProps struct {
|
||||
GroupBy KanbanGroupBy `json:"group_by"`
|
||||
}
|
||||
|
||||
// ToProps converts KanbanProps to a StringInterface map for storage in View.Props.
|
||||
func (kp *KanbanProps) ToProps() (StringInterface, error) {
|
||||
raw, err := json.Marshal(kp)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kanban props marshal: %w", err)
|
||||
}
|
||||
var m StringInterface
|
||||
if err := json.Unmarshal(raw, &m); err != nil {
|
||||
return nil, fmt.Errorf("kanban props unmarshal: %w", err)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// KanbanPropsFromProps parses View.Props into a typed KanbanProps.
|
||||
func KanbanPropsFromProps(props StringInterface) (*KanbanProps, error) {
|
||||
raw, err := json.Marshal(props)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kanban props marshal: %w", err)
|
||||
}
|
||||
var kp KanbanProps
|
||||
if err := json.Unmarshal(raw, &kp); err != nil {
|
||||
return nil, fmt.Errorf("kanban props unmarshal: %w", err)
|
||||
}
|
||||
return &kp, nil
|
||||
}
|
||||
|
||||
type View struct {
|
||||
Id string `json:"id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
Type ViewType `json:"type"`
|
||||
CreatorId string `json:"creator_id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description,omitempty"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
Props StringInterface `json:"props,omitempty"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
UpdateAt int64 `json:"update_at"`
|
||||
DeleteAt int64 `json:"delete_at"`
|
||||
}
|
||||
|
||||
type ViewPatch struct {
|
||||
Title *string `json:"title"`
|
||||
Description *string `json:"description"`
|
||||
SortOrder *int `json:"sort_order"`
|
||||
Props *StringInterface `json:"props"`
|
||||
}
|
||||
|
||||
type ViewsWithCount struct {
|
||||
Views []*View `json:"views"`
|
||||
TotalCount int64 `json:"total_count"`
|
||||
}
|
||||
|
||||
const ViewQueryDefaultPerPage = 20
|
||||
const ViewQueryMaxPerPage = 200
|
||||
|
||||
type ViewQueryOpts struct {
|
||||
// Page is the 0-based page number for limit/offset pagination.
|
||||
Page int
|
||||
// PerPage specifies the page size. Zero defaults to ViewQueryDefaultPerPage (20).
|
||||
// Values above ViewQueryMaxPerPage (200) are clamped to ViewQueryMaxPerPage.
|
||||
PerPage int
|
||||
}
|
||||
|
||||
func (o *View) Auditable() map[string]any {
|
||||
return map[string]any{
|
||||
"id": o.Id,
|
||||
"channel_id": o.ChannelId,
|
||||
"type": o.Type,
|
||||
"creator_id": o.CreatorId,
|
||||
"create_at": o.CreateAt,
|
||||
"update_at": o.UpdateAt,
|
||||
"delete_at": o.DeleteAt,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *View) Clone() *View {
|
||||
if o == nil {
|
||||
return nil
|
||||
}
|
||||
v := *o
|
||||
if o.Props != nil {
|
||||
v.Props = make(StringInterface, len(o.Props))
|
||||
maps.Copy(v.Props, o.Props)
|
||||
}
|
||||
return &v
|
||||
}
|
||||
|
||||
func (o *View) IsValid() *AppError {
|
||||
if !IsValidId(o.Id) {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(o.ChannelId) {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.channel_id.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(o.CreatorId) {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.creator_id.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.Type != ViewTypeKanban {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.type.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(o.Title) == "" || utf8.RuneCountInString(o.Title) > ViewTitleMaxRunes {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.title.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if utf8.RuneCountInString(o.Description) > ViewDescriptionMaxRunes {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.description.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.create_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if o.UpdateAt == 0 {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.update_at.app_error", nil, "id="+o.Id, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if err := validateViewProps(o.Type, o.Props); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateViewProps validates the props map based on the view type.
|
||||
func validateViewProps(viewType ViewType, props StringInterface) *AppError {
|
||||
if viewType == ViewTypeKanban {
|
||||
return validateKanbanProps(props)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateKanbanProps(props StringInterface) *AppError {
|
||||
if props == nil {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_required.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
kanban, err := KanbanPropsFromProps(props)
|
||||
if err != nil {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_invalid.app_error", nil, err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if !IsValidId(kanban.GroupBy.FieldID) {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_field_id.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if len(kanban.GroupBy.Columns) == 0 {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_columns_empty.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
if len(kanban.GroupBy.Columns) > MaxKanbanColumns {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_columns_max.app_error", nil, "", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
for i, col := range kanban.GroupBy.Columns {
|
||||
if !IsValidId(col.ID) {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_column_id.app_error", map[string]any{"Index": i}, "", http.StatusBadRequest)
|
||||
}
|
||||
if strings.TrimSpace(col.Name) == "" {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_column_name.app_error", map[string]any{"Index": i}, "", http.StatusBadRequest)
|
||||
}
|
||||
if len(col.OptionIDs) == 0 {
|
||||
return NewAppError("View.IsValid", "model.view.is_valid.props.kanban_column_options.app_error", map[string]any{"Index": i}, "", http.StatusBadRequest)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *View) PreSave() {
|
||||
if o.Id == "" {
|
||||
o.Id = NewId()
|
||||
}
|
||||
|
||||
if o.CreateAt == 0 {
|
||||
o.CreateAt = GetMillis()
|
||||
}
|
||||
o.UpdateAt = o.CreateAt
|
||||
o.DeleteAt = 0
|
||||
}
|
||||
|
||||
func (o *View) PreUpdate() {
|
||||
o.UpdateAt = GetMillis()
|
||||
}
|
||||
|
||||
func (o *View) Patch(patch *ViewPatch) {
|
||||
if patch == nil {
|
||||
return
|
||||
}
|
||||
if patch.Title != nil {
|
||||
o.Title = *patch.Title
|
||||
}
|
||||
if patch.Description != nil {
|
||||
o.Description = *patch.Description
|
||||
}
|
||||
if patch.SortOrder != nil {
|
||||
o.SortOrder = *patch.SortOrder
|
||||
}
|
||||
if patch.Props != nil {
|
||||
o.Props = make(StringInterface, len(*patch.Props))
|
||||
maps.Copy(o.Props, *patch.Props)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user