Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CST-12044 visualize the primary bitstream & CST-12043 primary bitstream flag #2631

Merged
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a55eb97
[CST-12043] fix: normalization for boolean
vNovski Oct 20, 2023
ffdeca6
[CST-12043] feature: add primary bitstream switch
vNovski Oct 20, 2023
bbc8b6a
[CST-12043] unit tests
vNovski Oct 23, 2023
1632795
Merge commit 'bbc8b6ac8a15bdd619d4f7dac90655ab52dbad2c' into CST-1204…
vNovski Oct 23, 2023
ae93cc0
[CST-12043] fix: translation for custom switch
vNovski Oct 24, 2023
6fc75a5
[CST-12043] ref: add and update documentation
vNovski Oct 24, 2023
7c723ca
[CST-12043] fix: redundant space
vNovski Oct 24, 2023
f006bb3
[CST-12043] fix: dependencies for unit test
vNovski Oct 25, 2023
7cee04d
[CST-12044] feature: badge for primary bitstream
vNovski Oct 25, 2023
c644cc5
[CST-12044] feature: add code coverage flag
vNovski Oct 25, 2023
462aafb
[CST-12044] feature: add translation for badge
vNovski Oct 26, 2023
027a6b0
Merged cdl7 into CST-12043-primary-bitstream-flag
atarix83 Oct 27, 2023
9322129
Merge branch 'CST-12043-primary-bitstream-flag' into CST-12044-visual…
atarix83 Oct 27, 2023
ca64bac
[CST-12043] feature: add title
vNovski Oct 30, 2023
823f728
Merge remote-tracking branch '4cience-origin/CST-12043-primary-bitstr…
vNovski Oct 30, 2023
0f1f4e8
[CST-12043] feature: add label position
vNovski Oct 31, 2023
41eb34b
[CST-12043] Add class for element container
atarix83 Nov 3, 2023
034d7dd
[CST-12043] Remove ng-deep styles
atarix83 Nov 3, 2023
9ce9606
[CST-12043] Fix layout
atarix83 Nov 3, 2023
f742039
Merge branch 'CST-12043-primary-bitstream-flag' into CST-12044-visual…
atarix83 Nov 3, 2023
71af428
[CST-12044] refactor: styles
vNovski Nov 10, 2023
b865259
[CST-12044] refactor: code
vNovski Feb 6, 2024
18e62f4
[CST-12044] fix accept/reject the license bug
vNovski Feb 6, 2024
c596b6c
[CST-12044] refactor: code
vNovski Feb 7, 2024
02baf86
[CST-12044] removed aria hidden attribute
vNovski Feb 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"build:prod": "yarn run build:ssr",
"build:ssr": "ng build --configuration production && ng run dspace-angular:server:production",
"test": "ng test --source-map=true --watch=false --configuration test",
"test:watch": "nodemon --exec \"ng test --source-map=true --watch=true --configuration test\"",
"test:watch": "nodemon --exec \"ng test --source-map=true --code-coverage --watch=true --configuration test\"",
vNovski marked this conversation as resolved.
Show resolved Hide resolved
"test:headless": "ng test --source-map=true --watch=false --configuration test --browsers=ChromeHeadless --code-coverage",
"lint": "ng lint",
"lint-fix": "ng lint --fix=true",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +0,0 @@
:host {
::ng-deep {
.switch {
position: absolute;
top: calc(var(--bs-spacer) * 2.5);
}
}
}
:host ::ng-deep ds-dynamic-form-control-container > div > label {
margin-top: 1.75rem;
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,35 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy, OnIni
import { Bitstream } from '../../core/shared/bitstream.model';
import { ActivatedRoute, Router } from '@angular/router';
import { filter, map, switchMap, tap } from 'rxjs/operators';
import { combineLatest, combineLatest as observableCombineLatest, Observable, of as observableOf, Subscription } from 'rxjs';
import { DynamicFormControlModel, DynamicFormGroupModel, DynamicFormLayout, DynamicFormService, DynamicInputModel, DynamicSelectModel } from '@ng-dynamic-forms/core';
import {
combineLatest,
combineLatest as observableCombineLatest,
Observable,
of as observableOf,
Subscription
} from 'rxjs';
import {
DynamicFormControlModel,
DynamicFormGroupModel,
DynamicFormLayout,
DynamicFormService,
DynamicInputModel,
DynamicSelectModel
} from '@ng-dynamic-forms/core';
import { UntypedFormGroup } from '@angular/forms';
import { TranslateService } from '@ngx-translate/core';
import { DynamicCustomSwitchModel } from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
import {
DynamicCustomSwitchModel
} from '../../shared/form/builder/ds-dynamic-form-ui/models/custom-switch/custom-switch.model';
import cloneDeep from 'lodash/cloneDeep';
import { BitstreamDataService } from '../../core/data/bitstream-data.service';
import { getAllSucceededRemoteDataPayload, getFirstCompletedRemoteData, getFirstSucceededRemoteData, getFirstSucceededRemoteDataPayload, getRemoteDataPayload } from '../../core/shared/operators';
import {
getAllSucceededRemoteDataPayload,
getFirstCompletedRemoteData,
getFirstSucceededRemoteData,
getFirstSucceededRemoteDataPayload,
getRemoteDataPayload
} from '../../core/shared/operators';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { BitstreamFormatDataService } from '../../core/data/bitstream-format-data.service';
import { BitstreamFormat } from '../../core/shared/bitstream-format.model';
Expand Down Expand Up @@ -245,7 +266,7 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
/**
* All input models in a simple array for easier iterations
*/
inputModels = [this.fileNameModel, this.primaryBitstreamModel, this.descriptionModel, this.selectedFormatModel,
inputModels = [this.primaryBitstreamModel, this.fileNameModel, this.descriptionModel, this.selectedFormatModel,
this.newFormatModel];

/**
Expand All @@ -256,8 +277,8 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
new DynamicFormGroupModel({
id: 'fileNamePrimaryContainer',
group: [
this.fileNameModel,
this.primaryBitstreamModel
this.primaryBitstreamModel,
this.fileNameModel
]
}, {
grid: {
Expand Down Expand Up @@ -295,7 +316,10 @@ export class EditBitstreamPageComponent implements OnInit, OnDestroy {
},
primaryBitstream: {
grid: {
host: 'col col-sm-4 d-inline-block switch border-0'
container: 'col-12'
},
element: {
container: 'text-right'
}
},
description: {
Expand Down
34 changes: 34 additions & 0 deletions src/app/core/data/bitstream-data.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import { NotificationsService } from '../../shared/notifications/notifications.s
import objectContaining = jasmine.objectContaining;
import { RemoteData } from './remote-data';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { BundleDataService } from './bundle-data.service';
import { ItemMock } from 'src/app/shared/mocks/item.mock';
import { createFailedRemoteDataObject, createSuccessfulRemoteDataObject } from 'src/app/shared/remote-data.utils';
import { Bundle } from '../shared/bundle.model';
import { cold } from 'jasmine-marbles';

describe('BitstreamDataService', () => {
let service: BitstreamDataService;
Expand All @@ -29,6 +34,7 @@ describe('BitstreamDataService', () => {
let halService: HALEndpointService;
let bitstreamFormatService: BitstreamFormatDataService;
let rdbService: RemoteDataBuildService;
let bundleDataService: BundleDataService;
const bitstreamFormatHref = 'rest-api/bitstreamformats';

const bitstream1 = Object.assign(new Bitstream(), {
Expand Down Expand Up @@ -62,6 +68,7 @@ describe('BitstreamDataService', () => {
bitstreamFormatService = jasmine.createSpyObj('bistreamFormatService', {
getBrowseEndpoint: observableOf(bitstreamFormatHref)
});

rdbService = getMockRemoteDataBuildService();

TestBed.configureTestingModule({
Expand All @@ -76,6 +83,7 @@ describe('BitstreamDataService', () => {
],
});
service = TestBed.inject(BitstreamDataService);
bundleDataService = TestBed.inject(BundleDataService);
});

describe('composition', () => {
Expand Down Expand Up @@ -118,6 +126,32 @@ describe('BitstreamDataService', () => {
expect(service.invalidateByHref).toHaveBeenCalledWith('fake-bitstream1-self');
});

describe('findPrimaryBitstreamByItemAndName', () => {
it('should return primary bitstream', () => {
const exprected$ = cold('(a|)', { a: bitstream1} );
const bundle = Object.assign(new Bundle(), {
primaryBitstream: observableOf(createSuccessfulRemoteDataObject(bitstream1)),
});
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createSuccessfulRemoteDataObject(bundle)));
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
});

it('should return null if primary bitstream has not be succeeded ', () => {
const exprected$ = cold('(a|)', { a: null} );
const bundle = Object.assign(new Bundle(), {
primaryBitstream: observableOf(createFailedRemoteDataObject()),
});
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createSuccessfulRemoteDataObject(bundle)));
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
});

it('should return EMPTY if nothing where found', () => {
const exprected$ = cold('(|)', {} );
spyOn(bundleDataService, 'findByItemAndName').and.returnValue(observableOf(createFailedRemoteDataObject<Bundle>()));
expect(service.findPrimaryBitstreamByItemAndName(ItemMock, 'ORIGINAL')).toBeObservable(exprected$);
});
});

it('should be able to delete multiple bitstreams', () => {
service.removeMultiple([bitstream1, bitstream2]);

Expand Down
36 changes: 34 additions & 2 deletions src/app/core/data/bitstream-data.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { combineLatest as observableCombineLatest, Observable } from 'rxjs';
import { combineLatest as observableCombineLatest, Observable, EMPTY } from 'rxjs';
import { find, map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../shared/empty.util';
import { FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { FollowLinkConfig, followLink } from '../../shared/utils/follow-link-config.model';
import { RemoteDataBuildService } from '../cache/builders/remote-data-build.service';
import { ObjectCacheService } from '../cache/object-cache.service';
import { Bitstream } from '../shared/bitstream.model';
Expand Down Expand Up @@ -34,6 +34,7 @@ import { NoContent } from '../shared/NoContent.model';
import { IdentifiableDataService } from './base/identifiable-data.service';
import { dataService } from './base/data-service.decorator';
import { Operation, RemoveOperation } from 'fast-json-patch';
import { getFirstCompletedRemoteData } from '../shared/operators';

/**
* A service to retrieve {@link Bitstream}s from the REST API
Expand Down Expand Up @@ -201,6 +202,37 @@ export class BitstreamDataService extends IdentifiableDataService<Bitstream> imp
return this.searchData.getSearchByHref(searchMethod, options, ...linksToFollow);
}


/**
*
* Make a request to get primary bitstream
* in all current use cases, and having it simplifies this method
*
* @param item the {@link Item} the {@link Bundle} is a part of
* @param bundleName the name of the {@link Bundle} we want to find
* {@link Bitstream}s for
* @param useCachedVersionIfAvailable If this is true, the request will only be sent if there's
* no valid cached version. Defaults to true
* @param reRequestOnStale Whether or not the request should automatically be re-
* requested after the response becomes stale
* @return {Observable<Bitstream | null>}
* Return an observable that constains primary bitstream information or null
*/
public findPrimaryBitstreamByItemAndName(item: Item, bundleName: string, useCachedVersionIfAvailable = true, reRequestOnStale = true): Observable<Bitstream | null> {
return this.bundleService.findByItemAndName(item, bundleName, useCachedVersionIfAvailable, reRequestOnStale, followLink('primaryBitstream')).pipe(
getFirstCompletedRemoteData(),
switchMap((rd: RemoteData<Bundle>) => {
if (!rd.hasSucceeded) {
return EMPTY;
}
return rd.payload.primaryBitstream.pipe(
getFirstCompletedRemoteData(),
map((rdb: RemoteData<Bitstream>) => rdb.hasSucceeded ? rdb.payload : null)
);
})
);
}

/**
* Make a new FindListRequest with given search method
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { WorkspaceitemSectionUploadFileObject } from './workspaceitem-section-up
* An interface to represent submission's upload section data.
*/
export interface WorkspaceitemSectionUploadObject {

/**
* Primary bitstream flag
*/
primary: string | null;
/**
* A list of [[WorkspaceitemSectionUploadFileObject]]
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<ds-metadata-field-wrapper *ngIf="bitstreams?.length > 0" [label]="label | translate">
<div class="file-section">
<ds-themed-file-download-link *ngFor="let file of bitstreams; let last=last;" [bitstream]="file" [item]="item">
<span>{{ dsoNameService.getName(file) }}</span>
<span>
<span *ngIf="primaryBitsreamId === file.id" class="badge badge-primary">{{ 'item.page.bitstreams.primary' | translate }}</span>
{{ dsoNameService.getName(file) }}
</span>
<span> ({{(file?.sizeBytes) | dsFileSize }})</span>
<span *ngIf="!last" innerHTML="{{separator}}"></span>
</ds-themed-file-download-link>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ describe('FileSectionComponent', () => {
let fixture: ComponentFixture<FileSectionComponent>;

const bitstreamDataService = jasmine.createSpyObj('bitstreamDataService', {
findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([]))
findAllByItemAndBundleName: createSuccessfulRemoteDataObject$(createPaginatedList([])),
findPrimaryBitstreamByItemAndName: observableOf(null)
});

const mockBitstream: Bitstream = Object.assign(new Bitstream(),
Expand Down Expand Up @@ -81,6 +82,20 @@ describe('FileSectionComponent', () => {
fixture.detectChanges();
}));

it('should set the id of primary bitstream', () => {
comp.primaryBitsreamId = undefined;
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(observableOf(mockBitstream));
comp.ngOnInit();
expect(comp.primaryBitsreamId).toBe(mockBitstream.id);
});

it('should not set the id of primary bitstream', () => {
comp.primaryBitsreamId = undefined;
bitstreamDataService.findPrimaryBitstreamByItemAndName.and.returnValue(observableOf(null));
comp.ngOnInit();
expect(comp.primaryBitsreamId).toBeUndefined();
});

describe('when the bitstreams are loading', () => {
beforeEach(() => {
comp.bitstreams$.next([mockBitstream]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export class FileSectionComponent implements OnInit {

pageSize: number;

primaryBitsreamId: string;

constructor(
protected bitstreamDataService: BitstreamDataService,
protected notificationsService: NotificationsService,
Expand All @@ -50,9 +52,19 @@ export class FileSectionComponent implements OnInit {
}

ngOnInit(): void {
this.getPrimaryBitstreamId();
this.getNextPage();
}

private getPrimaryBitstreamId() {
this.bitstreamDataService.findPrimaryBitstreamByItemAndName(this.item, 'ORIGINAL', true, true).subscribe((primaryBitstream: Bitstream | null) => {
if (!primaryBitstream) {
return;
}
this.primaryBitsreamId = primaryBitstream?.id;
});
}

/**
* This method will retrieve the next page of Bitstreams from the external BitstreamDataService call.
* It'll retrieve the currentPage from the class variables and it'll add the next page of bitstreams with the
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div [formGroup]="group" class="form-check custom-control custom-switch" [class.disabled]="model.disabled">
<div [formGroup]="group" [ngClass]="getClass('element', 'container')" class="form-check custom-control custom-switch" [class.disabled]="model.disabled">
<input type="checkbox" class="form-check-input custom-control-input"
[checked]="model.checked"
[class.is-invalid]="showErrorMessages"
Expand All @@ -14,7 +14,7 @@
(change)="onChange($event)"
(focus)="onFocus($event)"/>
<label class="form-check-label custom-control-label" [for]="bindId && model.id">
<span [innerHTML]="model.label"
<span [innerHTML]="model.label | translate"
[ngClass]="[getClass('element', 'label'), getClass('grid', 'label')]"></span>
</label>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
div.custom-switch {
&.custom-control-right {
margin-left: 0;
margin-right: 0;

&::after {
right: -1.5rem;
left: auto;
}

&::before {
right: -2.35rem;
left: auto;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { DynamicFormsCoreModule, DynamicFormService } from '@ng-dynamic-forms/core';
import { UntypedFormGroup, ReactiveFormsModule } from '@angular/forms';
import { ComponentFixture, inject, TestBed, waitForAsync } from '@angular/core/testing';
import { DebugElement } from '@angular/core';
import { DebugElement} from '@angular/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { By } from '@angular/platform-browser';
import { DynamicCustomSwitchModel } from './custom-switch.model';
import { CustomSwitchComponent } from './custom-switch.component';
import { TranslateModule } from '@ngx-translate/core';

describe('CustomSwitchComponent', () => {

Expand All @@ -20,9 +21,10 @@ describe('CustomSwitchComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
ReactiveFormsModule,
NoopAnimationsModule,
DynamicFormsCoreModule.forRoot()
DynamicFormsCoreModule.forRoot(),
],
declarations: [CustomSwitchComponent]

Expand Down
1 change: 1 addition & 0 deletions src/app/shared/form/builder/form-builder.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
return new FormFieldMetadataValueObject((controlValue as any).value, controlLanguage, authority, (controlValue as any).display, place, (controlValue as any).confidence);
}
}
return controlValue;

Check warning on line 199 in src/app/shared/form/builder/form-builder.service.ts

View check run for this annotation

Codecov / codecov/patch

src/app/shared/form/builder/form-builder.service.ts#L199

Added line #L199 was not covered by tests
};

const iterateControlModels = (findGroupModel: DynamicFormControlModel[], controlModelIndex: number = 0): void => {
Expand Down
3 changes: 3 additions & 0 deletions src/app/shared/mocks/section-upload.service.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { SubmissionFormsConfigDataService } from '../../core/config/submission-f
*/
export function getMockSectionUploadService(): SubmissionFormsConfigDataService {
return jasmine.createSpyObj('SectionUploadService', {
updatePrimaryBitstreamOperation: jasmine.createSpy('updatePrimaryBitstreamOperation'),
updateFilePrimaryBitstream: jasmine.createSpy('updateFilePrimaryBitstream'),
getUploadedFilesData: jasmine.createSpy('getUploadedFilesData'),
getUploadedFileList: jasmine.createSpy('getUploadedFileList'),
getFileData: jasmine.createSpy('getFileData'),
getDefaultPolicies: jasmine.createSpy('getDefaultPolicies'),
Expand Down
6 changes: 6 additions & 0 deletions src/app/shared/mocks/submission.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1612,7 +1612,13 @@ export const mockUploadFiles = [
}
];

export const mockUploadFilesData = {
primary: null,
files: JSON.parse(JSON.stringify(mockUploadFiles))
};

export const mockFileFormData = {
primary: [true],
metadata: {
'dc.title': [
{
Expand Down
Loading
Loading