Skip to content

Commit

Permalink
Merge pull request #618 from atmire/Manage-account-profile
Browse files Browse the repository at this point in the history
Manage account profile
  • Loading branch information
tdonohue committed Mar 18, 2020
2 parents 8f1df19 + a8636b0 commit 7c4a84b
Show file tree
Hide file tree
Showing 23 changed files with 1,065 additions and 29 deletions.
60 changes: 60 additions & 0 deletions resources/i18n/en.json5
Expand Up @@ -1377,6 +1377,8 @@

"nav.mydspace": "MyDSpace",

"nav.profile": "Profile",

"nav.search": "Search",

"nav.statistics.header": "Statistics",
Expand Down Expand Up @@ -1435,6 +1437,64 @@



"profile.breadcrumbs": "Update Profile",

"profile.card.identify": "Identify",

"profile.card.security": "Security",

"profile.form.submit": "Update Profile",

"profile.groups.head": "Authorization groups you belong to",

"profile.head": "Update Profile",

"profile.metadata.form.error.firstname.required": "First Name is required",

"profile.metadata.form.error.lastname.required": "Last Name is required",

"profile.metadata.form.label.email": "Email Address",

"profile.metadata.form.label.firstname": "First Name",

"profile.metadata.form.label.language": "Language",

"profile.metadata.form.label.lastname": "Last Name",

"profile.metadata.form.label.phone": "Contact Telephone",

"profile.metadata.form.notifications.success.content": "Your changes to the profile were saved.",

"profile.metadata.form.notifications.success.title": "Profile saved",

"profile.notifications.warning.no-changes.content": "No changes were made to the Profile.",

"profile.notifications.warning.no-changes.title": "No changes",

"profile.security.form.error.matching-passwords": "The passwords do not match.",

"profile.security.form.error.password-length": "The password should be at least 6 characters long.",

"profile.security.form.info": "Optionally, you can enter a new password in the box below, and confirm it by typing it again into the second box. It should be at least six characters long.",

"profile.security.form.label.password": "Password",

"profile.security.form.label.passwordrepeat": "Retype to confirm",

"profile.security.form.notifications.success.content": "Your changes to the password were saved.",

"profile.security.form.notifications.success.title": "Password saved",

"profile.security.form.notifications.error.title": "Error changing passwords",

"profile.security.form.notifications.error.not-long-enough": "The password has to be at least 6 characters long.",

"profile.security.form.notifications.error.not-same": "The provided passwords are not the same.",

"profile.title": "Update Profile",



"project.listelement.badge": "Research Project",

"project.page.contributor": "Contributors",
Expand Down
7 changes: 7 additions & 0 deletions src/app/app-routing.module.ts
Expand Up @@ -35,6 +35,12 @@ export function getAdminModulePath() {
return `/${ADMIN_MODULE_PATH}`;
}

const PROFILE_MODULE_PATH = 'profile';

export function getProfileModulePath() {
return `/${PROFILE_MODULE_PATH}`;
}

