Deletes CPA values on CPA field type change (#31122)

* Deletes CPA values on CPA field type change

* Fix error method name reference

* Cleans the state when a CPA field's type is updated

* Fix types

* Fix linter

* Webapp no longer makes a decision on the change and server sents a flag in the WS message

* Fix linter

---------

Co-authored-by: Miguel de la Cruz <miguel@ctrlz.es>
Этот коммит содержится в:
Miguel de la Cruz
2025-05-27 13:38:05 +02:00
коммит произвёл GitHub
родитель b3649132d0
Коммит e51ea025db
15 изменённых файлов: 684 добавлений и 61 удалений

Просмотреть файл

@@ -9452,11 +9452,11 @@ func (s *RetryLayerPropertyValueStore) Delete(groupID string, id string) error {
}
func (s *RetryLayerPropertyValueStore) DeleteForField(id string) error {
func (s *RetryLayerPropertyValueStore) DeleteForField(groupID string, fieldID string) error {
tries := 0
for {
err := s.PropertyValueStore.DeleteForField(id)
err := s.PropertyValueStore.DeleteForField(groupID, fieldID)
if err == nil {
return nil
}

Просмотреть файл

@@ -327,12 +327,16 @@ func (s *SqlPropertyValueStore) Delete(groupID string, id string) error {
return nil
}
func (s *SqlPropertyValueStore) DeleteForField(fieldID string) error {
func (s *SqlPropertyValueStore) DeleteForField(groupID, fieldID string) error {
builder := s.getQueryBuilder().
Update("PropertyValues").
Set("DeleteAt", model.GetMillis()).
Where(sq.Eq{"FieldID": fieldID})
if groupID != "" {
builder = builder.Where(sq.Eq{"GroupID": groupID})
}
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
return errors.Wrap(err, "property_value_delete_for_field_exec")
}

Просмотреть файл

@@ -1108,7 +1108,7 @@ type PropertyValueStore interface {
Update(groupID string, values []*model.PropertyValue) ([]*model.PropertyValue, error)
Upsert(values []*model.PropertyValue) ([]*model.PropertyValue, error)
Delete(groupID string, id string) error
DeleteForField(id string) error
DeleteForField(groupID, fieldID string) error
DeleteForTarget(groupID string, targetType string, targetID string) error
}

Просмотреть файл

@@ -62,17 +62,17 @@ func (_m *PropertyValueStore) Delete(groupID string, id string) error {
return r0
}
// DeleteForField provides a mock function with given fields: id
func (_m *PropertyValueStore) DeleteForField(id string) error {
ret := _m.Called(id)
// DeleteForField provides a mock function with given fields: groupID, fieldID
func (_m *PropertyValueStore) DeleteForField(groupID string, fieldID string) error {
ret := _m.Called(groupID, fieldID)
if len(ret) == 0 {
panic("no return value specified for DeleteForField")
}
var r0 error
if rf, ok := ret.Get(0).(func(string) error); ok {
r0 = rf(id)
if rf, ok := ret.Get(0).(func(string, string) error); ok {
r0 = rf(groupID, fieldID)
} else {
r0 = ret.Error(0)
}

Просмотреть файл

@@ -933,54 +933,213 @@ func testCreatePropertyValueWithArray(t *testing.T, _ request.CTX, ss store.Stor
}
func testDeleteForField(t *testing.T, _ request.CTX, ss store.Store) {
fieldID := model.NewId()
groupID := model.NewId()
t.Run("should delete values with matching fieldID and groupID", func(t *testing.T) {
fieldID := model.NewId()
groupID := model.NewId()
// Create test values
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: fieldID,
Value: json.RawMessage(`"value 1"`),
}
// Create test values
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: fieldID,
Value: json.RawMessage(`"value 1"`),
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: fieldID,
Value: json.RawMessage(`"value 2"`),
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: fieldID,
Value: json.RawMessage(`"value 2"`),
}
value3 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: model.NewId(), // Different field ID
Value: json.RawMessage(`"value 3"`),
}
value3 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID,
FieldID: model.NewId(), // Different field ID
Value: json.RawMessage(`"value 3"`),
}
for _, value := range []*model.PropertyValue{value1, value2, value3} {
_, err := ss.PropertyValue().Create(value)
for _, value := range []*model.PropertyValue{value1, value2, value3} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
}
// Delete values for the field
err := ss.PropertyValue().DeleteForField(groupID, fieldID)
require.NoError(t, err)
}
// Delete values for the field
err := ss.PropertyValue().DeleteForField(fieldID)
require.NoError(t, err)
// Verify values were soft-deleted
deletedValues, err := ss.PropertyValue().GetMany(groupID, []string{value1.ID, value2.ID})
require.NoError(t, err)
require.Len(t, deletedValues, 2)
require.NotZero(t, deletedValues[0].DeleteAt)
require.NotZero(t, deletedValues[1].DeleteAt)
// Verify values were soft-deleted
deletedValues, err := ss.PropertyValue().GetMany(groupID, []string{value1.ID, value2.ID})
require.NoError(t, err)
require.Len(t, deletedValues, 2)
require.NotZero(t, deletedValues[0].DeleteAt)
require.NotZero(t, deletedValues[1].DeleteAt)
// Verify value with different field ID was not deleted
nonDeletedValue, err := ss.PropertyValue().Get(groupID, value3.ID)
require.NoError(t, err)
require.Zero(t, nonDeletedValue.DeleteAt)
})
// Verify value with different field ID was not deleted
nonDeletedValue, err := ss.PropertyValue().Get(groupID, value3.ID)
require.NoError(t, err)
require.Zero(t, nonDeletedValue.DeleteAt)
t.Run("should not delete values with non-matching groupID", func(t *testing.T) {
fieldID := model.NewId()
groupID1 := model.NewId()
groupID2 := model.NewId()
// Create values with same fieldID but different groupIDs
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID1,
FieldID: fieldID,
Value: json.RawMessage(`"group1 value"`),
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID2,
FieldID: fieldID,
Value: json.RawMessage(`"group2 value"`),
}
for _, value := range []*model.PropertyValue{value1, value2} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
}
// Delete values for the field but only in groupID1
err := ss.PropertyValue().DeleteForField(groupID1, fieldID)
require.NoError(t, err)
// Verify value1 was deleted
deletedValue, err := ss.PropertyValue().Get(groupID1, value1.ID)
require.NoError(t, err)
require.NotZero(t, deletedValue.DeleteAt)
// Verify value2 was not deleted (different group)
nonDeletedValue, err := ss.PropertyValue().Get(groupID2, value2.ID)
require.NoError(t, err)
require.Zero(t, nonDeletedValue.DeleteAt)
})
t.Run("should delete values with empty groupID", func(t *testing.T) {
fieldID := model.NewId()
groupID1 := model.NewId()
groupID2 := model.NewId()
// Create values with same fieldID but different groupIDs
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID1,
FieldID: fieldID,
Value: json.RawMessage(`"group1 value 2"`),
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID2,
FieldID: fieldID,
Value: json.RawMessage(`"group2 value 2"`),
}
for _, value := range []*model.PropertyValue{value1, value2} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
}
// Delete values for the field across all groups (empty groupID)
err := ss.PropertyValue().DeleteForField("", fieldID)
require.NoError(t, err)
// Verify both values were deleted
opts := model.PropertyValueSearchOpts{
FieldID: fieldID,
IncludeDeleted: true,
PerPage: 10,
}
values, err := ss.PropertyValue().SearchPropertyValues(opts)
require.NoError(t, err)
// Find our two values within the results
var foundValues []*model.PropertyValue
for _, v := range values {
if v.ID == value1.ID || v.ID == value2.ID {
foundValues = append(foundValues, v)
}
}
require.Len(t, foundValues, 2)
for _, v := range foundValues {
require.NotZero(t, v.DeleteAt, "Value should be deleted")
}
})
t.Run("should work with multiple calls targeting different groups", func(t *testing.T) {
fieldID := model.NewId()
groupID1 := model.NewId()
groupID2 := model.NewId()
groupID3 := model.NewId()
// Create values with same fieldID across multiple groups
value1 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID1,
FieldID: fieldID,
Value: json.RawMessage(`"multi-group test 1"`),
}
value2 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID2,
FieldID: fieldID,
Value: json.RawMessage(`"multi-group test 2"`),
}
value3 := &model.PropertyValue{
TargetID: model.NewId(),
TargetType: "test_type",
GroupID: groupID3,
FieldID: fieldID,
Value: json.RawMessage(`"multi-group test 3"`),
}
for _, value := range []*model.PropertyValue{value1, value2, value3} {
_, err := ss.PropertyValue().Create(value)
require.NoError(t, err)
}
// Delete values for the field but only in groupID1
err := ss.PropertyValue().DeleteForField(groupID1, fieldID)
require.NoError(t, err)
// Delete values for the field but only in groupID2
err = ss.PropertyValue().DeleteForField(groupID2, fieldID)
require.NoError(t, err)
// Verify value1 was deleted
value1Result, err := ss.PropertyValue().Get(groupID1, value1.ID)
require.NoError(t, err)
require.NotZero(t, value1Result.DeleteAt)
// Verify value2 was deleted
value2Result, err := ss.PropertyValue().Get(groupID2, value2.ID)
require.NoError(t, err)
require.NotZero(t, value2Result.DeleteAt)
// Verify value3 was not deleted
value3Result, err := ss.PropertyValue().Get(groupID3, value3.ID)
require.NoError(t, err)
require.Zero(t, value3Result.DeleteAt)
})
}
func testDeleteForTarget(t *testing.T, _ request.CTX, ss store.Store) {

Просмотреть файл

@@ -7493,10 +7493,10 @@ func (s *TimerLayerPropertyValueStore) Delete(groupID string, id string) error {
return err
}
func (s *TimerLayerPropertyValueStore) DeleteForField(id string) error {
func (s *TimerLayerPropertyValueStore) DeleteForField(groupID string, fieldID string) error {
start := time.Now()
err := s.PropertyValueStore.DeleteForField(id)
err := s.PropertyValueStore.DeleteForField(groupID, fieldID)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {