diff --git a/pgmodels/intellectual_object.go b/pgmodels/intellectual_object.go index d5b867a..99b4ff6 100644 --- a/pgmodels/intellectual_object.go +++ b/pgmodels/intellectual_object.go @@ -74,6 +74,18 @@ func IntellectualObjectSelect(query *Query) ([]*IntellectualObject, error) { return objects, err } +// CountObjectsThatCanBeDeleted returns the number of active objects in the +// list of object IDs that belong to the specified institution. We use this +// when running batch deletions to ensure that no objects belong to an +// institution other than the one requesting the deletion. +// +// If we get a list of 100 ids, the return value should be 100. If it's not +// some object in the ID list was already deleted, or it belongs to someone +// else. +func CountObjectsThatCanBeDeleted(institutionID int64, objIDs []int64) (int, error) { + return common.Context().DB.Model((*IntellectualObject)(nil)).Where(`institution_id = ? and state = 'A' and id in (?)`, institutionID, pg.In(objIDs)).Count() +} + // Save saves this object to the database. This will peform an insert // if IntellectualObject.ID is zero. Otherwise, it updates. func (obj *IntellectualObject) Save() error { diff --git a/pgmodels/intellectual_object_test.go b/pgmodels/intellectual_object_test.go index 476756d..cb530f4 100644 --- a/pgmodels/intellectual_object_test.go +++ b/pgmodels/intellectual_object_test.go @@ -94,6 +94,34 @@ func TestObjHasActiveFiles(t *testing.T) { } +func TestCountObjectsThatCanBeDeleted(t *testing.T) { + // These are the ids of non-deleted objects belonging to institution 3. + // These are loaded from fixture data. + idsBelongingToInst3 := []int64{4, 5, 6, 12, 13} + + // All five items should be OK to delete, because all five + // belong to inst 3 and are in active state. + numberOkToDelete, err := pgmodels.CountObjectsThatCanBeDeleted(3, idsBelongingToInst3) + require.NoError(t, err) + assert.Equal(t, len(idsBelongingToInst3), numberOkToDelete) + + // We should get zero here, because none of these objects + // belong to inst2. + numberOkToDelete, err = pgmodels.CountObjectsThatCanBeDeleted(2, idsBelongingToInst3) + require.NoError(t, err) + assert.Equal(t, 0, numberOkToDelete) + + // In this set, the first three items belong to inst 3 and + // are active. ID 14 is already deleted, and items 1 and 2 + // belong to a different institution. So we should get three + // because only the first three items belong to inst 3 AND + // are currently active. + miscIds := []int64{4, 5, 6, 14, 1, 2} + numberOkToDelete, err = pgmodels.CountObjectsThatCanBeDeleted(3, miscIds) + require.NoError(t, err) + assert.Equal(t, 3, numberOkToDelete) +} + func TestObjLastIngestEvent(t *testing.T) { obj, err := pgmodels.IntellectualObjectByID(6) require.Nil(t, err) diff --git a/pgmodels/work_item.go b/pgmodels/work_item.go index e93f426..d90431b 100644 --- a/pgmodels/work_item.go +++ b/pgmodels/work_item.go @@ -9,6 +9,7 @@ import ( "github.com/APTrust/registry/common" "github.com/APTrust/registry/constants" v "github.com/asaskevich/govalidator" + "github.com/go-pg/pg/v10" "github.com/jinzhu/copier" "github.com/stretchr/stew/slice" ) @@ -116,6 +117,12 @@ func WorkItemsPendingForObject(instID int64, bagName string) ([]*WorkItem, error return WorkItemSelect(query) } +// WorkItemsPendingForObjectBatch returns the number of WorkItems pending +func WorkItemsPendingForObjectBatch(objIDs []int64) (int, error) { + completed := common.InterfaceList(constants.CompletedStatusValues) + return common.Context().DB.Model((*WorkItem)(nil)).Where(`intellectual_object_id in (?) and status not in (?)`, pg.In(objIDs), pg.In(completed)).Count() +} + // WorkItemsPendingForFile returns a list of in-progress WorkItems // for the GenericFile with the specified ID. func WorkItemsPendingForFile(fileID int64) ([]*WorkItem, error) { diff --git a/pgmodels/work_item_test.go b/pgmodels/work_item_test.go index fd4a759..b517323 100644 --- a/pgmodels/work_item_test.go +++ b/pgmodels/work_item_test.go @@ -439,6 +439,27 @@ func TestNewDeletionItem(t *testing.T) { require.Nil(t, item6) } +func TestWorkItemsPendingForObjectBatch(t *testing.T) { + db.LoadFixtures() + + // These objects belong to institution 3 + // and have no pending WorkItems in the fixture data. + objIDs := []int64{5, 6, 12, 13} + itemCount, err := pgmodels.WorkItemsPendingForObjectBatch(objIDs) + require.NoError(t, err) + assert.Equal(t, 0, itemCount) + + // Now add in Intel Obj 4, which has three + // pending WorkItems. The function should return + // the number of unfinished work items for this + // batch of objects. + objIDs = []int64{4, 5, 6, 12, 13} + itemCount, err = pgmodels.WorkItemsPendingForObjectBatch(objIDs) + require.NoError(t, err) + assert.Equal(t, 3, itemCount) + +} + func TestIsRestorationSpotTest(t *testing.T) { // This is not a spot test because it's not even a restoration. diff --git a/views/deletions/already_approved.html b/views/deletions/already_approved.html index 9b93c86..f970c97 100644 --- a/views/deletions/already_approved.html +++ b/views/deletions/already_approved.html @@ -5,9 +5,9 @@
The deletion of file {{ .itemIdentifier }} was approved by {{ .deletionRequest.ConfirmedBy.Name }} ({{ .deletionRequest.ConfirmedBy.Email }}) on {{ dateUS .deletionRequest.ConfirmedAt }}.
+The deletion of {{ .itemIdentifier }} was approved by {{ .deletionRequest.ConfirmedBy.Name }} ({{ .deletionRequest.ConfirmedBy.Email }}) on {{ dateUS .deletionRequest.ConfirmedAt }}.
-If the file has not yet been deleted, it will be soon.
+If the items have not yet been deleted, they will be soon.
Back to Deletions ListThe deletion of file {{ .itemIdentifier }} was cancelled by {{ .deletionRequest.CancelledBy.Name }} ({{ .deletionRequest.CancelledBy.Email }} on {{ dateUS .deletionRequest.CancelledAt }}.
+The deletion of {{ .itemIdentifier }} was cancelled by {{ .deletionRequest.CancelledBy.Name }} ({{ .deletionRequest.CancelledBy.Email }} on {{ dateUS .deletionRequest.CancelledAt }}.
-This deletion request will not be executed. Unless the file was deleted by a subsequent request, this file should still exist.
+This deletion request will not be executed. Unless the files or objects were deleted by a subsequent request, these items should still exist.
Back to Deletions ListUser {{ .deletionRequest.RequestedBy.Name }} ({{ .deletionRequest.RequestedBy.Email }}) wants to delete the following item:
+User {{ .deletionRequest.RequestedBy.Name }} ({{ .deletionRequest.RequestedBy.Email }}) wants to delete the following items:
+ {{ if (eq .itemType "file") }}{{ $obj.Identifier }}
+ {{ end }} + {{ end }} -Do you want to approve or cancel this request? If you approve, the file(s) will be deleted as soon as possible. Deletion cannot be undone. If you cancel, the file(s) will stay and no one else will be able to approve this request.
+Do you want to approve or cancel this request? If you approve, the items(s) will be deleted as soon as possible. Deletion cannot be undone. If you cancel, the file(s) will stay and no one else will be able to approve this request.