export function getDSOPath(dso: DSpaceObject): string {
switch ((dso as any).type) {
case Community.type.value:
Expand Down Expand Up @@ -66,6 +72,7 @@ export function getDSOPath(dso: DSpaceObject): string {
{ path: 'submit', loadChildren: './+submit-page/submit-page.module#SubmitPageModule' },
{ path: 'workspaceitems', loadChildren: './+workspaceitems-edit-page/workspaceitems-edit-page.module#WorkspaceitemsEditPageModule' },
{ path: 'workflowitems', loadChildren: './+workflowitems-edit-page/workflowitems-edit-page.module#WorkflowItemsEditPageModule' },
{ path: PROFILE_MODULE_PATH, loadChildren: './profile-page/profile-page.module#ProfilePageModule', canActivate: [AuthenticatedGuard] },
{ path: '**', pathMatch: 'full', component: PageNotFoundComponent },
],
{
Expand Down
4 changes: 2 additions & 2 deletions src/app/core/auth/auth.service.ts
Expand Up @@ -23,7 +23,7 @@ import { NativeWindowRef, NativeWindowService } from '../services/window.service
import { Base64EncodeUrl } from '../../shared/utils/encode-decode.util';
import { RouteService } from '../services/route.service';
import { EPersonDataService } from '../eperson/eperson-data.service';
import { getFirstSucceededRemoteDataPayload } from '../shared/operators';
import { getAllSucceededRemoteDataPayload, getFirstSucceededRemoteDataPayload } from '../shared/operators';

export const LOGIN_ROUTE = '/login';
export const LOGOUT_ROUTE = '/logout';
Expand Down Expand Up @@ -149,7 +149,7 @@ export class AuthService {
*/
public retrieveAuthenticatedUserByHref(userHref: string): Observable<EPerson> {
return this.epersonService.findByHref(userHref).pipe(
getFirstSucceededRemoteDataPayload()
getAllSucceededRemoteDataPayload()
)
}

Expand Down
8 changes: 8 additions & 0 deletions src/app/core/cache/server-sync-buffer.effects.spec.ts
Expand Up @@ -51,6 +51,14 @@ describe('ServerSyncBufferEffects', () => {
_links: { self: { href: link } }
});
return observableOf(object);
},
getBySelfLink: (link) => {
const object = Object.assign(new DSpaceObject(), {
_links: {
self: { href: link }
}
});
return observableOf(object);
}
}
},
Expand Down
22 changes: 13 additions & 9 deletions src/app/core/cache/server-sync-buffer.effects.ts
Expand Up @@ -16,13 +16,15 @@ import { Action, createSelector, MemoizedSelector, select, Store } from '@ngrx/s
import { ServerSyncBufferEntry, ServerSyncBufferState } from './server-sync-buffer.reducer';
import { combineLatest as observableCombineLatest, of as observableOf } from 'rxjs';
import { RequestService } from '../data/request.service';
import { PutRequest } from '../data/request.models';
import { PatchRequest, PutRequest } from '../data/request.models';
import { ObjectCacheService } from './object-cache.service';
import { ApplyPatchObjectCacheAction } from './object-cache.actions';
import { GenericConstructor } from '../shared/generic-constructor';
import { hasValue, isNotEmpty, isNotUndefined } from '../../shared/empty.util';
import { Observable } from 'rxjs/internal/Observable';
import { RestRequestMethod } from '../data/rest-request-method';
import { ObjectCacheEntry } from './object-cache.reducer';
import { Operation } from 'fast-json-patch';

@Injectable()
export class ServerSyncBufferEffects {
Expand Down Expand Up @@ -96,17 +98,19 @@ export class ServerSyncBufferEffects {
* @returns {Observable<Action>} ApplyPatchObjectCacheAction to be dispatched
*/
private applyPatch(href: string): Observable<Action> {
const patchObject = this.objectCache.getObjectBySelfLink(href).pipe(take(1));
const patchObject = this.objectCache.getBySelfLink(href).pipe(take(1));

return patchObject.pipe(
map((object) => {
const serializedObject = new DSpaceSerializer(object.constructor as GenericConstructor<{}>).serialize(object);

this.requestService.configure(new PutRequest(this.requestService.generateRequestId(), href, serializedObject));

return new ApplyPatchObjectCacheAction(href)
map((entry: ObjectCacheEntry) => {
if (isNotEmpty(entry.patches)) {
const flatPatch: Operation[] = [].concat(...entry.patches.map((patch) => patch.operations));
if (isNotEmpty(flatPatch)) {
this.requestService.configure(new PatchRequest(this.requestService.generateRequestId(), href, flatPatch));
}
}
return new ApplyPatchObjectCacheAction(href);
})
)
);
}

constructor(private actions$: Actions,
Expand Down
29 changes: 18 additions & 11 deletions src/app/core/data/data.service.spec.ts
Expand Up @@ -16,8 +16,10 @@ import { HALEndpointService } from '../shared/hal-endpoint.service';
import { Item } from '../shared/item.model';
import { ChangeAnalyzer } from './change-analyzer';
import { DataService } from './data.service';
import { FindListOptions } from './request.models';
import { FindListOptions, PatchRequest } from './request.models';
import { RequestService } from './request.service';
import { getMockRequestService } from '../../shared/mocks/mock-request.service';
import { HALEndpointServiceStub } from '../../shared/testing/hal-endpoint-service-stub';

const endpoint = 'https://rest.api/core';

Expand Down Expand Up @@ -53,8 +55,8 @@ class DummyChangeAnalyzer implements ChangeAnalyzer<Item> {
describe('DataService', () => {
let service: TestService;
let options: FindListOptions;
const requestService = { generateRequestId: () => uuidv4() } as RequestService;
const halService = {} as HALEndpointService;
const requestService = getMockRequestService();
const halService = new HALEndpointServiceStub('url') as any;
const rdbService = {} as RemoteDataBuildService;
const notificationsService = {} as NotificationsService;
const http = {} as HttpClient;
Expand Down Expand Up @@ -285,18 +287,23 @@ describe('DataService', () => {
});

describe('patch', () => {
let operations;
let selfLink;
const dso = {
uuid: 'dso-uuid'
};
const operations = [
Object.assign({
op: 'move',
from: '/1',
path: '/5'
}) as Operation
];

beforeEach(() => {
operations = [{ op: 'replace', path: '/metadata/dc.title', value: 'random string' } as Operation];
selfLink = 'https://rest.api/endpoint/1698f1d3-be98-4c51-9fd8-6bfedcbd59b7';
spyOn(objectCache, 'addPatch');
service.patch(dso, operations);
});

it('should call addPatch on the object cache with the right parameters', () => {
service.patch(selfLink, operations);
expect(objectCache.addPatch).toHaveBeenCalledWith(selfLink, operations);
it('should configure a PatchRequest', () => {
expect(requestService.configure).toHaveBeenCalledWith(jasmine.any(PatchRequest));
});
});

Expand Down
26 changes: 21 additions & 5 deletions src/app/core/data/data.service.ts
Expand Up @@ -44,7 +44,7 @@ import {
FindByIDRequest,
FindListOptions,
FindListRequest,
GetRequest
GetRequest, PatchRequest
} from './request.models';
import { RequestEntry } from './request.reducer';
import { RequestService } from './request.service';
Expand Down Expand Up @@ -329,12 +329,28 @@ export abstract class DataService<T extends CacheableObject> {
}

/**
* Add a new patch to the object cache to a specified object
* @param {string} href The selflink of the object that will be patched
* Send a patch request for a specified object
* @param {T} dso The object to send a patch request for
* @param {Operation[]} operations The patch operations to be performed
*/
patch(href: string, operations: Operation[]) {
this.objectCache.addPatch(href, operations);
patch(dso: T, operations: Operation[]): Observable<RestResponse> {
const requestId = this.requestService.generateRequestId();

const hrefObs = this.halService.getEndpoint(this.linkPath).pipe(
map((endpoint: string) => this.getIDHref(endpoint, dso.uuid)));

hrefObs.pipe(
find((href: string) => hasValue(href)),
map((href: string) => {
const request = new PatchRequest(requestId, href, operations);
this.requestService.configure(request);
})
).subscribe();

return this.requestService.getByUUID(requestId).pipe(
find((request: RequestEntry) => request.completed),
map((request: RequestEntry) => request.response)
);
}

/**
Expand Down
19 changes: 18 additions & 1 deletion src/app/core/data/dso-change-analyzer.service.ts
Expand Up @@ -3,6 +3,8 @@ import { compare } from 'fast-json-patch';
import { ChangeAnalyzer } from './change-analyzer';
import { Injectable } from '@angular/core';
import { DSpaceObject } from '../shared/dspace-object.model';
import { MetadataMap } from '../shared/metadata.models';
import { cloneDeep } from 'lodash';

/**
* A class to determine what differs between two
Expand All @@ -21,6 +23,21 @@ export class DSOChangeAnalyzer<T extends DSpaceObject> implements ChangeAnalyzer
* The second object to compare
*/
diff(object1: DSpaceObject, object2: DSpaceObject): Operation[] {
return compare(object1.metadata, object2.metadata).map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path }));
return compare(this.filterUUIDsFromMetadata(object1.metadata), this.filterUUIDsFromMetadata(object2.metadata))
.map((operation: Operation) => Object.assign({}, operation, { path: '/metadata' + operation.path }));
}

/**
* Filter the UUIDs out of a MetadataMap
* @param metadata
*/
filterUUIDsFromMetadata(metadata: MetadataMap): MetadataMap {
const result = cloneDeep(metadata);
for (const key of Object.keys(result)) {
for (const metadataValue of result[key]) {
metadataValue.uuid = undefined;
}
}
return result;
}
}
2 changes: 2 additions & 0 deletions src/app/core/data/request.models.ts
Expand Up @@ -119,6 +119,8 @@ export class HeadRequest extends RestRequest {
}

export class PatchRequest extends RestRequest {
public responseMsToLive = 60 * 15 * 1000;

constructor(
public uuid: string,
public href: string,
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/eperson/eperson-data.service.ts
Expand Up @@ -20,7 +20,7 @@ import { EPERSON } from './models/eperson.resource-type';
@dataService(EPERSON)
export class EPersonDataService extends DataService<EPerson> {

protected linkPath: 'epersons';
protected linkPath = 'epersons';

constructor(
protected requestService: RequestService,
Expand Down
@@ -0,0 +1,6 @@
<ds-form *ngIf="formModel"
[formId]="'profile-page-metadata-form-id'"
[formModel]="formModel"
[formGroup]="formGroup"
[displaySubmit]="false">
</ds-form>

0 comments on commit 7c4a84b

Please sign in to comment.