mattermost-community-enterp.../cmd/mmctl/commands/user_attributes.go
Claude ec1f89217a Merge: Complete Mattermost Server with Community Enterprise
Full Mattermost server source with integrated Community Enterprise features.
Includes vendor directory for offline/air-gapped builds.

Structure:
- enterprise-impl/: Enterprise feature implementations
- enterprise-community/: Init files that register implementations
- enterprise/: Bridge imports (community_imports.go)
- vendor/: All dependencies for offline builds

Build (online):
  go build ./cmd/mattermost

Build (offline/air-gapped):
  go build -mod=vendor ./cmd/mattermost

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-17 23:59:07 +09:00

239 lines
6.8 KiB
Go

// 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 {
switch field.Type {
case model.PropertyFieldTypeSelect, model.PropertyFieldTypeMultiselect:
return resolveOptionDisplayValue(field, rawValue)
default:
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 {
// 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, ", "))
}