mirror of
https://github.com/hanzoai/team.git
synced 2026-09-30 01:57:35 +00:00
594 lines
18 KiB
Go
594 lines
18 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package commands
|
|
|
|
import (
|
|
"context"
|
|
|
|
"github.com/mattermost/mattermost/server/public/model"
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/client"
|
|
"github.com/mattermost/mattermost/server/v8/cmd/mmctl/printer"
|
|
)
|
|
|
|
// createCPAField posts the given CPAField via the admin HTTP client and
|
|
// returns the server response reshaped as a typed CPAField.
|
|
func (s *MmctlE2ETestSuite) createCPAField(field *model.CPAField) *model.CPAField {
|
|
s.T().Helper()
|
|
created, _, err := s.th.SystemAdminClient.CreateCPAField(context.Background(), field.ToPropertyField())
|
|
s.Require().NoError(err)
|
|
cpa, err := model.NewCPAFieldFromPropertyField(created)
|
|
s.Require().NoError(err)
|
|
return cpa
|
|
}
|
|
|
|
// listCPAFields fetches all CPA fields via the admin HTTP client, returning
|
|
// them as typed CPAFields.
|
|
func (s *MmctlE2ETestSuite) listCPAFields() []*model.CPAField {
|
|
s.T().Helper()
|
|
fields, _, err := s.th.SystemAdminClient.ListCPAFields(context.Background())
|
|
s.Require().NoError(err)
|
|
out := make([]*model.CPAField, 0, len(fields))
|
|
for _, pf := range fields {
|
|
cpa, err := model.NewCPAFieldFromPropertyField(pf)
|
|
s.Require().NoError(err)
|
|
out = append(out, cpa)
|
|
}
|
|
return out
|
|
}
|
|
|
|
// getCPAField fetches a single CPA field by ID. There is no single-field HTTP
|
|
// endpoint, so this filters the full list — sufficient for verifying updates
|
|
// in tests with a clean fixture state.
|
|
func (s *MmctlE2ETestSuite) getCPAField(id string) *model.CPAField {
|
|
s.T().Helper()
|
|
for _, f := range s.listCPAFields() {
|
|
if f.ID == id {
|
|
return f
|
|
}
|
|
}
|
|
s.T().Fatalf("CPA field %q not found", id)
|
|
return nil
|
|
}
|
|
|
|
// cleanCPAFields removes all existing CPA fields to ensure clean test state
|
|
func (s *MmctlE2ETestSuite) cleanCPAFields() {
|
|
s.T().Helper()
|
|
for _, field := range s.listCPAFields() {
|
|
_, err := s.th.SystemAdminClient.DeleteCPAField(context.Background(), field.ID)
|
|
s.Require().NoError(err)
|
|
}
|
|
}
|
|
|
|
func (s *MmctlE2ETestSuite) TestCPAFieldListCmd() {
|
|
s.SetupEnterpriseTestHelper().InitBasic(s.T())
|
|
s.th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
|
|
|
s.RunForSystemAdminAndLocal("List CPA fields with no entries", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
err := cpaFieldListCmdF(c, &cobra.Command{}, []string{})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 0) // No fields should be present initially
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("List CPA fields with entries", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// Create a couple of test CPA fields
|
|
textField := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
},
|
|
Attrs: model.CPAAttrs{
|
|
Managed: "admin",
|
|
},
|
|
}
|
|
|
|
selectField := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Level",
|
|
Type: model.PropertyFieldTypeSelect,
|
|
TargetType: "user",
|
|
},
|
|
Attrs: model.CPAAttrs{
|
|
Managed: "",
|
|
Options: []*model.CustomProfileAttributesSelectOption{
|
|
{ID: model.NewId(), Name: "Junior"},
|
|
{ID: model.NewId(), Name: "Senior"},
|
|
},
|
|
},
|
|
}
|
|
|
|
createdTextField := s.createCPAField(textField)
|
|
s.Require().NotNil(createdTextField)
|
|
|
|
createdSelectField := s.createCPAField(selectField)
|
|
s.Require().NotNil(createdSelectField)
|
|
|
|
// Now test the list command
|
|
err := cpaFieldListCmdF(c, &cobra.Command{}, []string{})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 2) // Should have 2 fields now
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the output contains our created fields
|
|
lines := printer.GetLines()
|
|
s.Require().Len(lines, 2, "Should have exactly 2 output lines for 2 fields")
|
|
})
|
|
}
|
|
|
|
func (s *MmctlE2ETestSuite) TestCPAFieldCreateCmd() {
|
|
s.SetupEnterpriseTestHelper().InitBasic(s.T())
|
|
s.th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
|
|
|
s.RunForSystemAdminAndLocal("Create managed text field", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// Create command with arguments and managed flag
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
// Set the managed flag to true
|
|
err := cmd.Flags().Set("managed", "true")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldCreateCmdF(c, cmd, []string{"Department", "text"})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the success message
|
|
output := printer.GetLines()[0].(string)
|
|
s.Require().Contains(output, "Field Department correctly created")
|
|
|
|
// Verify field was actually created in the database
|
|
fields := s.listCPAFields()
|
|
s.Require().Len(fields, 1)
|
|
s.Require().Equal("Department", fields[0].Name)
|
|
s.Require().Equal(model.PropertyFieldTypeText, fields[0].Type)
|
|
s.Require().Equal("admin", fields[0].Attrs.Managed)
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Create multiselect field with options", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// Create command with arguments and option flags
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
// Set the option flags
|
|
err := cmd.Flags().Set("option", "Go")
|
|
s.Require().Nil(err)
|
|
err = cmd.Flags().Set("option", "React")
|
|
s.Require().Nil(err)
|
|
err = cmd.Flags().Set("option", "Python")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldCreateCmdF(c, cmd, []string{"Skills", "multiselect"})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the success message
|
|
output := printer.GetLines()[0].(string)
|
|
s.Require().Contains(output, "Field Skills correctly created")
|
|
|
|
// Verify field was actually created in the database with correct options
|
|
fields := s.listCPAFields()
|
|
s.Require().Len(fields, 1)
|
|
s.Require().Equal("Skills", fields[0].Name)
|
|
s.Require().Equal(model.PropertyFieldTypeMultiselect, fields[0].Type)
|
|
|
|
// Verify the options were created
|
|
s.Require().Len(fields[0].Attrs.Options, 3)
|
|
|
|
// Extract option names to verify they match what we set
|
|
var optionNames []string
|
|
for _, option := range fields[0].Attrs.Options {
|
|
optionNames = append(optionNames, option.Name)
|
|
}
|
|
s.Require().Contains(optionNames, "Go")
|
|
s.Require().Contains(optionNames, "React")
|
|
s.Require().Contains(optionNames, "Python")
|
|
})
|
|
}
|
|
|
|
func (s *MmctlE2ETestSuite) TestCPAFieldEditCmd() {
|
|
s.SetupEnterpriseTestHelper().InitBasic(s.T())
|
|
s.th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
|
|
|
s.RunForSystemAdminAndLocal("Edit nonexistent field should fail", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
err := cmd.Flags().Set("name", "New Name")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldEditCmdF(c, cmd, []string{"nonexistent-field-id"})
|
|
s.Require().NotNil(err)
|
|
s.Require().Contains(err.Error(), "failed to get field for \"nonexistent-field-id\"")
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Edit field using --name and --option flags", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// First create a field to edit
|
|
field := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Skills",
|
|
Type: model.PropertyFieldTypeSelect,
|
|
TargetType: "user",
|
|
},
|
|
Attrs: model.CPAAttrs{
|
|
Options: []*model.CustomProfileAttributesSelectOption{
|
|
{ID: model.NewId(), Name: "Go"},
|
|
},
|
|
},
|
|
}
|
|
|
|
createdField := s.createCPAField(field)
|
|
|
|
// Now edit the field
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
err := cmd.Flags().Set("name", "Programming Languages")
|
|
s.Require().Nil(err)
|
|
err = cmd.Flags().Set("option", "Go")
|
|
s.Require().Nil(err)
|
|
err = cmd.Flags().Set("option", "Python")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldEditCmdF(c, cmd, []string{createdField.ID})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the success message
|
|
output := printer.GetLines()[0].(string)
|
|
s.Require().Contains(output, "Field Programming Languages successfully updated")
|
|
|
|
// Verify field was actually updated
|
|
updatedField := s.getCPAField(createdField.ID)
|
|
s.Require().Equal("Programming Languages", updatedField.Name)
|
|
|
|
// Check options
|
|
s.Require().Len(updatedField.Attrs.Options, 2)
|
|
|
|
var optionNames []string
|
|
for _, option := range updatedField.Attrs.Options {
|
|
optionNames = append(optionNames, option.Name)
|
|
}
|
|
s.Require().Contains(optionNames, "Go")
|
|
s.Require().Contains(optionNames, "Python")
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Edit field using --managed flag", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// First create a field to edit
|
|
field := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
},
|
|
Attrs: model.CPAAttrs{
|
|
Managed: "",
|
|
},
|
|
}
|
|
|
|
createdField := s.createCPAField(field)
|
|
|
|
// Now edit the field with --managed flag
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
err := cmd.Flags().Set("managed", "true")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldEditCmdF(c, cmd, []string{createdField.ID})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify field was actually updated
|
|
updatedField := s.getCPAField(createdField.ID)
|
|
|
|
// Verify that managed flag was set correctly
|
|
s.Require().Equal("admin", updatedField.Attrs.Managed)
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Edit field by name", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// First create a field to edit
|
|
field := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
},
|
|
Attrs: model.CPAAttrs{
|
|
Managed: "",
|
|
},
|
|
}
|
|
|
|
createdField := s.createCPAField(field)
|
|
|
|
// Now edit the field using its name instead of ID
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
err := cmd.Flags().Set("name", "Team")
|
|
s.Require().Nil(err)
|
|
err = cmd.Flags().Set("managed", "true")
|
|
s.Require().Nil(err)
|
|
|
|
// Edit using field name "Department" instead of the field ID
|
|
err = cpaFieldEditCmdF(c, cmd, []string{"Department"})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the success message
|
|
output := printer.GetLines()[0].(string)
|
|
s.Require().Contains(output, "Field Team successfully updated")
|
|
|
|
// Verify field was actually updated by retrieving it
|
|
updatedField := s.getCPAField(createdField.ID)
|
|
s.Require().Equal("Team", updatedField.Name)
|
|
|
|
// Check managed status
|
|
s.Require().Equal("admin", updatedField.Attrs.Managed)
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Edit multiselect field with option preservation", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// First create a multiselect field with two options
|
|
field := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Programming Languages",
|
|
Type: model.PropertyFieldTypeMultiselect,
|
|
TargetType: "user",
|
|
},
|
|
Attrs: model.CPAAttrs{
|
|
Options: []*model.CustomProfileAttributesSelectOption{
|
|
{ID: model.NewId(), Name: "Go"},
|
|
{ID: model.NewId(), Name: "Python"},
|
|
},
|
|
},
|
|
}
|
|
|
|
createdField := s.createCPAField(field)
|
|
|
|
// Get the original option IDs to verify they are preserved
|
|
s.Require().Len(createdField.Attrs.Options, 2)
|
|
|
|
originalGoID := ""
|
|
originalPythonID := ""
|
|
for _, option := range createdField.Attrs.Options {
|
|
switch option.Name {
|
|
case "Go":
|
|
originalGoID = option.ID
|
|
case "Python":
|
|
originalPythonID = option.ID
|
|
}
|
|
}
|
|
s.Require().NotEmpty(originalGoID)
|
|
s.Require().NotEmpty(originalPythonID)
|
|
|
|
// Now edit the field to add a third option while preserving the first two
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().String("name", "", "")
|
|
cmd.Flags().Bool("managed", false, "")
|
|
cmd.Flags().String("attrs", "", "")
|
|
cmd.Flags().StringSlice("option", []string{}, "")
|
|
|
|
err := cmd.Flags().Set("option", "Go")
|
|
s.Require().Nil(err)
|
|
err = cmd.Flags().Set("option", "Python")
|
|
s.Require().Nil(err)
|
|
err = cmd.Flags().Set("option", "React")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldEditCmdF(c, cmd, []string{createdField.ID})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the success message
|
|
output := printer.GetLines()[0].(string)
|
|
s.Require().Contains(output, "Field Programming Languages successfully updated")
|
|
|
|
// Verify field was actually updated and options are preserved correctly
|
|
updatedField := s.getCPAField(createdField.ID)
|
|
|
|
// Check options
|
|
s.Require().Len(updatedField.Attrs.Options, 3)
|
|
|
|
// Verify the first two options preserved their original IDs and the third is new
|
|
foundGo := false
|
|
foundPython := false
|
|
foundReact := false
|
|
|
|
for _, option := range updatedField.Attrs.Options {
|
|
switch option.Name {
|
|
case "Go":
|
|
s.Require().Equal(originalGoID, option.ID, "Go option should preserve its original ID")
|
|
foundGo = true
|
|
case "Python":
|
|
s.Require().Equal(originalPythonID, option.ID, "Python option should preserve its original ID")
|
|
foundPython = true
|
|
case "React":
|
|
s.Require().NotEmpty(option.ID, "React option should have a valid ID")
|
|
s.Require().NotEqual(originalGoID, option.ID, "React option should have a different ID than Go")
|
|
s.Require().NotEqual(originalPythonID, option.ID, "React option should have a different ID than Python")
|
|
foundReact = true
|
|
}
|
|
}
|
|
|
|
s.Require().True(foundGo, "Go option should be present")
|
|
s.Require().True(foundPython, "Python option should be present")
|
|
s.Require().True(foundReact, "React option should be present")
|
|
})
|
|
}
|
|
|
|
func (s *MmctlE2ETestSuite) TestCPAFieldDeleteCmd() {
|
|
s.SetupEnterpriseTestHelper().InitBasic(s.T())
|
|
s.th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuEnterprise))
|
|
|
|
s.RunForSystemAdminAndLocal("Delete existing field", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// First create a field to delete
|
|
field := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
},
|
|
}
|
|
|
|
createdField := s.createCPAField(field)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
|
|
err := cmd.Flags().Set("confirm", "true")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldDeleteCmdF(c, cmd, []string{createdField.ID})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the success message
|
|
output := printer.GetLines()[0].(string)
|
|
s.Require().Contains(output, "Successfully deleted CPA field")
|
|
|
|
// Verify field was actually deleted by checking if it exists in the list
|
|
fields := s.listCPAFields()
|
|
|
|
// Field should not be in the list anymore
|
|
fieldExists := false
|
|
for _, field := range fields {
|
|
if field.ID == createdField.ID {
|
|
fieldExists = true
|
|
break
|
|
}
|
|
}
|
|
s.Require().False(fieldExists, "Field should have been deleted but still exists in the list")
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Delete existing field by name", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
// First create a field to delete
|
|
field := &model.CPAField{
|
|
PropertyField: model.PropertyField{
|
|
Name: "Department",
|
|
Type: model.PropertyFieldTypeText,
|
|
TargetType: "user",
|
|
},
|
|
}
|
|
|
|
createdField := s.createCPAField(field)
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
|
|
err := cmd.Flags().Set("confirm", "true")
|
|
s.Require().Nil(err)
|
|
|
|
// Delete using field name instead of ID
|
|
err = cpaFieldDeleteCmdF(c, cmd, []string{"Department"})
|
|
s.Require().Nil(err)
|
|
s.Require().Len(printer.GetLines(), 1)
|
|
s.Require().Len(printer.GetErrorLines(), 0)
|
|
|
|
// Verify the success message
|
|
output := printer.GetLines()[0].(string)
|
|
s.Require().Contains(output, "Successfully deleted CPA field: Department")
|
|
|
|
// Verify field was actually deleted by checking if it exists in the list
|
|
fields := s.listCPAFields()
|
|
|
|
// Field should not be in the list anymore
|
|
fieldExists := false
|
|
for _, field := range fields {
|
|
if field.ID == createdField.ID {
|
|
fieldExists = true
|
|
break
|
|
}
|
|
}
|
|
s.Require().False(fieldExists, "Field should have been deleted but still exists in the list")
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Delete nonexistent field", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
|
|
err := cmd.Flags().Set("confirm", "true")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldDeleteCmdF(c, cmd, []string{"nonexistent-field-id"})
|
|
s.Require().NotNil(err)
|
|
s.Require().Contains(err.Error(), `failed to get field for "nonexistent-field-id"`)
|
|
})
|
|
|
|
s.RunForSystemAdminAndLocal("Delete nonexistent field by name", func(c client.Client) {
|
|
printer.Clean()
|
|
s.cleanCPAFields()
|
|
|
|
cmd := &cobra.Command{}
|
|
cmd.Flags().Bool("confirm", false, "")
|
|
|
|
err := cmd.Flags().Set("confirm", "true")
|
|
s.Require().Nil(err)
|
|
|
|
err = cpaFieldDeleteCmdF(c, cmd, []string{"NonexistentField"})
|
|
s.Require().NotNil(err)
|
|
s.Require().Contains(err.Error(), `failed to get field for "NonexistentField"`)
|
|
})
|
|
}
|