Skip to content

Commit 123b6b7

Browse files
authored
Merge pull request DSpace#2761 from ybnd/fix-versioning-button
Fix caching issues for versioning
2 parents a5b9477 + e5ba482 commit 123b6b7

22 files changed

Lines changed: 166 additions & 100 deletions

src/app/core/data/base/identifiable-data.service.spec.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,7 @@
55
*
66
* http://www.dspace.org/license/
77
*/
8-
import {
9-
Observable,
10-
of as observableOf,
11-
} from 'rxjs';
8+
import { of as observableOf } from 'rxjs';
129
import { TestScheduler } from 'rxjs/testing';
1310

1411
import { getMockRemoteDataBuildService } from '../../../shared/mocks/remote-data-build.service.mock';
@@ -18,14 +15,14 @@ import { followLink } from '../../../shared/utils/follow-link-config.model';
1815
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
1916
import { ObjectCacheService } from '../../cache/object-cache.service';
2017
import { HALEndpointService } from '../../shared/hal-endpoint.service';
21-
import { FindListOptions } from '../find-list-options.model';
2218
import { RemoteData } from '../remote-data';
2319
import { RequestService } from '../request.service';
2420
import { RequestEntryState } from '../request-entry-state.model';
2521
import { EMBED_SEPARATOR } from './base-data.service';
2622
import { IdentifiableDataService } from './identifiable-data.service';
2723

28-
const endpoint = 'https://rest.api/core';
24+
const base = 'https://rest.api/core';
25+
const endpoint = 'test';
2926

3027
class TestService extends IdentifiableDataService<any> {
3128
constructor(
@@ -34,11 +31,7 @@ class TestService extends IdentifiableDataService<any> {
3431
protected objectCache: ObjectCacheService,
3532
protected halService: HALEndpointService,
3633
) {
37-
super(undefined, requestService, rdbService, objectCache, halService);
38-
}
39-
40-
public getBrowseEndpoint(options: FindListOptions = {}, linkPath: string = this.linkPath): Observable<string> {
41-
return observableOf(endpoint);
34+
super(endpoint, requestService, rdbService, objectCache, halService);
4235
}
4336
}
4437

@@ -55,7 +48,7 @@ describe('IdentifiableDataService', () => {
5548

5649
function initTestService(): TestService {
5750
requestService = getMockRequestService();
58-
halService = new HALEndpointServiceStub('url') as any;
51+
halService = new HALEndpointServiceStub(base) as any;
5952
rdbService = getMockRemoteDataBuildService();
6053
objectCache = {
6154

@@ -147,4 +140,12 @@ describe('IdentifiableDataService', () => {
147140
expect(result).toEqual(expected);
148141
});
149142
});
143+
144+
describe('invalidateById', () => {
145+
it('should invalidate the correct resource by href', () => {
146+
spyOn(service, 'invalidateByHref').and.returnValue(observableOf(true));
147+
service.invalidateById('123');
148+
expect(service.invalidateByHref).toHaveBeenCalledWith(`${base}/${endpoint}/123`);
149+
});
150+
});
150151
});

src/app/core/data/base/identifiable-data.service.ts

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
* http://www.dspace.org/license/
77
*/
88
import { Observable } from 'rxjs';
9-
import { map } from 'rxjs/operators';
9+
import {
10+
map,
11+
switchMap,
12+
take,
13+
} from 'rxjs/operators';
1014

1115
import { FollowLinkConfig } from '../../../shared/utils/follow-link-config.model';
1216
import { RemoteDataBuildService } from '../../cache/builders/remote-data-build.service';
@@ -81,4 +85,19 @@ export class IdentifiableDataService<T extends CacheableObject> extends BaseData
8185
return this.getEndpoint().pipe(
8286
map((endpoint: string) => this.getIDHref(endpoint, resourceID, ...linksToFollow)));
8387
}
88+
89+
/**
90+
* Invalidate a cached resource by its identifier
91+
* @param resourceID the identifier of the resource to invalidate
92+
*/
93+
invalidateById(resourceID: string): Observable<boolean> {
94+
const ok$ = this.getIDHrefObs(resourceID).pipe(
95+
take(1),
96+
switchMap((href) => this.invalidateByHref(href)),
97+
);
98+
99+
ok$.subscribe();
100+
101+
return ok$;
102+
}
84103
}

src/app/core/data/version-history-data.service.spec.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ describe('VersionHistoryDataService', () => {
6969
},
7070
},
7171
});
72+
const version1WithDraft = Object.assign(new Version(), {
73+
...version1,
74+
versionhistory: createSuccessfulRemoteDataObject$(versionHistoryDraft),
75+
});
7276
const versions = [version1, version2];
7377
versionHistory.versions = createSuccessfulRemoteDataObject$(createPaginatedList(versions));
7478
const item1 = Object.assign(new Item(), {
@@ -190,21 +194,18 @@ describe('VersionHistoryDataService', () => {
190194
});
191195

192196
describe('hasDraftVersion$', () => {
193-
beforeEach(waitForAsync(() => {
194-
versionService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$<Version>(version1));
195-
}));
196197
it('should return false if draftVersion is false', fakeAsync(() => {
197-
versionService.getHistoryFromVersion.and.returnValue(of(versionHistory));
198+
versionService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$<Version>(version1));
198199
service.hasDraftVersion$('href').subscribe((res) => {
199200
expect(res).toBeFalse();
200201
});
201202
}));
203+
202204
it('should return true if draftVersion is true', fakeAsync(() => {
203-
versionService.getHistoryFromVersion.and.returnValue(of(versionHistoryDraft));
205+
versionService.findByHref.and.returnValue(createSuccessfulRemoteDataObject$<Version>(version1WithDraft));
204206
service.hasDraftVersion$('href').subscribe((res) => {
205207
expect(res).toBeTrue();
206208
});
207209
}));
208210
});
209-
210211
});

src/app/core/data/version-history-data.service.ts

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
import { HttpHeaders } from '@angular/common/http';
22
import { Injectable } from '@angular/core';
33
import {
4+
combineLatest,
45
Observable,
5-
of,
6+
of as observableOf,
67
} from 'rxjs';
78
import {
89
filter,
10+
find,
911
map,
1012
switchMap,
1113
take,
1214
} from 'rxjs/operators';
1315

14-
import { hasValueOperator } from '../../shared/empty.util';
16+
import {
17+
hasValue,
18+
hasValueOperator,
19+
} from '../../shared/empty.util';
1520
import { PaginationComponentOptions } from '../../shared/pagination/pagination-component-options.model';
1621
import { PaginatedSearchOptions } from '../../shared/search/models/paginated-search-options.model';
1722
import {
@@ -29,7 +34,6 @@ import {
2934
getFirstSucceededRemoteDataPayload,
3035
getRemoteDataPayload,
3136
} from '../shared/operators';
32-
import { sendRequest } from '../shared/request.operators';
3337
import { Version } from '../shared/version.model';
3438
import { VersionHistory } from '../shared/version-history.model';
3539
import { IdentifiableDataService } from './base/identifiable-data.service';
@@ -38,7 +42,6 @@ import { PaginatedList } from './paginated-list.model';
3842
import { RemoteData } from './remote-data';
3943
import { PostRequest } from './request.models';
4044
import { RequestService } from './request.service';
41-
import { RestRequest } from './rest-request.model';
4245
import { VersionDataService } from './version-data.service';
4346

4447
/**
@@ -100,19 +103,31 @@ export class VersionHistoryDataService extends IdentifiableDataService<VersionHi
100103
* @param summary the summary of the new version
101104
*/
102105
createVersion(itemHref: string, summary: string): Observable<RemoteData<Version>> {
106+
const requestId = this.requestService.generateRequestId();
103107
const requestOptions: HttpOptions = Object.create({});
104108
let requestHeaders = new HttpHeaders();
105109
requestHeaders = requestHeaders.append('Content-Type', 'text/uri-list');
106110
requestOptions.headers = requestHeaders;
107111

108-
return this.halService.getEndpoint(this.versionsEndpoint).pipe(
112+
this.halService.getEndpoint(this.versionsEndpoint).pipe(
109113
take(1),
110114
map((endpointUrl: string) => (summary?.length > 0) ? `${endpointUrl}?summary=${summary}` : `${endpointUrl}`),
111-
map((endpointURL: string) => new PostRequest(this.requestService.generateRequestId(), endpointURL, itemHref, requestOptions)),
112-
sendRequest(this.requestService),
113-
switchMap((restRequest: RestRequest) => this.rdbService.buildFromRequestUUID(restRequest.uuid)),
115+
find((href: string) => hasValue(href)),
116+
).subscribe((href) => {
117+
const request = new PostRequest(requestId, href, itemHref, requestOptions);
118+
if (hasValue(this.responseMsToLive)) {
119+
request.responseMsToLive = this.responseMsToLive;
120+
}
121+
122+
this.requestService.send(request);
123+
});
124+
125+
return this.rdbService.buildFromRequestUUIDAndAwait<Version>(requestId, (versionRD) => combineLatest([
126+
this.requestService.setStaleByHrefSubstring(versionRD.payload._links.self.href),
127+
this.requestService.setStaleByHrefSubstring(versionRD.payload._links.versionhistory.href),
128+
])).pipe(
114129
getFirstCompletedRemoteData(),
115-
) as Observable<RemoteData<Version>>;
130+
);
116131
}
117132

118133
/**
@@ -151,7 +166,7 @@ export class VersionHistoryDataService extends IdentifiableDataService<VersionHi
151166
switchMap((res) => res.versionhistory),
152167
getFirstSucceededRemoteDataPayload(),
153168
switchMap((versionHistoryRD) => this.getLatestVersionFromHistory$(versionHistoryRD)),
154-
) : of(null);
169+
) : observableOf(null);
155170
}
156171

157172
/**
@@ -162,8 +177,8 @@ export class VersionHistoryDataService extends IdentifiableDataService<VersionHi
162177
isLatest$(version: Version): Observable<boolean> {
163178
return version ? this.getLatestVersion$(version).pipe(
164179
take(1),
165-
switchMap((latestVersion) => of(version.version === latestVersion.version)),
166-
) : of(null);
180+
switchMap((latestVersion) => observableOf(version.version === latestVersion.version)),
181+
) : observableOf(null);
167182
}
168183

169184
/**
@@ -172,17 +187,22 @@ export class VersionHistoryDataService extends IdentifiableDataService<VersionHi
172187
* @returns `true` if a workspace item exists, `false` otherwise, or `null` if a version history does not exist
173188
*/
174189
hasDraftVersion$(versionHref: string): Observable<boolean> {
175-
return this.versionDataService.findByHref(versionHref, true, true, followLink('versionhistory')).pipe(
190+
return this.versionDataService.findByHref(versionHref, false, true, followLink('versionhistory')).pipe(
176191
getFirstCompletedRemoteData(),
177-
switchMap((res) => {
178-
if (res.hasSucceeded && !res.hasNoContent) {
179-
return of(res).pipe(
180-
getFirstSucceededRemoteDataPayload(),
181-
switchMap((version) => this.versionDataService.getHistoryFromVersion(version)),
182-
map((versionHistory) => versionHistory ? versionHistory.draftVersion : false),
192+
switchMap((versionRD: RemoteData<Version>) => {
193+
if (versionRD.hasSucceeded && !versionRD.hasNoContent) {
194+
return versionRD.payload.versionhistory.pipe(
195+
getFirstCompletedRemoteData(),
196+
map((versionHistoryRD: RemoteData<VersionHistory>) => {
197+
if (versionHistoryRD.hasSucceeded && !versionHistoryRD.hasNoContent) {
198+
return versionHistoryRD.payload.draftVersion;
199+
} else {
200+
return false;
201+
}
202+
}),
183203
);
184204
} else {
185-
return of(false);
205+
return observableOf(false);
186206
}
187207
}),
188208
);

src/app/core/submission/workflowitem-data.service.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,12 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
2929
import { NoContent } from '../shared/NoContent.model';
3030
import { getFirstCompletedRemoteData } from '../shared/operators';
3131
import { WorkflowItem } from './models/workflowitem.model';
32-
import { WorkspaceItem } from './models/workspaceitem.model';
3332

3433
/**
3534
* A service that provides methods to make REST requests with workflow items endpoint.
3635
*/
3736
@Injectable({ providedIn: 'root' })
3837
export class WorkflowItemDataService extends IdentifiableDataService<WorkflowItem> implements SearchData<WorkflowItem>, DeleteData<WorkflowItem> {
39-
protected linkPath = 'workflowitems';
4038
protected searchByItemLinkPath = 'item';
4139
protected responseMsToLive = 10 * 1000;
4240

@@ -50,7 +48,7 @@ export class WorkflowItemDataService extends IdentifiableDataService<WorkflowIte
5048
protected halService: HALEndpointService,
5149
protected notificationsService: NotificationsService,
5250
) {
53-
super('workspaceitems', requestService, rdbService, objectCache, halService);
51+
super('workflowitems', requestService, rdbService, objectCache, halService);
5452

5553
this.searchData = new SearchDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, this.responseMsToLive);
5654
this.deleteData = new DeleteDataImpl(this.linkPath, requestService, rdbService, objectCache, halService, notificationsService, this.responseMsToLive, this.constructIdEndpoint);
@@ -113,7 +111,7 @@ export class WorkflowItemDataService extends IdentifiableDataService<WorkflowIte
113111
* @param options The {@link FindListOptions} object
114112
* @param linksToFollow List of {@link FollowLinkConfig} that indicate which {@link HALLink}s should be automatically resolved
115113
*/
116-
public findByItem(uuid: string, useCachedVersionIfAvailable = false, reRequestOnStale = true, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<WorkspaceItem>> {
114+
public findByItem(uuid: string, useCachedVersionIfAvailable = false, reRequestOnStale = true, options: FindListOptions = {}, ...linksToFollow: FollowLinkConfig<WorkflowItem>[]): Observable<RemoteData<WorkflowItem>> {
117115
const findListOptions = new FindListOptions();
118116
findListOptions.searchParams = [new RequestParam('uuid', uuid)];
119117
const href$ = this.searchData.getSearchByHref(this.searchByItemLinkPath, findListOptions, ...linksToFollow);
@@ -134,7 +132,7 @@ export class WorkflowItemDataService extends IdentifiableDataService<WorkflowIte
134132
* @return {Observable<RemoteData<PaginatedList<T>>}
135133
* Return an observable that emits response from the server
136134
*/
137-
public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<WorkspaceItem>[]): Observable<RemoteData<PaginatedList<WorkspaceItem>>> {
135+
public searchBy(searchMethod: string, options?: FindListOptions, useCachedVersionIfAvailable?: boolean, reRequestOnStale?: boolean, ...linksToFollow: FollowLinkConfig<WorkflowItem>[]): Observable<RemoteData<PaginatedList<WorkflowItem>>> {
138136
return this.searchData.searchBy(searchMethod, options, useCachedVersionIfAvailable, reRequestOnStale, ...linksToFollow);
139137
}
140138

src/app/shared/dso-page/dso-versioning-modal-service/dso-versioning-modal.service.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
} from 'rxjs';
88
import {
99
map,
10-
startWith,
1110
switchMap,
1211
tap,
1312
} from 'rxjs/operators';
@@ -96,7 +95,6 @@ export class DsoVersioningModalService {
9695
// button is disabled if hasDraftVersion = true, and enabled if hasDraftVersion = false or null
9796
// (hasDraftVersion is null when a version history does not exist)
9897
map((res) => Boolean(res)),
99-
startWith(true),
10098
);
10199
}
102100

src/app/shared/mydspace-actions/claimed-task/approve/claimed-task-actions-approve.component.spec.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,11 @@ import {
1414
TranslateLoader,
1515
TranslateModule,
1616
} from '@ngx-translate/core';
17-
import {
18-
of as observableOf,
19-
of,
20-
} from 'rxjs';
17+
import { of as observableOf } from 'rxjs';
2118

2219
import { RequestService } from '../../../../core/data/request.service';
2320
import { SearchService } from '../../../../core/shared/search/search.service';
21+
import { WorkflowItemDataService } from '../../../../core/submission/workflowitem-data.service';
2422
import { ClaimedTaskDataService } from '../../../../core/tasks/claimed-task-data.service';
2523
import { ClaimedTask } from '../../../../core/tasks/models/claimed-task-object.model';
2624
import { ProcessTaskResponse } from '../../../../core/tasks/models/process-task-response';
@@ -41,6 +39,7 @@ const searchService = getMockSearchService();
4139
const requestService = getMockRequestService();
4240

4341
let mockPoolTaskDataService: PoolTaskDataService;
42+
let mockWorkflowItemDataService: WorkflowItemDataService;
4443

4544
describe('ClaimedTaskActionsApproveComponent', () => {
4645
const object = Object.assign(new ClaimedTask(), { id: 'claimed-task-1' });
@@ -50,6 +49,10 @@ describe('ClaimedTaskActionsApproveComponent', () => {
5049

5150
beforeEach(waitForAsync(() => {
5251
mockPoolTaskDataService = new PoolTaskDataService(null, null, null, null);
52+
mockWorkflowItemDataService = jasmine.createSpyObj('WorkflowItemDataService', {
53+
'invalidateByHref': observableOf(false),
54+
});
55+
5356
TestBed.configureTestingModule({
5457
imports: [
5558
TranslateModule.forRoot({
@@ -68,6 +71,7 @@ describe('ClaimedTaskActionsApproveComponent', () => {
6871
{ provide: SearchService, useValue: searchService },
6972
{ provide: RequestService, useValue: requestService },
7073
{ provide: PoolTaskDataService, useValue: mockPoolTaskDataService },
74+
{ provide: WorkflowItemDataService, useValue: mockWorkflowItemDataService },
7175
],
7276
schemas: [NO_ERRORS_SCHEMA],
7377
}).overrideComponent(ClaimedTaskActionsApproveComponent, {
@@ -103,7 +107,7 @@ describe('ClaimedTaskActionsApproveComponent', () => {
103107

104108
beforeEach(() => {
105109
spyOn(component.processCompleted, 'emit');
106-
spyOn(component, 'startActionExecution').and.returnValue(of(null));
110+
spyOn(component, 'startActionExecution').and.returnValue(observableOf(null));
107111

108112
expectedBody = {
109113
[component.option]: 'true',

0 commit comments

Comments
 (0)