diff --git a/controller/test-files/event/list/ok-witype-change.res.headers.golden.json b/controller/test-files/event/list/ok-witype-change.res.headers.golden.json new file mode 100755 index 0000000000..305d7c10e7 --- /dev/null +++ b/controller/test-files/event/list/ok-witype-change.res.headers.golden.json @@ -0,0 +1,14 @@ +{ + "Cache-Control": [ + "max-age=2" + ], + "Content-Type": [ + "application/vnd.api+json" + ], + "Etag": [ + "1GmclFDDPcLR1ZWPZnykWw==" + ], + "Last-Modified": [ + "Mon, 01 Jan 0001 00:00:00 GMT" + ] +} \ No newline at end of file diff --git a/controller/test-files/event/list/ok-witype-change.res.payload.golden.json b/controller/test-files/event/list/ok-witype-change.res.payload.golden.json new file mode 100755 index 0000000000..a891fdba78 --- /dev/null +++ b/controller/test-files/event/list/ok-witype-change.res.payload.golden.json @@ -0,0 +1,49 @@ +{ + "data": [ + { + "attributes": { + "name": "workitemtype", + "timestamp": "0001-01-01T00:00:00Z" + }, + "id": "00000000-0000-0000-0000-000000000001", + "relationships": { + "modifier": { + "data": { + "id": "00000000-0000-0000-0000-000000000002", + "type": "users" + }, + "links": { + "related": "http:///api/users/00000000-0000-0000-0000-000000000002", + "self": "http:///api/users/00000000-0000-0000-0000-000000000002" + } + }, + "newValue": { + "data": [ + { + "id": "00000000-0000-0000-0000-000000000003", + "type": "workitemtypes" + } + ] + }, + "oldValue": { + "data": [ + { + "id": "00000000-0000-0000-0000-000000000004", + "type": "workitemtypes" + } + ] + }, + "workItemType": { + "data": { + "id": "00000000-0000-0000-0000-000000000003", + "type": "workitemtypes" + }, + "links": { + "self": "http:///api/workitemtypes/00000000-0000-0000-0000-000000000003" + } + } + }, + "type": "events" + } + ] +} \ No newline at end of file diff --git a/controller/work_item_events.go b/controller/work_item_events.go index e9c088aa83..7bd97761e6 100644 --- a/controller/work_item_events.go +++ b/controller/work_item_events.go @@ -81,11 +81,6 @@ func ConvertEvent(ctx context.Context, appl application.Application, req *http.R if err != nil { return nil, errs.Wrapf(err, "failed to load work item type: %s", wiEvent.WorkItemTypeID) } - fieldName := wiEvent.Name - fieldDef, ok := wit.Fields[fieldName] - if !ok { - return nil, errs.Errorf("failed to find field \"%s\" in work item type: %s (%s)", fieldName, wit.Name, wit.ID) - } modifierData, modifierLinks := ConvertUserSimple(req, wiEvent.Modifier) e := app.Event{ Type: event.APIStringTypeEvents, @@ -111,6 +106,40 @@ func ConvertEvent(ctx context.Context, appl application.Application, req *http.R }, } + if wiEvent.Name == event.WorkitemTypeChangeEvent { + oldTypeUUID, ok := wiEvent.Old.(uuid.UUID) + if !ok { + return nil, errs.Errorf("failed to convert old workitem type ID to UUID: %s", wiEvent.Old) + } + newTypeUUID, ok := wiEvent.New.(uuid.UUID) + if !ok { + return nil, errs.Errorf("failed to convert new workitem type ID to UUID: %s", wiEvent.New) + } + e.Relationships.OldValue = &app.RelationGenericList{ + Data: []*app.GenericData{ + { + ID: ptr.String(oldTypeUUID.String()), + Type: ptr.String(APIStringTypeWorkItemType), + }, + }, + } + e.Relationships.NewValue = &app.RelationGenericList{ + Data: []*app.GenericData{ + { + ID: ptr.String(newTypeUUID.String()), + Type: ptr.String(APIStringTypeWorkItemType), + }, + }, + } + return &e, nil + } + + fieldName := wiEvent.Name + fieldDef, ok := wit.Fields[fieldName] + if !ok { + return nil, errs.Errorf("failed to find field \"%s\" in work item type: %s (%s)", fieldName, wit.Name, wit.ID) + } + // convertVal returns the given value converted from storage space to // JSONAPI space. If the given value is supposed to be stored as a // relationship in JSONAPI, the second return value will be true. diff --git a/controller/work_item_events_test.go b/controller/work_item_events_test.go index 47112f4112..516d1e400e 100644 --- a/controller/work_item_events_test.go +++ b/controller/work_item_events_test.go @@ -600,4 +600,35 @@ func (s *TestEvent) TestListEvent() { // }) } }) + + s.T().Run("event list ok - workitem type change", func(t *testing.T) { + fxt := tf.NewTestFixture(t, s.DB, tf.CreateWorkItemEnvironment(), tf.WorkItems(1), tf.WorkItemTypes(2)) + svc := testsupport.ServiceAsSpaceUser("Event-Service", *fxt.Identities[0], &TestSpaceAuthzService{*fxt.Identities[0], ""}) + EventCtrl := NewEventsController(svc, s.GormDB, s.Configuration) + workitemCtrl := NewWorkitemController(svc, s.GormDB, s.Configuration) + payload := app.UpdateWorkitemPayload{ + Data: &app.WorkItem{ + Type: APIStringTypeWorkItem, + ID: &fxt.WorkItems[0].ID, + Attributes: map[string]interface{}{ + workitem.SystemVersion: fxt.WorkItems[0].Version, + }, + Relationships: &app.WorkItemRelationships{ + BaseType: &app.RelationBaseType{ + Data: &app.BaseTypeData{ + ID: fxt.WorkItemTypes[1].ID, + Type: APIStringTypeWorkItemType, + }, + }, + }, + }, + } + test.UpdateWorkitemOK(t, svc.Context, svc, workitemCtrl, fxt.WorkItems[0].ID, &payload) + res, eventList := test.ListWorkItemEventsOK(t, svc.Context, svc, EventCtrl, fxt.WorkItems[0].ID, nil, nil) + safeOverriteHeader(t, res, app.ETag, "1GmclFDDPcLR1ZWPZnykWw==") + require.NotEmpty(t, eventList) + require.Len(t, eventList.Data, 1) + compareWithGoldenAgnostic(t, filepath.Join(s.testDir, "list", "ok-witype-change.res.payload.golden.json"), eventList) + compareWithGoldenAgnostic(t, filepath.Join(s.testDir, "list", "ok-witype-change.res.headers.golden.json"), res.Header()) + }) } diff --git a/workitem/event/event_repository.go b/workitem/event/event_repository.go index 6adfab9255..8500fa3c16 100644 --- a/workitem/event/event_repository.go +++ b/workitem/event/event_repository.go @@ -16,6 +16,9 @@ import ( // APIStringTypeEvents represent the type of event const APIStringTypeEvents = "events" +// WorkitemTypeChangeEvent represents the attribute name for type change event +const WorkitemTypeChangeEvent = "workitemtype" + // Repository encapsulates retrieval of work item events type Repository interface { //repository.Exister @@ -61,15 +64,6 @@ func (r *GormEventRepository) List(ctx context.Context, wiID uuid.UUID) ([]Event oldRev := revisionList[k-1] newRev := revisionList[k] - // If the new and old work item type are different, we're skipping this - // revision because it denotes the change of a work item type. - // - // TODO(kwk): make sure we have a proper "changed work item type" - // revision entry in one way or another. - if oldRev.WorkItemTypeID != newRev.WorkItemTypeID { - continue - } - wit, err := r.workItemTypeRepo.Load(ctx, oldRev.WorkItemTypeID) if err != nil { return nil, errs.Wrapf(err, "failed to load old work item type: %s", oldRev.WorkItemTypeID) @@ -80,6 +74,24 @@ func (r *GormEventRepository) List(ctx context.Context, wiID uuid.UUID) ([]Event return nil, errs.Wrapf(err, "failed to load modifier identity %s", newRev.ModifierIdentity) } + // TODO(kwk): make sure we have a proper "changed work item type" + // revision entry in one way or another. + // TODO(ibrahim): type change event should have more information than just the new and old type IDs + if oldRev.WorkItemTypeID != newRev.WorkItemTypeID { + event := Event{ + ID: newRev.ID, + Name: WorkitemTypeChangeEvent, + WorkItemTypeID: newRev.WorkItemTypeID, + Timestamp: newRev.Time, + Modifier: modifierID.ID, + Old: oldRev.WorkItemTypeID, + New: newRev.WorkItemTypeID, + } + eventList = append(eventList, event) + // We do not compare any of the fields since this is a type change event. + continue + } + for fieldName, fieldDef := range wit.Fields { oldVal := oldRev.WorkItemFields[fieldName] diff --git a/workitem/event/event_repository_blackbox_test.go b/workitem/event/event_repository_blackbox_test.go index a98fec992e..1f743f335b 100644 --- a/workitem/event/event_repository_blackbox_test.go +++ b/workitem/event/event_repository_blackbox_test.go @@ -378,4 +378,19 @@ func (s *eventRepoBlackBoxTest) TestList() { } assert.Equal(t, 2, c) }) + + s.T().Run("Type change event", func(t *testing.T) { + fxt := tf.NewTestFixture(t, s.DB, tf.WorkItems(1), tf.WorkItemTypes(2)) + fxt.WorkItems[0].Type = fxt.WorkItemTypes[1].ID + wiNew, err := s.wiRepo.Save(s.Ctx, fxt.WorkItems[0].SpaceID, *fxt.WorkItems[0], fxt.Identities[0].ID) + require.NoError(t, err) + require.Equal(t, fxt.WorkItemTypes[1].ID, wiNew.Type) + eventList, err := s.wiEventRepo.List(s.Ctx, fxt.WorkItems[0].ID) + require.NoError(t, err) + require.NotEmpty(t, eventList) + require.Len(t, eventList, 1) + assert.Equal(t, event.WorkitemTypeChangeEvent, eventList[0].Name) + assert.Equal(t, fxt.WorkItemTypes[0].ID, eventList[0].Old) + assert.Equal(t, fxt.WorkItemTypes[1].ID, eventList[0].New) + }) }