diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5
index 8bfc282d73..b350d2c979 100644
--- a/resources/i18n/en.json5
+++ b/resources/i18n/en.json5
@@ -1657,6 +1657,36 @@
+ "resource-policies.add.for.": "Add a new policy",
+
+ "resource-policies.add.for.bitstream": "Add a new Bitstream policy",
+
+ "resource-policies.add.for.bundle": "Add a new Bundle policy",
+
+ "resource-policies.add.for.item": "Add a new Item policy",
+
+ "resource-policies.table.headers.action": "Action",
+
+ "resource-policies.table.headers.date.end": "End Date",
+
+ "resource-policies.table.headers.date.start": "Start Date",
+
+ "resource-policies.table.headers.group": "Group",
+
+ "resource-policies.table.headers.group.edit": "Edit",
+
+ "resource-policies.table.headers.name": "Name",
+
+ "resource-policies.table.headers.id": "ID",
+
+ "resource-policies.table.headers.title.for.bitstream": "Policies for Bitstream",
+
+ "resource-policies.table.headers.title.for.bundle": "Policies for Bundle",
+
+ "resource-policies.table.headers.title.for.item": "Policies for Item",
+
+
+
"search.description": "",
"search.switch-configuration.title": "Show",
diff --git a/src/app/shared/resource-policies/resource-policies.component.html b/src/app/shared/resource-policies/resource-policies.component.html
new file mode 100644
index 0000000000..dbcf3a45e7
--- /dev/null
+++ b/src/app/shared/resource-policies/resource-policies.component.html
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+ {{ 'resource-policies.table.headers.title.for.' + resourceKey | translate }} {{resourceUUID}}
+
+
+ |
+
+
+ {{'resource-policies.table.headers.id' | translate}} |
+ {{'resource-policies.table.headers.name' | translate}} |
+ {{'resource-policies.table.headers.action' | translate}} |
+ {{'resource-policies.table.headers.group' | translate}} |
+ {{'resource-policies.table.headers.date.start' | translate}} |
+ {{'resource-policies.table.headers.date.end' | translate}} |
+
+
+
+
+ {{policy.id}} |
+ {{policy.name}} |
+ {{policy.action}} |
+
+ {{getGroupName(policy) | async}}
+
+ |
+ |
+ {{policy.startDate}} |
+ {{policy.endDate}} |
+
+
+
+
diff --git a/src/app/shared/resource-policies/resource-policies.component.scss b/src/app/shared/resource-policies/resource-policies.component.scss
new file mode 100644
index 0000000000..0d9329e760
--- /dev/null
+++ b/src/app/shared/resource-policies/resource-policies.component.scss
@@ -0,0 +1,3 @@
+td .btn-link:focus {
+ box-shadow: none !important;
+}
diff --git a/src/app/shared/resource-policies/resource-policies.component.ts b/src/app/shared/resource-policies/resource-policies.component.ts
new file mode 100644
index 0000000000..0596dba586
--- /dev/null
+++ b/src/app/shared/resource-policies/resource-policies.component.ts
@@ -0,0 +1,134 @@
+import { Component, Input, OnDestroy, OnInit } from '@angular/core';
+import { Router } from '@angular/router';
+
+import { Observable, Subscription } from 'rxjs';
+import { map } from 'rxjs/operators';
+
+import { ResourcePolicyService } from '../../core/resource-policy/resource-policy.service';
+import { PaginatedList } from '../../core/data/paginated-list';
+import { getFirstSucceededRemoteDataPayload, getSucceededRemoteData } from '../../core/shared/operators';
+import { RemoteData } from '../../core/data/remote-data';
+import { ResourcePolicy } from '../../core/resource-policy/models/resource-policy.model';
+import { DSONameService } from '../../core/breadcrumbs/dso-name.service';
+import { Group } from '../../core/eperson/models/group.model';
+import { GroupDataService } from '../../core/eperson/group-data.service';
+import { hasValue, isNotEmpty } from '../empty.util';
+
+@Component({
+ selector: 'ds-resource-policies',
+ styleUrls: ['./resource-policies.component.scss'],
+ templateUrl: './resource-policies.component.html'
+})
+/**
+ * Component that shows the policies for given resource
+ */
+export class ResourcePoliciesComponent implements OnInit, OnDestroy {
+
+ /**
+ * The resource UUID
+ * @type {string}
+ */
+ @Input() public resourceUUID: string;
+
+ /**
+ * The resource type (e.g. 'item', 'bundle' etc) used as key to build automatically translation label
+ * @type {string}
+ */
+ @Input() public resourceKey: string;
+
+ /**
+ * The list of policies for given resource
+ * @type {Observable>>}
+ */
+ private resourcePolicies$: Observable>>;
+
+ /**
+ * Array to track all subscriptions and unsubscribe them onDestroy
+ * @type {Array}
+ */
+ private subs: Subscription[] = [];
+
+ /**
+ * Initialize instance variables
+ *
+ * @param {DSONameService} dsoNameService
+ * @param {GroupDataService} groupService
+ * @param {ResourcePolicyService} resourcePolicyService
+ * @param {Router} router
+ */
+ constructor(
+ private dsoNameService: DSONameService,
+ private groupService: GroupDataService,
+ private resourcePolicyService: ResourcePolicyService,
+ private router: Router
+ ) {
+ }
+
+ /**
+ * Initialize the component, setting up the resource's policies
+ */
+ ngOnInit(): void {
+ this.resourcePolicies$ = this.resourcePolicyService.searchByResource(this.resourceUUID).pipe(
+ getSucceededRemoteData()
+ );
+
+ }
+
+ /**
+ * Return the group's name which the given policy is linked to
+ *
+ * @param policy The resource policy
+ */
+ getGroupName(policy: ResourcePolicy): Observable {
+ return this.groupService.findByHref(policy._links.group.href).pipe(
+ getFirstSucceededRemoteDataPayload(),
+ // A group has not dc.title metadata so is not possible to use DSONameService to retrieve name
+ map((group: Group) => group.name)
+ )
+ }
+
+ /**
+ * Return all resource's policies
+ *
+ * @return an observable that emits all resource's policies
+ */
+ getResourcePolicies(): Observable>> {
+ return this.resourcePolicies$;
+ }
+
+ /**
+ * Check whether the given policy is linked to a group
+ *
+ * @param policy The resource policy
+ * @return an observable that emits true when the policy is linked to a group, false otherwise
+ */
+ hasGroup(policy): Observable {
+ return this.groupService.findByHref(policy._links.group.href).pipe(
+ getFirstSucceededRemoteDataPayload(),
+ map((group: Group) => isNotEmpty(group))
+ )
+ }
+
+ /**
+ * Redirect to group edit page
+ *
+ * @param policy The resource policy
+ */
+ redirectToGroupEditPage(policy: ResourcePolicy): void {
+ this.subs.push(
+ this.groupService.findByHref(policy._links.group.href).pipe(
+ getFirstSucceededRemoteDataPayload(),
+ map((group: Group) => group.id)
+ ).subscribe((groupUUID) => this.router.navigate(['groups', groupUUID, 'edit']))
+ )
+ }
+
+ /**
+ * Unsubscribe from all subscriptions
+ */
+ ngOnDestroy(): void {
+ this.subs
+ .filter((subscription) => hasValue(subscription))
+ .forEach((subscription) => subscription.unsubscribe())
+ }
+}
diff --git a/src/app/shared/shared.module.ts b/src/app/shared/shared.module.ts
index 673c969506..313c56089b 100644
--- a/src/app/shared/shared.module.ts
+++ b/src/app/shared/shared.module.ts
@@ -179,6 +179,8 @@ import { ExistingMetadataListElementComponent } from './form/builder/ds-dynamic-
import { ItemVersionsComponent } from './item/item-versions/item-versions.component';
import { SortablejsModule } from 'ngx-sortablejs';
import { MissingTranslationHelper } from './translate/missing-translation.helper';
+import { ResourcePoliciesComponent } from './resource-policies/resource-policies.component';
+import { NgForTrackByIdDirective } from './ng-for-track-by-id.directive';
const MODULES = [
// Do NOT include UniversalModule, HttpModule, or JsonpModule here
@@ -347,6 +349,7 @@ const COMPONENTS = [
ExistingMetadataListElementComponent,
ItemVersionsComponent,
PublicationSearchResultListElementComponent,
+ ResourcePoliciesComponent
];
const ENTRY_COMPONENTS = [
@@ -438,7 +441,8 @@ const DIRECTIVES = [
AutoFocusDirective,
RoleDirective,
MetadataRepresentationDirective,
- ListableObjectDirective
+ ListableObjectDirective,
+ NgForTrackByIdDirective
];
@NgModule({