Adds websocket messages to Custom Profile Attributes (#30163)
* Adds websocket messages to Custom Profile Attributes The app layer now fires a websocket event as part of the operations over Custom Profile Attribute fields and values. It updates as well the Patch method for CPA values so all the changes are commited as part of the same transaction. To be able to do this last operation, the change adds methods to upsert CPA values in both the store and the property service. * Fix i18n strings --------- Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5ba80d51ae
Коммит
f85a8c61a4
@@ -152,9 +152,9 @@ func (_m *PropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearc
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: field
|
||||
func (_m *PropertyFieldStore) Update(field []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
ret := _m.Called(field)
|
||||
// Update provides a mock function with given fields: fields
|
||||
func (_m *PropertyFieldStore) Update(fields []*model.PropertyField) ([]*model.PropertyField, error) {
|
||||
ret := _m.Called(fields)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
@@ -163,10 +163,10 @@ func (_m *PropertyFieldStore) Update(field []*model.PropertyField) ([]*model.Pro
|
||||
var r0 []*model.PropertyField
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyField) ([]*model.PropertyField, error)); ok {
|
||||
return rf(field)
|
||||
return rf(fields)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyField) []*model.PropertyField); ok {
|
||||
r0 = rf(field)
|
||||
r0 = rf(fields)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyField)
|
||||
@@ -174,7 +174,7 @@ func (_m *PropertyFieldStore) Update(field []*model.PropertyField) ([]*model.Pro
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]*model.PropertyField) error); ok {
|
||||
r1 = rf(field)
|
||||
r1 = rf(fields)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -170,9 +170,9 @@ func (_m *PropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearc
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: field
|
||||
func (_m *PropertyValueStore) Update(field []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
ret := _m.Called(field)
|
||||
// Update provides a mock function with given fields: values
|
||||
func (_m *PropertyValueStore) Update(values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
ret := _m.Called(values)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Update")
|
||||
@@ -181,10 +181,10 @@ func (_m *PropertyValueStore) Update(field []*model.PropertyValue) ([]*model.Pro
|
||||
var r0 []*model.PropertyValue
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) ([]*model.PropertyValue, error)); ok {
|
||||
return rf(field)
|
||||
return rf(values)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) []*model.PropertyValue); ok {
|
||||
r0 = rf(field)
|
||||
r0 = rf(values)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyValue)
|
||||
@@ -192,7 +192,37 @@ func (_m *PropertyValueStore) Update(field []*model.PropertyValue) ([]*model.Pro
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]*model.PropertyValue) error); ok {
|
||||
r1 = rf(field)
|
||||
r1 = rf(values)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Upsert provides a mock function with given fields: values
|
||||
func (_m *PropertyValueStore) Upsert(values []*model.PropertyValue) ([]*model.PropertyValue, error) {
|
||||
ret := _m.Called(values)
|
||||
|
||||
if len(ret) == 0 {
|
||||
panic("no return value specified for Upsert")
|
||||
}
|
||||
|
||||
var r0 []*model.PropertyValue
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) ([]*model.PropertyValue, error)); ok {
|
||||
return rf(values)
|
||||
}
|
||||
if rf, ok := ret.Get(0).(func([]*model.PropertyValue) []*model.PropertyValue); ok {
|
||||
r0 = rf(values)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]*model.PropertyValue)
|
||||
}
|
||||
}
|
||||
|
||||
if rf, ok := ret.Get(1).(func([]*model.PropertyValue) error); ok {
|
||||
r1 = rf(values)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ func TestPropertyValueStore(t *testing.T, rctx request.CTX, ss store.Store, s Sq
|
||||
t.Run("GetPropertyValue", func(t *testing.T) { testGetPropertyValue(t, rctx, ss) })
|
||||
t.Run("GetManyPropertyValues", func(t *testing.T) { testGetManyPropertyValues(t, rctx, ss) })
|
||||
t.Run("UpdatePropertyValue", func(t *testing.T) { testUpdatePropertyValue(t, rctx, ss) })
|
||||
t.Run("UpsertPropertyValue", func(t *testing.T) { testUpsertPropertyValue(t, rctx, ss) })
|
||||
t.Run("DeletePropertyValue", func(t *testing.T) { testDeletePropertyValue(t, rctx, ss) })
|
||||
t.Run("SearchPropertyValues", func(t *testing.T) { testSearchPropertyValues(t, rctx, ss) })
|
||||
t.Run("DeleteForField", func(t *testing.T) { testDeleteForField(t, rctx, ss) })
|
||||
@@ -306,6 +307,170 @@ func testUpdatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
})
|
||||
}
|
||||
|
||||
func testUpsertPropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail if the property value is not valid", func(t *testing.T) {
|
||||
value := &model.PropertyValue{
|
||||
TargetID: "",
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"test value"`),
|
||||
}
|
||||
updatedValue, err := ss.PropertyValue().Upsert([]*model.PropertyValue{value})
|
||||
require.Zero(t, updatedValue)
|
||||
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
|
||||
|
||||
value.TargetID = model.NewId()
|
||||
value.GroupID = ""
|
||||
updatedValue, err = ss.PropertyValue().Upsert([]*model.PropertyValue{value})
|
||||
require.Zero(t, updatedValue)
|
||||
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
|
||||
})
|
||||
|
||||
t.Run("should be able to insert new property values", func(t *testing.T) {
|
||||
value1 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"value 1"`),
|
||||
}
|
||||
|
||||
value2 := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"value 2"`),
|
||||
}
|
||||
|
||||
values, err := ss.PropertyValue().Upsert([]*model.PropertyValue{value1, value2})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, values, 2)
|
||||
require.NotEmpty(t, values[0].ID)
|
||||
require.NotEmpty(t, values[1].ID)
|
||||
require.NotZero(t, values[0].CreateAt)
|
||||
require.NotZero(t, values[1].CreateAt)
|
||||
|
||||
valuesFromStore, err := ss.PropertyValue().GetMany([]string{values[0].ID, values[1].ID})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, valuesFromStore, 2)
|
||||
})
|
||||
|
||||
t.Run("should be able to update existing property values", func(t *testing.T) {
|
||||
// Create initial value
|
||||
value := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"initial value"`),
|
||||
}
|
||||
_, err := ss.PropertyValue().Create(value)
|
||||
require.NoError(t, err)
|
||||
valueID := value.ID
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Update via upsert
|
||||
value.ID = ""
|
||||
value.Value = json.RawMessage(`"updated value"`)
|
||||
values, err := ss.PropertyValue().Upsert([]*model.PropertyValue{value})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, values, 1)
|
||||
require.Equal(t, valueID, values[0].ID)
|
||||
require.Equal(t, json.RawMessage(`"updated value"`), values[0].Value)
|
||||
require.Greater(t, values[0].UpdateAt, values[0].CreateAt)
|
||||
|
||||
// Verify in database
|
||||
updated, err := ss.PropertyValue().Get(valueID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, json.RawMessage(`"updated value"`), updated.Value)
|
||||
require.Greater(t, updated.UpdateAt, updated.CreateAt)
|
||||
})
|
||||
|
||||
t.Run("should handle mixed insert and update operations", func(t *testing.T) {
|
||||
// Create first value
|
||||
existingValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"existing value"`),
|
||||
}
|
||||
_, err := ss.PropertyValue().Create(existingValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Prepare new value
|
||||
newValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"new value"`),
|
||||
}
|
||||
|
||||
// Update existing and insert new via upsert
|
||||
existingValue.Value = json.RawMessage(`"updated existing"`)
|
||||
values, err := ss.PropertyValue().Upsert([]*model.PropertyValue{existingValue, newValue})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, values, 2)
|
||||
|
||||
// Verify both values
|
||||
newValueUpserted, err := ss.PropertyValue().Get(newValue.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, json.RawMessage(`"new value"`), newValueUpserted.Value)
|
||||
existingValueUpserted, err := ss.PropertyValue().Get(existingValue.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, json.RawMessage(`"updated existing"`), existingValueUpserted.Value)
|
||||
})
|
||||
|
||||
t.Run("should not perform any operation if one of the fields is invalid", func(t *testing.T) {
|
||||
// Create initial valid value
|
||||
existingValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: model.NewId(),
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"existing value"`),
|
||||
}
|
||||
_, err := ss.PropertyValue().Create(existingValue)
|
||||
require.NoError(t, err)
|
||||
|
||||
originalValue := *existingValue
|
||||
|
||||
// Prepare an invalid value
|
||||
invalidValue := &model.PropertyValue{
|
||||
TargetID: model.NewId(),
|
||||
TargetType: "test_type",
|
||||
GroupID: "", // Invalid: empty group ID
|
||||
FieldID: model.NewId(),
|
||||
Value: json.RawMessage(`"new value"`),
|
||||
}
|
||||
|
||||
// Try to update existing and insert invalid via upsert
|
||||
existingValue.Value = json.RawMessage(`"should not update"`)
|
||||
_, err = ss.PropertyValue().Upsert([]*model.PropertyValue{existingValue, invalidValue})
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "model.property_value.is_valid.app_error")
|
||||
|
||||
// Verify the existing value was not changed
|
||||
retrieved, err := ss.PropertyValue().Get(existingValue.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, originalValue.Value, retrieved.Value)
|
||||
require.Equal(t, originalValue.UpdateAt, retrieved.UpdateAt)
|
||||
|
||||
// Verify the invalid value was not inserted
|
||||
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
|
||||
TargetID: invalidValue.TargetID,
|
||||
Page: 0,
|
||||
PerPage: 10,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, results)
|
||||
})
|
||||
}
|
||||
|
||||
func testDeletePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||
t.Run("should fail on nonexisting value", func(t *testing.T) {
|
||||
err := ss.PropertyValue().Delete(model.NewId())
|
||||
|
||||
Ссылка в новой задаче
Block a user