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 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package commands
import (
"context"
"encoding/json"
"fmt"
"maps"
"strings"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
"github.com/spf13/cobra"
)
var UserAttributesCmd = &cobra.Command{
Use: "attributes",
Aliases: []string{"attrs", "cpa"},
Short: "Management of User Attributes",
Long: "Management of User Attributes fields and values.",
}
var UserAttributesFieldCmd = &cobra.Command{
Use: "field",
Short: "Management of User Attributes fields",
Long: "Create, list, edit, and delete User Attribute fields.",
}
var UserAttributesValueCmd = &cobra.Command{
Use: "value",
Short: "Management of User Attributes values",
Long: "List, set, and delete User Attribute values for users.",
}
func init() {
UserAttributesCmd.AddCommand(
UserAttributesFieldCmd,
UserAttributesValueCmd,
)
UserCmd.AddCommand(UserAttributesCmd)
}
// Helper function to build field attributes from command flags. If existingAttrs is
// provided, it will be used as the base and merged with flag changes
func buildFieldAttrs(cmd *cobra.Command, existingAttrs model.StringInterface) (model.StringInterface, error) {
var attrs = make(model.StringInterface)
if existingAttrs != nil {
maps.Copy(attrs, existingAttrs)
}
// First parse --attrs if provided
if attrsStr, err := cmd.Flags().GetString("attrs"); err == nil && attrsStr != "" && cmd.Flags().Changed("attrs") {
var attrsMap map[string]any
if err := json.Unmarshal([]byte(attrsStr), &attrsMap); err != nil {
return nil, fmt.Errorf("failed to parse attrs JSON: %w", err)
}
// Copy to our attrs map
maps.Copy(attrs, attrsMap)
}
// Individual flags override --attrs (applied on top)
if cmd.Flags().Changed("managed") {
managed, _ := cmd.Flags().GetBool("managed")
if managed {
attrs["managed"] = "admin"
} else {
attrs["managed"] = ""
}
}
// Handle --option flags for select/multiselect fields
if options, err := cmd.Flags().GetStringSlice("option"); err == nil && len(options) > 0 && cmd.Flags().Changed("option") {
var selectOptions []*model.CustomProfileAttributesSelectOption
existingOptionsMap := make(map[string]*model.CustomProfileAttributesSelectOption)
if existingOptions, ok := attrs["options"]; ok {
existingOptionsJSON, err := json.Marshal(existingOptions)
if err == nil {
var existingSelectOptions []*model.CustomProfileAttributesSelectOption
if err := json.Unmarshal(existingOptionsJSON, &existingSelectOptions); err == nil {
for _, option := range existingSelectOptions {
existingOptionsMap[option.Name] = option
}
}
}
}
for _, optionName := range options {
if existingOption, exists := existingOptionsMap[optionName]; exists {
selectOptions = append(selectOptions, existingOption)
} else {
selectOptions = append(selectOptions, &model.CustomProfileAttributesSelectOption{
ID: model.NewId(),
Name: optionName,
})
}
}
attrs["options"] = selectOptions
}
return attrs, nil
}
func hasAttrsChanges(cmd *cobra.Command) bool {
return cmd.Flags().Changed("managed") ||
cmd.Flags().Changed("attrs") ||
cmd.Flags().Changed("option")
}
func getFieldFromArg(c client.Client, fieldArg string) (*model.PropertyField, error) {
fields, _, err := c.ListCPAFields(context.TODO())
if err != nil {
return nil, fmt.Errorf("failed to get CPA fields: %w", err)
}
if model.IsValidId(fieldArg) {
for _, field := range fields {
if field.ID == fieldArg {
return field, nil
}
}
}
for _, field := range fields {
if field.Name == fieldArg {
return field, nil
}
}
return nil, fmt.Errorf("failed to get field for %q", fieldArg)
}
// setupCPATemplateContext sets up template functions for field and value resolution
func setupCPATemplateContext(c client.Client) error {
// Get all fields once for the entire command
fields, _, err := c.ListCPAFields(context.TODO())
if err != nil {
return fmt.Errorf("failed to get CPA fields for template context: %w", err)
}
fieldMap := make(map[string]*model.PropertyField)
for _, field := range fields {
fieldMap[field.ID] = field
}
// Set template function to resolve field ID to field name
printer.SetTemplateFunc("fieldName", func(fieldID string) string {
if field, exists := fieldMap[fieldID]; exists {
return field.Name
}
return fieldID // fallback to field ID if not found
})
// Set template function to get field type
printer.SetTemplateFunc("fieldType", func(fieldID string) string {
if field, exists := fieldMap[fieldID]; exists {
return string(field.Type)
}
return "unknown"
})
// Set template function to resolve field value to human-readable format
printer.SetTemplateFunc("resolveValue", func(fieldID string, rawValue json.RawMessage) string {
field, exists := fieldMap[fieldID]
if !exists {
return string(rawValue)
}
return resolveDisplayValue(field, rawValue)
})
return nil
}
// resolveDisplayValue converts raw field values to human-readable display format
func resolveDisplayValue(field *model.PropertyField, rawValue json.RawMessage) string {
if field.Type.SupportsOptions() {
return resolveOptionDisplayValue(field, rawValue)
}
var value any
if err := json.Unmarshal(rawValue, &value); err != nil {
return string(rawValue)
}
return fmt.Sprintf("%v", value)
}
// resolveOptionDisplayValue converts option IDs to option names for select/multiselect fields
func resolveOptionDisplayValue(field *model.PropertyField, rawValue json.RawMessage) string {
// Convert PropertyField to CPAField to access options
cpaField, err := model.NewCPAFieldFromPropertyField(field)
if err != nil {
return string(rawValue)
}
if len(cpaField.Attrs.Options) == 0 {
return string(rawValue)
}
// Create option lookup map
optionMap := make(map[string]string)
for _, option := range cpaField.Attrs.Options {
optionMap[option.ID] = option.Name
}
if field.Type == model.PropertyFieldTypeSelect || field.Type == model.PropertyFieldTypeRank {
// Single select - expect a string
var optionID string
if err := json.Unmarshal(rawValue, &optionID); err != nil {
return string(rawValue)
}
if optionName, exists := optionMap[optionID]; exists {
return optionName
}
return optionID
}
// Multiselect - expect an array
var optionIDs []string
if err := json.Unmarshal(rawValue, &optionIDs); err != nil {
return string(rawValue)
}
optionNames := make([]string, 0, len(optionIDs))
for _, optionID := range optionIDs {
if optionName, exists := optionMap[optionID]; exists {
optionNames = append(optionNames, optionName)
} else {
optionNames = append(optionNames, optionID)
}
}
return fmt.Sprintf("[%s]", strings.Join(optionNames, ", "))
}