diff --git a/e2e/app.po.ts b/e2e/app.po.ts
index 54b5b55af3..c76bef118f 100644
--- a/e2e/app.po.ts
+++ b/e2e/app.po.ts
@@ -2,7 +2,8 @@ import { browser, element, by } from 'protractor';
export class ProtractorPage {
navigateTo() {
- return browser.get('/');
+ return browser.get('/')
+ .then(() => browser.waitForAngular());
}
getPageTitleText() {
diff --git a/package.json b/package.json
index 3a54b941dd..392d8f52f4 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
},
"dependencies": {
"@angular/animations": "^6.1.4",
+ "@angular/cdk": "^6.4.7",
"@angular/cli": "^6.1.5",
"@angular/common": "^6.1.4",
"@angular/core": "^6.1.4",
@@ -228,7 +229,7 @@
"rollup-plugin-node-globals": "1.2.1",
"rollup-plugin-node-resolve": "^3.0.3",
"rollup-plugin-terser": "^2.0.2",
- "sass-loader": "7.1.0",
+ "sass-loader": "^7.1.0",
"script-ext-html-webpack-plugin": "2.0.1",
"source-map": "0.7.3",
"source-map-loader": "0.2.4",
diff --git a/resources/fonts/README.md b/resources/fonts/README.md
new file mode 100644
index 0000000000..e4817b8572
--- /dev/null
+++ b/resources/fonts/README.md
@@ -0,0 +1,3 @@
+# Supported font formats
+
+DSpace supports EOT, TTF, OTF, SVG, WOFF and WOFF2 fonts.
diff --git a/resources/i18n/en.json5 b/resources/i18n/en.json5
index 344433f21b..a1f7f7e7ad 100644
--- a/resources/i18n/en.json5
+++ b/resources/i18n/en.json5
@@ -382,6 +382,14 @@
+ "communityList.tabTitle": "DSpace - Community List",
+
+ "communityList.title": "List of Communities",
+
+ "communityList.showMore": "Show More",
+
+
+
"community.create.head": "Create a Community",
"community.create.notifications.success": "Successfully created the Community",
@@ -900,6 +908,14 @@
"item.page.related-items.view-less": "View less",
+ "item.page.relationships.isAuthorOfPublication": "Publications",
+
+ "item.page.relationships.isJournalOfPublication": "Publications",
+
+ "item.page.relationships.isOrgUnitOfPerson": "Authors",
+
+ "item.page.relationships.isOrgUnitOfProject": "Research Projects",
+
"item.page.subject": "Keywords",
"item.page.uri": "URI",
@@ -1350,6 +1366,8 @@
"project.page.titleprefix": "Research Project: ",
+ "project.search.results.head": "Project Search Results",
+
"publication.listelement.badge": "Publication",
diff --git a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
index cb7aa1ef91..ec4003c108 100644
--- a/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
+++ b/src/app/+admin/admin-registries/bitstream-formats/bitstream-formats.component.ts
@@ -5,7 +5,7 @@ import { PaginatedList } from '../../../core/data/paginated-list';
import { PaginationComponentOptions } from '../../../shared/pagination/pagination-component-options.model';
import { BitstreamFormat } from '../../../core/shared/bitstream-format.model';
import { BitstreamFormatDataService } from '../../../core/data/bitstream-format-data.service';
-import { FindAllOptions } from '../../../core/data/request.models';
+import { FindListOptions } from '../../../core/data/request.models';
import { map, switchMap, take } from 'rxjs/operators';
import { hasValue } from '../../../shared/empty.util';
import { NotificationsService } from '../../../shared/notifications/notifications.service';
@@ -35,7 +35,7 @@ export class BitstreamFormatsComponent implements OnInit {
* The current pagination configuration for the page used by the FindAll method
* Currently simply renders all bitstream formats
*/
- config: FindAllOptions = Object.assign(new FindAllOptions(), {
+ config: FindListOptions = Object.assign(new FindListOptions(), {
elementsPerPage: 20
});
@@ -145,7 +145,7 @@ export class BitstreamFormatsComponent implements OnInit {
* @param event The page change event
*/
onPageChange(event) {
- this.config = Object.assign(new FindAllOptions(), this.config, {
+ this.config = Object.assign(new FindListOptions(), this.config, {
currentPage: event,
});
this.pageConfig.currentPage = event;
diff --git a/src/app/+collection-page/collection-page.component.html b/src/app/+collection-page/collection-page.component.html
index a8a9a9fdd5..1cd0318bd5 100644
--- a/src/app/+collection-page/collection-page.component.html
+++ b/src/app/+collection-page/collection-page.component.html
@@ -3,7 +3,13 @@
*ngVar="(collectionRD$ | async) as collectionRD">
-
diff --git a/src/app/navbar/navbar.component.ts b/src/app/navbar/navbar.component.ts
index 4c7c3cd030..b2ba10fb98 100644
--- a/src/app/navbar/navbar.component.ts
+++ b/src/app/navbar/navbar.component.ts
@@ -53,17 +53,18 @@ export class NavbarComponent extends MenuComponent implements OnInit {
} as TextMenuItemModel,
index: 0
},
- // {
- // id: 'browse_global_communities_and_collections',
- // parentID: 'browse_global',
- // active: false,
- // visible: true,
- // model: {
- // type: MenuItemType.LINK,
- // text: 'menu.section.browse_global_communities_and_collections',
- // link: '#'
- // } as LinkMenuItemModel,
- // },
+ /* Communities & Collections tree */
+ {
+ id: `browse_global_communities_and_collections`,
+ parentID: 'browse_global',
+ active: false,
+ visible: true,
+ model: {
+ type: MenuItemType.LINK,
+ text: `menu.section.browse_global_communities_and_collections`,
+ link: `/community-list`
+ } as LinkMenuItemModel
+ },
/* Statistics */
{
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
index 13a9ba4e85..e5663e93cb 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/ds-dynamic-form-control-container.component.spec.ts
@@ -112,6 +112,7 @@ describe('DsDynamicFormControlContainerComponent test suite', () => {
repeatable: false
}),
new DynamicRelationGroupModel({
+ submissionId: '1234',
id: 'relationGroup',
formConfiguration: [],
mandatoryField: '',
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts
index 6d5839f867..e1d12650fe 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.component.spec.ts
@@ -33,6 +33,8 @@ export let FORM_GROUP_TEST_GROUP;
const config: GlobalConfig = MOCK_SUBMISSION_CONFIG;
+const submissionId = '1234';
+
function init() {
FORM_GROUP_TEST_MODEL_CONFIG = {
disabled: false,
@@ -67,6 +69,7 @@ function init() {
}]
} as FormFieldModel]
} as FormRowModel],
+ submissionId,
id: 'dc_contributor_author',
label: 'Authors',
mandatoryField: 'dc.contributor.author',
@@ -183,7 +186,7 @@ describe('DsDynamicRelationGroupComponent test suite', () => {
it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
const formConfig = { rows: groupComp.model.formConfiguration } as SubmissionFormsModel;
- const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
+ const formModel = service.modelFromConfiguration(submissionId, formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
const chips = new Chips([], 'value', 'dc.contributor.author');
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(false);
@@ -257,7 +260,7 @@ describe('DsDynamicRelationGroupComponent test suite', () => {
it('should init component properly', inject([FormBuilderService], (service: FormBuilderService) => {
const formConfig = { rows: groupComp.model.formConfiguration } as SubmissionFormsModel;
- const formModel = service.modelFromConfiguration(formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
+ const formModel = service.modelFromConfiguration(submissionId, formConfig, groupComp.model.scopeUUID, {}, groupComp.model.submissionScope, groupComp.model.readOnly);
const chips = new Chips(modelValue, 'value', 'dc.contributor.author');
groupComp.formCollapsed.subscribe((value) => {
expect(value).toEqual(true);
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts
index 62b6b4effa..ea62eeb4ce 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.components.ts
@@ -93,6 +93,7 @@ export class DsDynamicRelationGroupComponent extends DynamicFormControlComponent
this.formId = this.formService.getUniqueId(this.model.id);
this.formModel = this.formBuilderService.modelFromConfiguration(
+ this.model.submissionId,
config,
this.model.scopeUUID,
{},
diff --git a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model.ts b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model.ts
index e6d2b95afc..c1f76f0431 100644
--- a/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model.ts
+++ b/src/app/shared/form/builder/ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model.ts
@@ -10,6 +10,7 @@ export const PLACEHOLDER_PARENT_METADATA = '#PLACEHOLDER_PARENT_METADATA_VALUE#'
* Dynamic Group Model configuration interface
*/
export interface DynamicRelationGroupModelConfig extends DsDynamicInputModelConfig {
+ submissionId: string,
formConfiguration: FormRowModel[],
mandatoryField: string,
relationFields: string[],
@@ -21,6 +22,7 @@ export interface DynamicRelationGroupModelConfig extends DsDynamicInputModelConf
* Dynamic Group Model class
*/
export class DynamicRelationGroupModel extends DsDynamicInputModel {
+ @serializable() submissionId: string;
@serializable() formConfiguration: FormRowModel[];
@serializable() mandatoryField: string;
@serializable() relationFields: string[];
@@ -32,6 +34,7 @@ export class DynamicRelationGroupModel extends DsDynamicInputModel {
constructor(config: DynamicRelationGroupModelConfig, layout?: DynamicFormControlLayout) {
super(config, layout);
+ this.submissionId = config.submissionId;
this.formConfiguration = config.formConfiguration;
this.mandatoryField = config.mandatoryField;
this.relationFields = config.relationFields;
diff --git a/src/app/shared/form/builder/form-builder.service.spec.ts b/src/app/shared/form/builder/form-builder.service.spec.ts
index 58a1696a92..496c06629a 100644
--- a/src/app/shared/form/builder/form-builder.service.spec.ts
+++ b/src/app/shared/form/builder/form-builder.service.spec.ts
@@ -56,6 +56,8 @@ describe('FormBuilderService test suite', () => {
let testFormConfiguration: SubmissionFormsModel;
let service: FormBuilderService;
+ const submissionId = '1234';
+
function testValidator() {
return {testValidator: {valid: true}};
}
@@ -204,6 +206,7 @@ describe('FormBuilderService test suite', () => {
new DynamicListRadioGroupModel({id: 'testRadioList', authorityOptions: authorityOptions, repeatable: false}),
new DynamicRelationGroupModel({
+ submissionId,
id: 'testRelationGroup',
formConfiguration: [{
fields: [{
@@ -406,7 +409,7 @@ describe('FormBuilderService test suite', () => {
});
it('should create an array of form models', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
expect(formModel[0] instanceof DynamicRowGroupModel).toBe(true);
expect((formModel[0] as DynamicRowGroupModel).group.length).toBe(3);
@@ -427,7 +430,7 @@ describe('FormBuilderService test suite', () => {
});
it('should return form\'s fields value from form model', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
let value = {} as any;
expect(service.getValueFromModel(formModel)).toEqual(value);
@@ -448,7 +451,7 @@ describe('FormBuilderService test suite', () => {
});
it('should clear all form\'s fields value', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
const value = {} as any;
((formModel[0] as DynamicRowGroupModel).get(1) as DsDynamicInputModel).valueUpdates.next('test');
@@ -460,7 +463,7 @@ describe('FormBuilderService test suite', () => {
});
it('should return true when model has a custom group model as parent', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
let model = service.findById('dc_identifier_QUALDROP_VALUE', formModel);
let modelParent = service.findById('dc_identifier_QUALDROP_GROUP', formModel);
model.parent = modelParent;
@@ -489,7 +492,7 @@ describe('FormBuilderService test suite', () => {
});
it('should return true when model value is a map', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
const model = service.findById('dc_identifier_QUALDROP_VALUE', formModel);
const modelParent = service.findById('dc_identifier_QUALDROP_GROUP', formModel);
model.parent = modelParent;
@@ -498,7 +501,7 @@ describe('FormBuilderService test suite', () => {
});
it('should return true when model is a Qualdrop Group', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
let model = service.findById('dc_identifier_QUALDROP_GROUP', formModel);
expect(service.isQualdropGroup(model)).toBe(true);
@@ -509,7 +512,7 @@ describe('FormBuilderService test suite', () => {
});
it('should return true when model is a Custom or List Group', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
let model = service.findById('dc_identifier_QUALDROP_GROUP', formModel);
expect(service.isCustomOrListGroup(model)).toBe(true);
@@ -528,7 +531,7 @@ describe('FormBuilderService test suite', () => {
});
it('should return true when model is a Custom Group', () => {
- const formModel = service.modelFromConfiguration(testFormConfiguration, 'testScopeUUID');
+ const formModel = service.modelFromConfiguration(submissionId, testFormConfiguration, 'testScopeUUID');
let model = service.findById('dc_identifier_QUALDROP_GROUP', formModel);
expect(service.isCustomGroup(model)).toBe(true);
diff --git a/src/app/shared/form/builder/form-builder.service.ts b/src/app/shared/form/builder/form-builder.service.ts
index 21e702aabb..f867d1f79c 100644
--- a/src/app/shared/form/builder/form-builder.service.ts
+++ b/src/app/shared/form/builder/form-builder.service.ts
@@ -10,7 +10,7 @@ import {
DynamicFormArrayModel,
DynamicFormControlModel,
DynamicFormGroupModel,
- DynamicFormService,
+ DynamicFormService, DynamicFormValidationService,
DynamicPathable,
JSONUtils,
} from '@ng-dynamic-forms/core';
@@ -33,6 +33,13 @@ import { isNgbDateStruct } from '../../date.util';
@Injectable()
export class FormBuilderService extends DynamicFormService {
+ constructor(
+ validationService: DynamicFormValidationService,
+ protected rowParser: RowParser
+ ) {
+ super(validationService);
+ }
+
findById(id: string, groupModel: DynamicFormControlModel[], arrayIndex = null): DynamicFormControlModel | null {
let result = null;
@@ -198,13 +205,13 @@ export class FormBuilderService extends DynamicFormService {
return result;
}
- modelFromConfiguration(json: string | SubmissionFormsModel, scopeUUID: string, initFormValues: any = {}, submissionScope?: string, readOnly = false): DynamicFormControlModel[] | never {
+ modelFromConfiguration(submissionId: string, json: string | SubmissionFormsModel, scopeUUID: string, sectionData: any = {}, submissionScope?: string, readOnly = false): DynamicFormControlModel[] | never {
let rows: DynamicFormControlModel[] = [];
const rawData = typeof json === 'string' ? JSON.parse(json, JSONUtils.parseReviver) : json;
if (rawData.rows && !isEmpty(rawData.rows)) {
rawData.rows.forEach((currentRow) => {
- const rowParsed = new RowParser(currentRow, scopeUUID, initFormValues, submissionScope, readOnly).parse();
+ const rowParsed = this.rowParser.parse(submissionId, currentRow, scopeUUID, sectionData, submissionScope, readOnly);
if (isNotNull(rowParsed)) {
if (Array.isArray(rowParsed)) {
rows = rows.concat(rowParsed);
diff --git a/src/app/shared/form/builder/parsers/concat-field-parser.ts b/src/app/shared/form/builder/parsers/concat-field-parser.ts
index 6323905555..449827c56b 100644
--- a/src/app/shared/form/builder/parsers/concat-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/concat-field-parser.ts
@@ -1,4 +1,11 @@
-import { FieldParser } from './field-parser';
+import { Inject } from '@angular/core';
+import {
+ CONFIG_DATA,
+ FieldParser,
+ INIT_FORM_VALUES,
+ PARSER_OPTIONS,
+ SUBMISSION_ID
+} from './field-parser';
import { FormFieldModel } from '../models/form-field.model';
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
import { DynamicFormControlLayout, DynamicInputModel, DynamicInputModelConfig } from '@ng-dynamic-forms/core';
@@ -14,13 +21,15 @@ import { ParserOptions } from './parser-options';
export class ConcatFieldParser extends FieldParser {
- constructor(protected configData: FormFieldModel,
- protected initFormValues,
- protected parserOptions: ParserOptions,
+ constructor(
+ @Inject(SUBMISSION_ID) submissionId: string,
+ @Inject(CONFIG_DATA) configData: FormFieldModel,
+ @Inject(INIT_FORM_VALUES) initFormValues,
+ @Inject(PARSER_OPTIONS) parserOptions: ParserOptions,
protected separator: string,
protected firstPlaceholder: string = null,
protected secondPlaceholder: string = null) {
- super(configData, initFormValues, parserOptions);
+ super(submissionId, configData, initFormValues, parserOptions);
this.separator = separator;
this.firstPlaceholder = firstPlaceholder;
diff --git a/src/app/shared/form/builder/parsers/date-field-parser.spec.ts b/src/app/shared/form/builder/parsers/date-field-parser.spec.ts
index bbcfa60621..efa4f3cdb5 100644
--- a/src/app/shared/form/builder/parsers/date-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/date-field-parser.spec.ts
@@ -1,6 +1,4 @@
import { FormFieldModel } from '../models/form-field.model';
-import { DynamicConcatModel } from '../ds-dynamic-form-ui/models/ds-dynamic-concat.model';
-import { SeriesFieldParser } from './series-field-parser';
import { DateFieldParser } from './date-field-parser';
import { DynamicDsDatePickerModel } from '../ds-dynamic-form-ui/models/date-picker/date-picker.model';
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
@@ -10,6 +8,7 @@ describe('DateFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues: any = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: null,
@@ -37,13 +36,13 @@ describe('DateFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new DateFieldParser(field, initFormValues, parserOptions);
+ const parser = new DateFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof DateFieldParser).toBe(true);
});
it('should return a DynamicDsDatePickerModel object when repeatable option is false', () => {
- const parser = new DateFieldParser(field, initFormValues, parserOptions);
+ const parser = new DateFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -56,7 +55,7 @@ describe('DateFieldParser test suite', () => {
};
const expectedValue = '1983-11-18';
- const parser = new DateFieldParser(field, initFormValues, parserOptions);
+ const parser = new DateFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/dropdown-field-parser.spec.ts b/src/app/shared/form/builder/parsers/dropdown-field-parser.spec.ts
index 5dfdcfa5ce..8dbd68e05a 100644
--- a/src/app/shared/form/builder/parsers/dropdown-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/dropdown-field-parser.spec.ts
@@ -6,6 +6,7 @@ import { ParserOptions } from './parser-options';
describe('DropdownFieldParser test suite', () => {
let field: FormFieldModel;
+ const submissionId = '1234';
const initFormValues = {};
const parserOptions: ParserOptions = {
readOnly: false,
@@ -35,13 +36,13 @@ describe('DropdownFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new DropdownFieldParser(field, initFormValues, parserOptions);
+ const parser = new DropdownFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof DropdownFieldParser).toBe(true);
});
it('should return a DynamicScrollableDropdownModel object when repeatable option is false', () => {
- const parser = new DropdownFieldParser(field, initFormValues, parserOptions);
+ const parser = new DropdownFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -50,7 +51,7 @@ describe('DropdownFieldParser test suite', () => {
it('should throw when authority is not passed', () => {
field.selectableMetadata[0].authority = null;
- const parser = new DropdownFieldParser(field, initFormValues, parserOptions);
+ const parser = new DropdownFieldParser(submissionId, field, initFormValues, parserOptions);
expect(() => parser.parse())
.toThrow();
diff --git a/src/app/shared/form/builder/parsers/dropdown-field-parser.ts b/src/app/shared/form/builder/parsers/dropdown-field-parser.ts
index 1623829b15..4816a2a073 100644
--- a/src/app/shared/form/builder/parsers/dropdown-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/dropdown-field-parser.ts
@@ -1,4 +1,12 @@
-import { FieldParser } from './field-parser';
+import { Inject } from '@angular/core';
+import { FormFieldModel } from '../models/form-field.model';
+import {
+ CONFIG_DATA,
+ FieldParser,
+ INIT_FORM_VALUES,
+ PARSER_OPTIONS,
+ SUBMISSION_ID
+} from './field-parser';
import { DynamicFormControlLayout, } from '@ng-dynamic-forms/core';
import {
DynamicScrollableDropdownModel,
@@ -6,9 +14,19 @@ import {
} from '../ds-dynamic-form-ui/models/scrollable-dropdown/dynamic-scrollable-dropdown.model';
import { isNotEmpty } from '../../../empty.util';
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
+import { ParserOptions } from './parser-options';
export class DropdownFieldParser extends FieldParser {
+ constructor(
+ @Inject(SUBMISSION_ID) submissionId: string,
+ @Inject(CONFIG_DATA) configData: FormFieldModel,
+ @Inject(INIT_FORM_VALUES) initFormValues,
+ @Inject(PARSER_OPTIONS) parserOptions: ParserOptions
+ ) {
+ super(submissionId, configData, initFormValues, parserOptions)
+ }
+
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean): any {
const dropdownModelConfig: DynamicScrollableDropdownModelConfig = this.initModel(null, label);
let layout: DynamicFormControlLayout;
diff --git a/src/app/shared/form/builder/parsers/field-parser.ts b/src/app/shared/form/builder/parsers/field-parser.ts
index dd37a45fba..83bdd14e9c 100644
--- a/src/app/shared/form/builder/parsers/field-parser.ts
+++ b/src/app/shared/form/builder/parsers/field-parser.ts
@@ -1,3 +1,4 @@
+import { Inject, InjectionToken } from '@angular/core';
import { hasValue, isNotEmpty, isNotNull, isNotUndefined } from '../../../empty.util';
import { FormFieldModel } from '../models/form-field.model';
@@ -13,12 +14,21 @@ import { setLayout } from './parser.utils';
import { AuthorityOptions } from '../../../../core/integration/models/authority-options.model';
import { ParserOptions } from './parser-options';
+export const SUBMISSION_ID: InjectionToken
= new InjectionToken('submissionId');
+export const CONFIG_DATA: InjectionToken = new InjectionToken('configData');
+export const INIT_FORM_VALUES:InjectionToken = new InjectionToken('initFormValues');
+export const PARSER_OPTIONS: InjectionToken = new InjectionToken('parserOptions');
+
export abstract class FieldParser {
protected fieldId: string;
- constructor(protected configData: FormFieldModel, protected initFormValues, protected parserOptions: ParserOptions) {
- }
+ constructor(
+ @Inject(SUBMISSION_ID) protected submissionId: string,
+ @Inject(CONFIG_DATA) protected configData: FormFieldModel,
+ @Inject(INIT_FORM_VALUES) protected initFormValues: any,
+ @Inject(PARSER_OPTIONS) protected parserOptions: ParserOptions
+ ) {}
public abstract modelFactory(fieldValue?: FormFieldMetadataValueObject, label?: boolean): any;
diff --git a/src/app/shared/form/builder/parsers/list-field-parser.spec.ts b/src/app/shared/form/builder/parsers/list-field-parser.spec.ts
index b2fa0b2089..fab5ec3888 100644
--- a/src/app/shared/form/builder/parsers/list-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/list-field-parser.spec.ts
@@ -9,6 +9,7 @@ describe('ListFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: 'testScopeUUID',
@@ -37,13 +38,13 @@ describe('ListFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new ListFieldParser(field, initFormValues, parserOptions);
+ const parser = new ListFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof ListFieldParser).toBe(true);
});
it('should return a DynamicListCheckboxGroupModel object when repeatable option is true', () => {
- const parser = new ListFieldParser(field, initFormValues, parserOptions);
+ const parser = new ListFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -52,7 +53,7 @@ describe('ListFieldParser test suite', () => {
it('should return a DynamicListRadioGroupModel object when repeatable option is false', () => {
field.repeatable = false;
- const parser = new ListFieldParser(field, initFormValues, parserOptions);
+ const parser = new ListFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -65,7 +66,7 @@ describe('ListFieldParser test suite', () => {
};
const expectedValue = [new FormFieldMetadataValueObject('test type')];
- const parser = new ListFieldParser(field, initFormValues, parserOptions);
+ const parser = new ListFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/lookup-field-parser.spec.ts b/src/app/shared/form/builder/parsers/lookup-field-parser.spec.ts
index c45d39d5bb..5e14e0c013 100644
--- a/src/app/shared/form/builder/parsers/lookup-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/lookup-field-parser.spec.ts
@@ -8,6 +8,7 @@ describe('LookupFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: 'testScopeUUID',
@@ -36,13 +37,13 @@ describe('LookupFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new LookupFieldParser(field, initFormValues, parserOptions);
+ const parser = new LookupFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof LookupFieldParser).toBe(true);
});
it('should return a DynamicLookupModel object when repeatable option is false', () => {
- const parser = new LookupFieldParser(field, initFormValues, parserOptions);
+ const parser = new LookupFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -55,7 +56,7 @@ describe('LookupFieldParser test suite', () => {
};
const expectedValue = new FormFieldMetadataValueObject('test journal');
- const parser = new LookupFieldParser(field, initFormValues, parserOptions);
+ const parser = new LookupFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/lookup-name-field-parser.spec.ts b/src/app/shared/form/builder/parsers/lookup-name-field-parser.spec.ts
index b324ba7a7e..adc1e90166 100644
--- a/src/app/shared/form/builder/parsers/lookup-name-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/lookup-name-field-parser.spec.ts
@@ -1,7 +1,5 @@
import { FormFieldModel } from '../models/form-field.model';
import { FormFieldMetadataValueObject } from '../models/form-field-metadata-value.model';
-import { LookupFieldParser } from './lookup-field-parser';
-import { DynamicLookupModel } from '../ds-dynamic-form-ui/models/lookup/dynamic-lookup.model';
import { LookupNameFieldParser } from './lookup-name-field-parser';
import { DynamicLookupNameModel } from '../ds-dynamic-form-ui/models/lookup/dynamic-lookup-name.model';
import { ParserOptions } from './parser-options';
@@ -10,6 +8,7 @@ describe('LookupNameFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: 'testScopeUUID',
@@ -38,13 +37,13 @@ describe('LookupNameFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new LookupNameFieldParser(field, initFormValues, parserOptions);
+ const parser = new LookupNameFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof LookupNameFieldParser).toBe(true);
});
it('should return a DynamicLookupNameModel object when repeatable option is false', () => {
- const parser = new LookupNameFieldParser(field, initFormValues, parserOptions);
+ const parser = new LookupNameFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -57,7 +56,7 @@ describe('LookupNameFieldParser test suite', () => {
};
const expectedValue = new FormFieldMetadataValueObject('test author');
- const parser = new LookupNameFieldParser(field, initFormValues, parserOptions);
+ const parser = new LookupNameFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/name-field-parser.spec.ts b/src/app/shared/form/builder/parsers/name-field-parser.spec.ts
index 889244e8f2..1b0c637030 100644
--- a/src/app/shared/form/builder/parsers/name-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/name-field-parser.spec.ts
@@ -10,6 +10,7 @@ describe('NameFieldParser test suite', () => {
let field3: FormFieldModel;
let initFormValues: any = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: 'testScopeUUID',
@@ -69,13 +70,13 @@ describe('NameFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new NameFieldParser(field1, initFormValues, parserOptions);
+ const parser = new NameFieldParser(submissionId, field1, initFormValues, parserOptions);
expect(parser instanceof NameFieldParser).toBe(true);
});
it('should return a DynamicConcatModel object when repeatable option is false', () => {
- const parser = new NameFieldParser(field2, initFormValues, parserOptions);
+ const parser = new NameFieldParser(submissionId, field2, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -83,7 +84,7 @@ describe('NameFieldParser test suite', () => {
});
it('should return a DynamicConcatModel object with the correct separator', () => {
- const parser = new NameFieldParser(field2, initFormValues, parserOptions);
+ const parser = new NameFieldParser(submissionId, field2, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -96,7 +97,7 @@ describe('NameFieldParser test suite', () => {
};
const expectedValue = new FormFieldMetadataValueObject('test, name');
- const parser = new NameFieldParser(field1, initFormValues, parserOptions);
+ const parser = new NameFieldParser(submissionId, field1, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/name-field-parser.ts b/src/app/shared/form/builder/parsers/name-field-parser.ts
index 896b3cc478..e5ecb034ea 100644
--- a/src/app/shared/form/builder/parsers/name-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/name-field-parser.ts
@@ -1,10 +1,17 @@
+import { Inject } from '@angular/core';
import { FormFieldModel } from '../models/form-field.model';
import { ConcatFieldParser } from './concat-field-parser';
+import { CONFIG_DATA, INIT_FORM_VALUES, PARSER_OPTIONS, SUBMISSION_ID } from './field-parser';
import { ParserOptions } from './parser-options';
export class NameFieldParser extends ConcatFieldParser {
- constructor(protected configData: FormFieldModel, protected initFormValues, protected parserOptions: ParserOptions) {
- super(configData, initFormValues, parserOptions, ',', 'form.last-name', 'form.first-name');
+ constructor(
+ @Inject(SUBMISSION_ID) submissionId: string,
+ @Inject(CONFIG_DATA) configData: FormFieldModel,
+ @Inject(INIT_FORM_VALUES) initFormValues,
+ @Inject(PARSER_OPTIONS) parserOptions: ParserOptions
+ ) {
+ super(submissionId, configData, initFormValues, parserOptions, ',', 'form.last-name', 'form.first-name');
}
}
diff --git a/src/app/shared/form/builder/parsers/onebox-field-parser.spec.ts b/src/app/shared/form/builder/parsers/onebox-field-parser.spec.ts
index 89c576bf3a..4668b3017d 100644
--- a/src/app/shared/form/builder/parsers/onebox-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/onebox-field-parser.spec.ts
@@ -10,6 +10,7 @@ describe('OneboxFieldParser test suite', () => {
let field2: FormFieldModel;
let field3: FormFieldModel;
+ const submissionId = '1234';
const initFormValues = {};
const parserOptions: ParserOptions = {
readOnly: false,
@@ -70,13 +71,13 @@ describe('OneboxFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new OneboxFieldParser(field1, initFormValues, parserOptions);
+ const parser = new OneboxFieldParser(submissionId, field1, initFormValues, parserOptions);
expect(parser instanceof OneboxFieldParser).toBe(true);
});
it('should return a DynamicQualdropModel object when selectableMetadata is multiple', () => {
- const parser = new OneboxFieldParser(field2, initFormValues, parserOptions);
+ const parser = new OneboxFieldParser(submissionId, field2, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -84,7 +85,7 @@ describe('OneboxFieldParser test suite', () => {
});
it('should return a DsDynamicInputModel object when selectableMetadata is not multiple', () => {
- const parser = new OneboxFieldParser(field3, initFormValues, parserOptions);
+ const parser = new OneboxFieldParser(submissionId, field3, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -92,7 +93,7 @@ describe('OneboxFieldParser test suite', () => {
});
it('should return a DynamicTypeaheadModel object when selectableMetadata has authority', () => {
- const parser = new OneboxFieldParser(field1, initFormValues, parserOptions);
+ const parser = new OneboxFieldParser(submissionId, field1, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/parser-factory.ts b/src/app/shared/form/builder/parsers/parser-factory.ts
index 2cbee18783..67d8f31740 100644
--- a/src/app/shared/form/builder/parsers/parser-factory.ts
+++ b/src/app/shared/form/builder/parsers/parser-factory.ts
@@ -1,6 +1,12 @@
+import { StaticProvider } from '@angular/core';
import { ParserType } from './parser-type';
-import { GenericConstructor } from '../../../../core/shared/generic-constructor';
-import { FieldParser } from './field-parser';
+import {
+ CONFIG_DATA,
+ FieldParser,
+ INIT_FORM_VALUES,
+ PARSER_OPTIONS,
+ SUBMISSION_ID
+} from './field-parser';
import { DateFieldParser } from './date-field-parser';
import { DropdownFieldParser } from './dropdown-field-parser';
import { RelationGroupFieldParser } from './relation-group-field-parser';
@@ -13,41 +19,92 @@ import { SeriesFieldParser } from './series-field-parser';
import { TagFieldParser } from './tag-field-parser';
import { TextareaFieldParser } from './textarea-field-parser';
+const fieldParserDeps = [
+ SUBMISSION_ID,
+ CONFIG_DATA,
+ INIT_FORM_VALUES,
+ PARSER_OPTIONS,
+];
+
export class ParserFactory {
- public static getConstructor(type: ParserType): GenericConstructor {
+ public static getProvider(type: ParserType): StaticProvider {
switch (type) {
case ParserType.Date: {
- return DateFieldParser
+ return {
+ provide: FieldParser,
+ useClass: DateFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.Dropdown: {
- return DropdownFieldParser
+ return {
+ provide: FieldParser,
+ useClass: DropdownFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.RelationGroup: {
- return RelationGroupFieldParser
+ return {
+ provide: FieldParser,
+ useClass: RelationGroupFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.List: {
- return ListFieldParser
+ return {
+ provide: FieldParser,
+ useClass: ListFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.Lookup: {
- return LookupFieldParser
+ return {
+ provide: FieldParser,
+ useClass: LookupFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.LookupName: {
- return LookupNameFieldParser
+ return {
+ provide: FieldParser,
+ useClass: LookupNameFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.Onebox: {
- return OneboxFieldParser
+ return {
+ provide: FieldParser,
+ useClass: OneboxFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.Name: {
- return NameFieldParser
+ return {
+ provide: FieldParser,
+ useClass: NameFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.Series: {
- return SeriesFieldParser
+ return {
+ provide: FieldParser,
+ useClass: SeriesFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.Tag: {
- return TagFieldParser
+ return {
+ provide: FieldParser,
+ useClass: TagFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
case ParserType.Textarea: {
- return TextareaFieldParser
+ return {
+ provide: FieldParser,
+ useClass: TextareaFieldParser,
+ deps: [...fieldParserDeps]
+ }
}
default: {
diff --git a/src/app/shared/form/builder/parsers/relation-group-field-parser.spec.ts b/src/app/shared/form/builder/parsers/relation-group-field-parser.spec.ts
index e6bf0dc2c8..84f3df0365 100644
--- a/src/app/shared/form/builder/parsers/relation-group-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/relation-group-field-parser.spec.ts
@@ -8,6 +8,7 @@ describe('RelationGroupFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: 'testScopeUUID',
@@ -71,13 +72,13 @@ describe('RelationGroupFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new RelationGroupFieldParser(field, initFormValues, parserOptions);
+ const parser = new RelationGroupFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof RelationGroupFieldParser).toBe(true);
});
it('should return a DynamicRelationGroupModel object', () => {
- const parser = new RelationGroupFieldParser(field, initFormValues, parserOptions);
+ const parser = new RelationGroupFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -86,7 +87,7 @@ describe('RelationGroupFieldParser test suite', () => {
it('should throw when rows configuration is empty', () => {
field.rows = null;
- const parser = new RelationGroupFieldParser(field, initFormValues, parserOptions);
+ const parser = new RelationGroupFieldParser(submissionId, field, initFormValues, parserOptions);
expect(() => parser.parse())
.toThrow();
@@ -97,7 +98,7 @@ describe('RelationGroupFieldParser test suite', () => {
author: [new FormFieldMetadataValueObject('test author')],
affiliation: [new FormFieldMetadataValueObject('test affiliation')]
};
- const parser = new RelationGroupFieldParser(field, initFormValues, parserOptions);
+ const parser = new RelationGroupFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
const expectedValue = [{
diff --git a/src/app/shared/form/builder/parsers/relation-group-field-parser.ts b/src/app/shared/form/builder/parsers/relation-group-field-parser.ts
index b3f6e749f3..01699d9e78 100644
--- a/src/app/shared/form/builder/parsers/relation-group-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/relation-group-field-parser.ts
@@ -15,6 +15,7 @@ export class RelationGroupFieldParser extends FieldParser {
public modelFactory(fieldValue?: FormFieldMetadataValueObject | any, label?: boolean) {
const modelConfiguration: DynamicRelationGroupModelConfig = this.initModel(null, label);
+ modelConfiguration.submissionId = this.submissionId;
modelConfiguration.scopeUUID = this.parserOptions.authorityUuid;
modelConfiguration.submissionScope = this.parserOptions.submissionScope;
if (this.configData && this.configData.rows && this.configData.rows.length > 0) {
diff --git a/src/app/shared/form/builder/parsers/row-parser.spec.ts b/src/app/shared/form/builder/parsers/row-parser.spec.ts
index 58b1d1de99..435c6a6426 100644
--- a/src/app/shared/form/builder/parsers/row-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/row-parser.spec.ts
@@ -17,6 +17,7 @@ describe('RowParser test suite', () => {
let row9: FormRowModel;
let row10: FormRowModel;
+ const submissionId = '1234';
const scopeUUID = 'testScopeUUID';
const initFormValues = {};
const submissionScope = 'WORKSPACE';
@@ -328,76 +329,98 @@ describe('RowParser test suite', () => {
});
it('should init parser properly', () => {
- let parser = new RowParser(row1, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row2, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row3, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row4, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row5, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row6, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row7, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row8, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row9, scopeUUID, initFormValues, submissionScope, readOnly);
-
- expect(parser instanceof RowParser).toBe(true);
-
- parser = new RowParser(row10, scopeUUID, initFormValues, submissionScope, readOnly);
+ const parser = new RowParser(undefined);
expect(parser instanceof RowParser).toBe(true);
});
- it('should return a DynamicRowGroupModel object', () => {
- const parser = new RowParser(row1, scopeUUID, initFormValues, submissionScope, readOnly);
+ describe('parse', () => {
+ it('should return a DynamicRowGroupModel object', () => {
+ const parser = new RowParser(undefined);
- const rowModel = parser.parse();
+ const rowModel = parser.parse(submissionId, row1, scopeUUID, initFormValues, submissionScope, readOnly);
- expect(rowModel instanceof DynamicRowGroupModel).toBe(true);
- });
+ expect(rowModel instanceof DynamicRowGroupModel).toBe(true);
+ });
- it('should return a row with three fields', () => {
- const parser = new RowParser(row1, scopeUUID, initFormValues, submissionScope, readOnly);
+ it('should return a row with three fields', () => {
+ const parser = new RowParser(undefined);
- const rowModel = parser.parse();
+ const rowModel = parser.parse(submissionId, row1, scopeUUID, initFormValues, submissionScope, readOnly);
- expect((rowModel as DynamicRowGroupModel).group.length).toBe(3);
- });
+ expect((rowModel as DynamicRowGroupModel).group.length).toBe(3);
+ });
- it('should return a DynamicRowArrayModel object', () => {
- const parser = new RowParser(row2, scopeUUID, initFormValues, submissionScope, readOnly);
+ it('should return a DynamicRowArrayModel object', () => {
+ const parser = new RowParser(undefined);
- const rowModel = parser.parse();
+ const rowModel = parser.parse(submissionId, row2, scopeUUID, initFormValues, submissionScope, readOnly);
- expect(rowModel instanceof DynamicRowArrayModel).toBe(true);
- });
+ expect(rowModel instanceof DynamicRowArrayModel).toBe(true);
+ });
- it('should return a row that contains only scoped fields', () => {
- const parser = new RowParser(row3, scopeUUID, initFormValues, submissionScope, readOnly);
+ it('should return a row that contains only scoped fields', () => {
+ const parser = new RowParser(undefined);
- const rowModel = parser.parse();
+ const rowModel = parser.parse(submissionId, row3, scopeUUID, initFormValues, submissionScope, readOnly);
- expect((rowModel as DynamicRowGroupModel).group.length).toBe(1);
+ expect((rowModel as DynamicRowGroupModel).group.length).toBe(1);
+ });
+
+ it('should be able to parse a dropdown combo field', () => {
+ const parser = new RowParser(undefined);
+
+ const rowModel = parser.parse(submissionId, row4, scopeUUID, initFormValues, submissionScope, readOnly);
+
+ expect(rowModel).toBeDefined();
+ });
+
+ it('should be able to parse a lookup-name field', () => {
+ const parser = new RowParser(undefined);
+
+ const rowModel = parser.parse(submissionId, row5, scopeUUID, initFormValues, submissionScope, readOnly);
+
+ expect(rowModel).toBeDefined();
+ });
+
+ it('should be able to parse a list field', () => {
+ const parser = new RowParser(undefined);
+
+ const rowModel = parser.parse(submissionId, row6, scopeUUID, initFormValues, submissionScope, readOnly);
+
+ expect(rowModel).toBeDefined();
+ });
+
+ it('should be able to parse a date field', () => {
+ const parser = new RowParser(undefined);
+
+ const rowModel = parser.parse(submissionId, row7, scopeUUID, initFormValues, submissionScope, readOnly);
+
+ expect(rowModel).toBeDefined();
+ });
+
+ it('should be able to parse a tag field', () => {
+ const parser = new RowParser(undefined);
+
+ const rowModel = parser.parse(submissionId, row8, scopeUUID, initFormValues, submissionScope, readOnly);
+
+ expect(rowModel).toBeDefined();
+ });
+
+ it('should be able to parse a textarea field', () => {
+ const parser = new RowParser(undefined);
+
+ const rowModel = parser.parse(submissionId, row9, scopeUUID, initFormValues, submissionScope, readOnly);
+
+ expect(rowModel).toBeDefined();
+ });
+
+ it('should be able to parse a group field', () => {
+ const parser = new RowParser(undefined);
+
+ const rowModel = parser.parse(submissionId, row10, scopeUUID, initFormValues, submissionScope, readOnly);
+
+ expect(rowModel).toBeDefined();
+ });
});
});
diff --git a/src/app/shared/form/builder/parsers/row-parser.ts b/src/app/shared/form/builder/parsers/row-parser.ts
index 0bb8a0e89a..72737cfaa9 100644
--- a/src/app/shared/form/builder/parsers/row-parser.ts
+++ b/src/app/shared/form/builder/parsers/row-parser.ts
@@ -1,30 +1,42 @@
-import { DYNAMIC_FORM_CONTROL_TYPE_ARRAY, DynamicFormGroupModelConfig } from '@ng-dynamic-forms/core';
+import { Injectable, Injector } from '@angular/core';
+import {
+ DYNAMIC_FORM_CONTROL_TYPE_ARRAY,
+ DynamicFormGroupModelConfig
+} from '@ng-dynamic-forms/core';
import { uniqueId } from 'lodash';
import { IntegrationSearchOptions } from '../../../../core/integration/models/integration-options.model';
-import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from '../ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
-import { DynamicRowGroupModel } from '../ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
import { isEmpty } from '../../../empty.util';
-import { setLayout } from './parser.utils';
+import { DynamicRowGroupModel } from '../ds-dynamic-form-ui/models/ds-dynamic-row-group-model';
+import { DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP } from '../ds-dynamic-form-ui/models/relation-group/dynamic-relation-group.model';
import { FormFieldModel } from '../models/form-field.model';
-import { ParserType } from './parser-type';
-import { ParserOptions } from './parser-options';
+import {
+ CONFIG_DATA,
+ FieldParser,
+ INIT_FORM_VALUES,
+ PARSER_OPTIONS,
+ SUBMISSION_ID
+} from './field-parser';
import { ParserFactory } from './parser-factory';
+import { ParserOptions } from './parser-options';
+import { ParserType } from './parser-type';
+import { setLayout } from './parser.utils';
export const ROW_ID_PREFIX = 'df-row-group-config-';
+@Injectable({
+ providedIn: 'root'
+})
export class RowParser {
- protected authorityOptions: IntegrationSearchOptions;
-
- constructor(protected rowData,
- protected scopeUUID,
- protected initFormValues: any,
- protected submissionScope,
- protected readOnly: boolean) {
- this.authorityOptions = new IntegrationSearchOptions(scopeUUID);
+ constructor(private parentInjector: Injector) {
}
- public parse(): DynamicRowGroupModel {
+ public parse(submissionId: string,
+ rowData,
+ scopeUUID,
+ initFormValues: any,
+ submissionScope,
+ readOnly: boolean): DynamicRowGroupModel {
let fieldModel: any = null;
let parsedResult = null;
const config: DynamicFormGroupModelConfig = {
@@ -32,31 +44,44 @@ export class RowParser {
group: [],
};
- const scopedFields: FormFieldModel[] = this.filterScopedFields(this.rowData.fields);
+ const authorityOptions = new IntegrationSearchOptions(scopeUUID);
+
+ const scopedFields: FormFieldModel[] = this.filterScopedFields(rowData.fields, submissionScope);
const layoutDefaultGridClass = ' col-sm-' + Math.trunc(12 / scopedFields.length);
const layoutClass = ' d-flex flex-column justify-content-start';
const parserOptions: ParserOptions = {
- readOnly: this.readOnly,
- submissionScope: this.submissionScope,
- authorityUuid: this.authorityOptions.uuid
+ readOnly: readOnly,
+ submissionScope: submissionScope,
+ authorityUuid: authorityOptions.uuid
};
// Iterate over row's fields
scopedFields.forEach((fieldData: FormFieldModel) => {
const layoutFieldClass = (fieldData.style || layoutDefaultGridClass) + layoutClass;
- const parserCo = ParserFactory.getConstructor(fieldData.input.type as ParserType);
- if (parserCo) {
- fieldModel = new parserCo(fieldData, this.initFormValues, parserOptions).parse();
+ const parserProvider = ParserFactory.getProvider(fieldData.input.type as ParserType);
+ if (parserProvider) {
+ const fieldInjector = Injector.create({
+ providers: [
+ parserProvider,
+ { provide: SUBMISSION_ID, useValue: submissionId },
+ { provide: CONFIG_DATA, useValue: fieldData },
+ { provide: INIT_FORM_VALUES, useValue: initFormValues },
+ { provide: PARSER_OPTIONS, useValue: parserOptions }
+ ],
+ parent: this.parentInjector
+ });
+
+ fieldModel = fieldInjector.get(FieldParser).parse();
} else {
- throw new Error(`unknown form control model type "${fieldData.input.type}" defined for Input field with label "${fieldData.label}".`, );
+ throw new Error(`unknown form control model type "${fieldData.input.type}" defined for Input field with label "${fieldData.label}".`,);
}
if (fieldModel) {
if (fieldModel.type === DYNAMIC_FORM_CONTROL_TYPE_ARRAY || fieldModel.type === DYNAMIC_FORM_CONTROL_TYPE_RELATION_GROUP) {
- if (this.rowData.fields.length > 1) {
+ if (rowData.fields.length > 1) {
setLayout(fieldModel, 'grid', 'host', layoutFieldClass);
config.group.push(fieldModel);
// if (isEmpty(parsedResult)) {
@@ -98,15 +123,15 @@ export class RowParser {
return parsedResult;
}
- checksFieldScope(fieldScope) {
- return (isEmpty(fieldScope) || isEmpty(this.submissionScope) || fieldScope === this.submissionScope);
+ checksFieldScope(fieldScope, submissionScope) {
+ return (isEmpty(fieldScope) || isEmpty(submissionScope) || fieldScope === submissionScope);
}
- filterScopedFields(fields: FormFieldModel[]): FormFieldModel[] {
+ filterScopedFields(fields: FormFieldModel[], submissionScope): FormFieldModel[] {
const filteredFields: FormFieldModel[] = [];
fields.forEach((field: FormFieldModel) => {
// Whether field scope doesn't match the submission scope, skip it
- if (this.checksFieldScope(field.scope)) {
+ if (this.checksFieldScope(field.scope, submissionScope)) {
filteredFields.push(field);
}
});
diff --git a/src/app/shared/form/builder/parsers/series-field-parser.spec.ts b/src/app/shared/form/builder/parsers/series-field-parser.spec.ts
index 95351d027f..ceb4e96320 100644
--- a/src/app/shared/form/builder/parsers/series-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/series-field-parser.spec.ts
@@ -8,6 +8,7 @@ describe('SeriesFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues: any = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: 'testScopeUUID',
@@ -32,13 +33,13 @@ describe('SeriesFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new SeriesFieldParser(field, initFormValues, parserOptions);
+ const parser = new SeriesFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof SeriesFieldParser).toBe(true);
});
it('should return a DynamicConcatModel object when repeatable option is false', () => {
- const parser = new SeriesFieldParser(field, initFormValues, parserOptions);
+ const parser = new SeriesFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -46,7 +47,7 @@ describe('SeriesFieldParser test suite', () => {
});
it('should return a DynamicConcatModel object with the correct separator', () => {
- const parser = new SeriesFieldParser(field, initFormValues, parserOptions);
+ const parser = new SeriesFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -59,7 +60,7 @@ describe('SeriesFieldParser test suite', () => {
};
const expectedValue = new FormFieldMetadataValueObject('test; series');
- const parser = new SeriesFieldParser(field, initFormValues, parserOptions);
+ const parser = new SeriesFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/series-field-parser.ts b/src/app/shared/form/builder/parsers/series-field-parser.ts
index 9857b4e993..36ee9c36c1 100644
--- a/src/app/shared/form/builder/parsers/series-field-parser.ts
+++ b/src/app/shared/form/builder/parsers/series-field-parser.ts
@@ -1,10 +1,17 @@
+import { Inject } from '@angular/core';
import { FormFieldModel } from '../models/form-field.model';
import { ConcatFieldParser } from './concat-field-parser';
+import { CONFIG_DATA, INIT_FORM_VALUES, PARSER_OPTIONS, SUBMISSION_ID } from './field-parser';
import { ParserOptions } from './parser-options';
export class SeriesFieldParser extends ConcatFieldParser {
- constructor(protected configData: FormFieldModel, protected initFormValues, protected parserOptions: ParserOptions) {
- super(configData, initFormValues, parserOptions, ';');
+ constructor(
+ @Inject(SUBMISSION_ID) submissionId: string,
+ @Inject(CONFIG_DATA) configData: FormFieldModel,
+ @Inject(INIT_FORM_VALUES) initFormValues,
+ @Inject(PARSER_OPTIONS) parserOptions: ParserOptions
+ ) {
+ super(submissionId, configData, initFormValues, parserOptions, ';');
}
}
diff --git a/src/app/shared/form/builder/parsers/tag-field-parser.spec.ts b/src/app/shared/form/builder/parsers/tag-field-parser.spec.ts
index 3051dc6395..90449e62e5 100644
--- a/src/app/shared/form/builder/parsers/tag-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/tag-field-parser.spec.ts
@@ -8,6 +8,7 @@ describe('TagFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues: any = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: 'testScopeUUID',
@@ -36,13 +37,13 @@ describe('TagFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new TagFieldParser(field, initFormValues, parserOptions);
+ const parser = new TagFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof TagFieldParser).toBe(true);
});
it('should return a DynamicTagModel object when repeatable option is false', () => {
- const parser = new TagFieldParser(field, initFormValues, parserOptions);
+ const parser = new TagFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -57,7 +58,7 @@ describe('TagFieldParser test suite', () => {
],
};
- const parser = new TagFieldParser(field, initFormValues, parserOptions);
+ const parser = new TagFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/form/builder/parsers/textarea-field-parser.spec.ts b/src/app/shared/form/builder/parsers/textarea-field-parser.spec.ts
index c26d758e48..167f126cf2 100644
--- a/src/app/shared/form/builder/parsers/textarea-field-parser.spec.ts
+++ b/src/app/shared/form/builder/parsers/textarea-field-parser.spec.ts
@@ -8,6 +8,7 @@ describe('TextareaFieldParser test suite', () => {
let field: FormFieldModel;
let initFormValues: any = {};
+ const submissionId = '1234';
const parserOptions: ParserOptions = {
readOnly: false,
submissionScope: null,
@@ -34,13 +35,13 @@ describe('TextareaFieldParser test suite', () => {
});
it('should init parser properly', () => {
- const parser = new TextareaFieldParser(field, initFormValues, parserOptions);
+ const parser = new TextareaFieldParser(submissionId, field, initFormValues, parserOptions);
expect(parser instanceof TextareaFieldParser).toBe(true);
});
it('should return a DsDynamicTextAreaModel object when repeatable option is false', () => {
- const parser = new TextareaFieldParser(field, initFormValues, parserOptions);
+ const parser = new TextareaFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
@@ -55,7 +56,7 @@ describe('TextareaFieldParser test suite', () => {
};
const expectedValue ='test description';
- const parser = new TextareaFieldParser(field, initFormValues, parserOptions);
+ const parser = new TextareaFieldParser(submissionId, field, initFormValues, parserOptions);
const fieldModel = parser.parse();
diff --git a/src/app/shared/mocks/mock-angulartics.service.ts b/src/app/shared/mocks/mock-angulartics.service.ts
index 99a8b96b22..5581e183d1 100644
--- a/src/app/shared/mocks/mock-angulartics.service.ts
+++ b/src/app/shared/mocks/mock-angulartics.service.ts
@@ -1,4 +1,5 @@
/* tslint:disable:no-empty */
export class AngularticsMock {
public eventTrack(action, properties) { }
+ public startTracking():void {}
}
diff --git a/src/app/shared/mocks/mock-form-models.ts b/src/app/shared/mocks/mock-form-models.ts
index f2fc38c420..74f1581814 100644
--- a/src/app/shared/mocks/mock-form-models.ts
+++ b/src/app/shared/mocks/mock-form-models.ts
@@ -115,6 +115,7 @@ const mockFormRowModel = {
} as FormRowModel;
const relationGroupConfig = {
+ submissionId: '1234',
id: 'relationGroup',
formConfiguration: [mockFormRowModel],
mandatoryField: 'false',
diff --git a/src/app/shared/mocks/mock-request.service.ts b/src/app/shared/mocks/mock-request.service.ts
index 9a320b749c..9d7f2baeab 100644
--- a/src/app/shared/mocks/mock-request.service.ts
+++ b/src/app/shared/mocks/mock-request.service.ts
@@ -1,8 +1,9 @@
import {of as observableOf, Observable } from 'rxjs';
import { RequestService } from '../../core/data/request.service';
import { RequestEntry } from '../../core/data/request.reducer';
+import SpyObj = jasmine.SpyObj;
-export function getMockRequestService(requestEntry$: Observable = observableOf(new RequestEntry())): RequestService {
+export function getMockRequestService(requestEntry$: Observable = observableOf(new RequestEntry())): SpyObj {
return jasmine.createSpyObj('requestService', {
configure: false,
generateRequestId: 'clients/b186e8ce-e99c-4183-bc9a-42b4821bdb78',
diff --git a/src/app/statistics/angulartics/dspace-provider.spec.ts b/src/app/statistics/angulartics/dspace-provider.spec.ts
new file mode 100644
index 0000000000..d89d2d9fc6
--- /dev/null
+++ b/src/app/statistics/angulartics/dspace-provider.spec.ts
@@ -0,0 +1,26 @@
+import { Angulartics2DSpace } from './dspace-provider';
+import { Angulartics2 } from 'angulartics2';
+import { StatisticsService } from '../statistics.service';
+import { filter } from 'rxjs/operators';
+import { of as observableOf } from 'rxjs';
+
+describe('Angulartics2DSpace', () => {
+ let provider:Angulartics2DSpace;
+ let angulartics2:Angulartics2;
+ let statisticsService:jasmine.SpyObj;
+
+ beforeEach(() => {
+ angulartics2 = {
+ eventTrack: observableOf({action: 'pageView', properties: {object: 'mock-object'}}),
+ filterDeveloperMode: () => filter(() => true)
+ } as any;
+ statisticsService = jasmine.createSpyObj('statisticsService', {trackViewEvent: null});
+ provider = new Angulartics2DSpace(angulartics2, statisticsService);
+ });
+
+ it('should use the statisticsService', () => {
+ provider.startTracking();
+ expect(statisticsService.trackViewEvent).toHaveBeenCalledWith('mock-object');
+ });
+
+});
diff --git a/src/app/statistics/angulartics/dspace-provider.ts b/src/app/statistics/angulartics/dspace-provider.ts
new file mode 100644
index 0000000000..9ab01f6023
--- /dev/null
+++ b/src/app/statistics/angulartics/dspace-provider.ts
@@ -0,0 +1,38 @@
+import { Injectable } from '@angular/core';
+import { Angulartics2 } from 'angulartics2';
+import { StatisticsService } from '../statistics.service';
+
+/**
+ * Angulartics2DSpace is a angulartics2 plugin that provides DSpace with the events.
+ */
+@Injectable({providedIn: 'root'})
+export class Angulartics2DSpace {
+
+ constructor(
+ private angulartics2:Angulartics2,
+ private statisticsService:StatisticsService,
+ ) {
+ }
+
+ /**
+ * Activates this plugin
+ */
+ startTracking():void {
+ this.angulartics2.eventTrack
+ .pipe(this.angulartics2.filterDeveloperMode())
+ .subscribe((event) => this.eventTrack(event));
+ }
+
+ private eventTrack(event) {
+ if (event.action === 'pageView') {
+ this.statisticsService.trackViewEvent(event.properties.object);
+ } else if (event.action === 'search') {
+ this.statisticsService.trackSearchEvent(
+ event.properties.searchOptions,
+ event.properties.page,
+ event.properties.sort,
+ event.properties.filters
+ );
+ }
+ }
+}
diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.html b/src/app/statistics/angulartics/dspace/view-tracker.component.html
new file mode 100644
index 0000000000..c0c0ffe181
--- /dev/null
+++ b/src/app/statistics/angulartics/dspace/view-tracker.component.html
@@ -0,0 +1 @@
+
diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.scss b/src/app/statistics/angulartics/dspace/view-tracker.component.scss
new file mode 100644
index 0000000000..c76cafbe44
--- /dev/null
+++ b/src/app/statistics/angulartics/dspace/view-tracker.component.scss
@@ -0,0 +1,3 @@
+:host {
+ display: none
+}
diff --git a/src/app/statistics/angulartics/dspace/view-tracker.component.ts b/src/app/statistics/angulartics/dspace/view-tracker.component.ts
new file mode 100644
index 0000000000..1151287ea8
--- /dev/null
+++ b/src/app/statistics/angulartics/dspace/view-tracker.component.ts
@@ -0,0 +1,27 @@
+import { Component, Input, OnInit } from '@angular/core';
+import { Angulartics2 } from 'angulartics2';
+import { DSpaceObject } from '../../../core/shared/dspace-object.model';
+
+/**
+ * This component triggers a page view statistic
+ */
+@Component({
+ selector: 'ds-view-tracker',
+ styleUrls: ['./view-tracker.component.scss'],
+ templateUrl: './view-tracker.component.html',
+})
+export class ViewTrackerComponent implements OnInit {
+ @Input() object:DSpaceObject;
+
+ constructor(
+ public angulartics2:Angulartics2
+ ) {
+ }
+
+ ngOnInit():void {
+ this.angulartics2.eventTrack.next({
+ action: 'pageView',
+ properties: {object: this.object},
+ });
+ }
+}
diff --git a/src/app/statistics/statistics.module.ts b/src/app/statistics/statistics.module.ts
new file mode 100644
index 0000000000..a67ff7613c
--- /dev/null
+++ b/src/app/statistics/statistics.module.ts
@@ -0,0 +1,36 @@
+import { ModuleWithProviders, NgModule } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { CoreModule } from '../core/core.module';
+import { SharedModule } from '../shared/shared.module';
+import { ViewTrackerComponent } from './angulartics/dspace/view-tracker.component';
+import { StatisticsService } from './statistics.service';
+
+@NgModule({
+ imports: [
+ CommonModule,
+ CoreModule.forRoot(),
+ SharedModule,
+ ],
+ declarations: [
+ ViewTrackerComponent,
+ ],
+ exports: [
+ ViewTrackerComponent,
+ ],
+ providers: [
+ StatisticsService
+ ]
+})
+/**
+ * This module handles the statistics
+ */
+export class StatisticsModule {
+ static forRoot():ModuleWithProviders {
+ return {
+ ngModule: StatisticsModule,
+ providers: [
+ StatisticsService
+ ]
+ };
+ }
+}
diff --git a/src/app/statistics/statistics.service.spec.ts b/src/app/statistics/statistics.service.spec.ts
new file mode 100644
index 0000000000..3a416968f8
--- /dev/null
+++ b/src/app/statistics/statistics.service.spec.ts
@@ -0,0 +1,145 @@
+import { StatisticsService } from './statistics.service';
+import { RequestService } from '../core/data/request.service';
+import { HALEndpointServiceStub } from '../shared/testing/hal-endpoint-service-stub';
+import { getMockRequestService } from '../shared/mocks/mock-request.service';
+import { TrackRequest } from './track-request.model';
+import { ResourceType } from '../core/shared/resource-type';
+import { SearchOptions } from '../+search-page/search-options.model';
+import { isEqual } from 'lodash';
+import { DSpaceObjectType } from '../core/shared/dspace-object-type.model';
+
+describe('StatisticsService', () => {
+ let service:StatisticsService;
+ let requestService:jasmine.SpyObj;
+ const restURL = 'https://rest.api';
+ const halService:any = new HALEndpointServiceStub(restURL);
+
+ function initTestService() {
+ return new StatisticsService(
+ requestService,
+ halService,
+ );
+ }
+
+ describe('trackViewEvent', () => {
+ requestService = getMockRequestService();
+ service = initTestService();
+
+ it('should send a request to track an item view ', () => {
+ const mockItem:any = {uuid: 'mock-item-uuid', type: 'item'};
+ service.trackViewEvent(mockItem);
+ const request:TrackRequest = requestService.configure.calls.mostRecent().args[0];
+ expect(request.body).toBeDefined('request.body');
+ const body = JSON.parse(request.body);
+ expect(body.targetId).toBe('mock-item-uuid');
+ expect(body.targetType).toBe('item');
+ });
+ });
+
+ describe('trackSearchEvent', () => {
+ requestService = getMockRequestService();
+ service = initTestService();
+
+ const mockSearch:any = new SearchOptions({
+ query: 'mock-query',
+ });
+
+ const page = {
+ size: 10,
+ totalElements: 248,
+ totalPages: 25,
+ number: 4
+ };
+ const sort = {by: 'search-field', order: 'ASC'};
+ service.trackSearchEvent(mockSearch, page, sort);
+ const request:TrackRequest = requestService.configure.calls.mostRecent().args[0];
+ const body = JSON.parse(request.body);
+
+ it('should specify the right query', () => {
+ expect(body.query).toBe('mock-query');
+ });
+
+ it('should specify the pagination info', () => {
+ expect(body.page).toEqual({
+ size: 10,
+ totalElements: 248,
+ totalPages: 25,
+ number: 4
+ });
+ });
+
+ it('should specify the sort options', () => {
+ expect(body.sort).toEqual({
+ by: 'search-field',
+ order: 'asc'
+ });
+ });
+ });
+
+ describe('trackSearchEvent with optional parameters', () => {
+ requestService = getMockRequestService();
+ service = initTestService();
+
+ const mockSearch:any = new SearchOptions({
+ query: 'mock-query',
+ configuration: 'mock-configuration',
+ dsoType: DSpaceObjectType.ITEM,
+ scope: 'mock-scope'
+ });
+
+ const page = {
+ size: 10,
+ totalElements: 248,
+ totalPages: 25,
+ number: 4
+ };
+ const sort = {by: 'search-field', order: 'ASC'};
+ const filters = [
+ {
+ filter: 'title',
+ operator: 'notcontains',
+ value: 'dolor sit',
+ label: 'dolor sit'
+ },
+ {
+ filter: 'author',
+ operator: 'authority',
+ value: '9zvxzdm4qru17or5a83wfgac',
+ label: 'Amet, Consectetur'
+ }
+ ];
+ service.trackSearchEvent(mockSearch, page, sort, filters);
+ const request:TrackRequest = requestService.configure.calls.mostRecent().args[0];
+ const body = JSON.parse(request.body);
+
+ it('should specify the dsoType', () => {
+ expect(body.dsoType).toBe('item');
+ });
+
+ it('should specify the scope', () => {
+ expect(body.scope).toBe('mock-scope');
+ });
+
+ it('should specify the configuration', () => {
+ expect(body.configuration).toBe('mock-configuration');
+ });
+
+ it('should specify the filters', () => {
+ expect(isEqual(body.appliedFilters, [
+ {
+ filter: 'title',
+ operator: 'notcontains',
+ value: 'dolor sit',
+ label: 'dolor sit'
+ },
+ {
+ filter: 'author',
+ operator: 'authority',
+ value: '9zvxzdm4qru17or5a83wfgac',
+ label: 'Amet, Consectetur'
+ }
+ ])).toBe(true);
+ });
+ });
+
+});
diff --git a/src/app/statistics/statistics.service.ts b/src/app/statistics/statistics.service.ts
new file mode 100644
index 0000000000..cd89125b3c
--- /dev/null
+++ b/src/app/statistics/statistics.service.ts
@@ -0,0 +1,93 @@
+import { RequestService } from '../core/data/request.service';
+import { Injectable } from '@angular/core';
+import { DSpaceObject } from '../core/shared/dspace-object.model';
+import { map, take } from 'rxjs/operators';
+import { TrackRequest } from './track-request.model';
+import { SearchOptions } from '../+search-page/search-options.model';
+import { hasValue, isNotEmpty } from '../shared/empty.util';
+import { HALEndpointService } from '../core/shared/hal-endpoint.service';
+import { RestRequest } from '../core/data/request.models';
+
+/**
+ * The statistics service
+ */
+@Injectable()
+export class StatisticsService {
+
+ constructor(
+ protected requestService:RequestService,
+ protected halService:HALEndpointService,
+ ) {
+ }
+
+ private sendEvent(linkPath:string, body:any) {
+ const requestId = this.requestService.generateRequestId();
+ this.halService.getEndpoint(linkPath).pipe(
+ map((endpoint:string) => new TrackRequest(requestId, endpoint, JSON.stringify(body))),
+ take(1) // otherwise the previous events will fire again
+ ).subscribe((request:RestRequest) => this.requestService.configure(request));
+ }
+
+ /**
+ * To track a page view
+ * @param dso: The dso which was viewed
+ */
+ trackViewEvent(dso:DSpaceObject) {
+ this.sendEvent('/statistics/viewevents', {
+ targetId: dso.uuid,
+ targetType: (dso as any).type
+ });
+ }
+
+ /**
+ * To track a search
+ * @param searchOptions: The query, scope, dsoType and configuration of the search. Filters from this object are ignored in favor of the filters parameter of this method.
+ * @param page: An object that describes the pagination status
+ * @param sort: An object that describes the sort status
+ * @param filters: An array of search filters used to filter the result set
+ */
+ trackSearchEvent(
+ searchOptions:SearchOptions,
+ page:{ size:number, totalElements:number, totalPages:number, number:number },
+ sort:{ by:string, order:string },
+ filters?:Array<{ filter:string, operator:string, value:string, label:string }>
+ ) {
+ const body = {
+ query: searchOptions.query,
+ page: {
+ size: page.size,
+ totalElements: page.totalElements,
+ totalPages: page.totalPages,
+ number: page.number
+ },
+ sort: {
+ by: sort.by,
+ order: sort.order.toLowerCase()
+ },
+ };
+ if (hasValue(searchOptions.configuration)) {
+ Object.assign(body, {configuration: searchOptions.configuration})
+ }
+ if (hasValue(searchOptions.dsoType)) {
+ Object.assign(body, {dsoType: searchOptions.dsoType.toLowerCase()})
+ }
+ if (hasValue(searchOptions.scope)) {
+ Object.assign(body, {scope: searchOptions.scope})
+ }
+ if (isNotEmpty(filters)) {
+ const bodyFilters = [];
+ for (let i = 0, arrayLength = filters.length; i < arrayLength; i++) {
+ const filter = filters[i];
+ bodyFilters.push({
+ filter: filter.filter,
+ operator: filter.operator,
+ value: filter.value,
+ label: filter.label
+ })
+ }
+ Object.assign(body, {appliedFilters: bodyFilters})
+ }
+ this.sendEvent('/statistics/searchevents', body);
+ }
+
+}
diff --git a/src/app/statistics/track-request.model.ts b/src/app/statistics/track-request.model.ts
new file mode 100644
index 0000000000..df3e51c070
--- /dev/null
+++ b/src/app/statistics/track-request.model.ts
@@ -0,0 +1,4 @@
+import { PostRequest } from '../core/data/request.models';
+
+export class TrackRequest extends PostRequest {
+}
diff --git a/src/app/submission/form/collection/submission-form-collection.component.ts b/src/app/submission/form/collection/submission-form-collection.component.ts
index 79d2f2a7bc..f84764d6a4 100644
--- a/src/app/submission/form/collection/submission-form-collection.component.ts
+++ b/src/app/submission/form/collection/submission-form-collection.component.ts
@@ -36,7 +36,7 @@ import { SubmissionService } from '../../submission.service';
import { SubmissionObject } from '../../../core/submission/models/submission-object.model';
import { SubmissionJsonPatchOperationsService } from '../../../core/submission/submission-json-patch-operations.service';
import { CollectionDataService } from '../../../core/data/collection-data.service';
-import { FindAllOptions } from '../../../core/data/request.models';
+import { FindListOptions } from '../../../core/data/request.models';
/**
* An interface to represent a collection entry
@@ -205,7 +205,7 @@ export class SubmissionFormCollectionComponent implements OnChanges, OnInit {
map((collectionRD: RemoteData) => collectionRD.payload.name)
);
- const findOptions: FindAllOptions = {
+ const findOptions: FindListOptions = {
elementsPerPage: 1000
};
diff --git a/src/app/submission/form/submission-form.component.ts b/src/app/submission/form/submission-form.component.ts
index a11ad43db3..3ea07f9ae7 100644
--- a/src/app/submission/form/submission-form.component.ts
+++ b/src/app/submission/form/submission-form.component.ts
@@ -1,7 +1,7 @@
import { ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { of as observableOf, Observable, Subscription } from 'rxjs';
-import { distinctUntilChanged, filter, flatMap, map } from 'rxjs/operators';
+import { distinctUntilChanged, filter, flatMap, map, switchMap } from 'rxjs/operators';
import { hasValue, isNotEmpty } from '../../shared/empty.util';
import { SubmissionObjectEntry } from '../objects/submission-objects.reducer';
@@ -120,7 +120,7 @@ export class SubmissionFormComponent implements OnChanges, OnDestroy {
map((submission: SubmissionObjectEntry) => submission.isLoading),
map((isLoading: boolean) => isLoading),
distinctUntilChanged(),
- flatMap((isLoading: boolean) => {
+ switchMap((isLoading: boolean) => {
if (!isLoading) {
return this.getSectionsList();
} else {
diff --git a/src/app/submission/sections/form/section-form.component.ts b/src/app/submission/sections/form/section-form.component.ts
index 2269ccd5f1..c1ca394911 100644
--- a/src/app/submission/sections/form/section-form.component.ts
+++ b/src/app/submission/sections/form/section-form.component.ts
@@ -215,6 +215,7 @@ export class SubmissionSectionformComponent extends SectionModelComponent {
initForm(sectionData: WorkspaceitemSectionFormObject): void {
try {
this.formModel = this.formBuilderService.modelFromConfiguration(
+ this.submissionId,
this.formConfig,
this.collectionId,
sectionData,
diff --git a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts
index b2575d1d58..8cf0d22d20 100644
--- a/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts
+++ b/src/app/submission/sections/upload/file/edit/section-upload-file-edit.component.ts
@@ -166,6 +166,7 @@ export class SubmissionSectionUploadFileEditComponent implements OnChanges {
const formModel: DynamicFormControlModel[] = [];
const metadataGroupModelConfig = Object.assign({}, BITSTREAM_METADATA_FORM_GROUP_CONFIG);
metadataGroupModelConfig.group = this.formBuilderService.modelFromConfiguration(
+ this.submissionId,
configForm,
this.collectionId,
this.fileData.metadata,
diff --git a/src/modules/app/browser-app.module.ts b/src/modules/app/browser-app.module.ts
index ede2b53e74..87b830ee7d 100644
--- a/src/modules/app/browser-app.module.ts
+++ b/src/modules/app/browser-app.module.ts
@@ -21,6 +21,8 @@ import { AuthService } from '../../app/core/auth/auth.service';
import { Angulartics2Module } from 'angulartics2';
import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { SubmissionService } from '../../app/submission/submission.service';
+import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
+import { StatisticsModule } from '../../app/statistics/statistics.module';
export const REQ_KEY = makeStateKey('req');
@@ -47,7 +49,8 @@ export function getRequest(transferState: TransferState): any {
preloadingStrategy:
IdlePreload
}),
- Angulartics2Module.forRoot([Angulartics2GoogleAnalytics]),
+ StatisticsModule.forRoot(),
+ Angulartics2Module.forRoot([Angulartics2GoogleAnalytics, Angulartics2DSpace]),
BrowserAnimationsModule,
DSpaceBrowserTransferStateModule,
TranslateModule.forRoot({
diff --git a/src/modules/app/server-app.module.ts b/src/modules/app/server-app.module.ts
index 02abf6449b..44b21859bd 100644
--- a/src/modules/app/server-app.module.ts
+++ b/src/modules/app/server-app.module.ts
@@ -22,6 +22,8 @@ import { Angulartics2GoogleAnalytics } from 'angulartics2/ga';
import { AngularticsMock } from '../../app/shared/mocks/mock-angulartics.service';
import { SubmissionService } from '../../app/submission/submission.service';
import { ServerSubmissionService } from '../../app/submission/server-submission.service';
+import { Angulartics2DSpace } from '../../app/statistics/angulartics/dspace-provider';
+import { Angulartics2Module } from 'angulartics2';
export function createTranslateLoader() {
return new TranslateJson5UniversalLoader('dist/assets/i18n/', '.json5');
@@ -45,6 +47,7 @@ export function createTranslateLoader() {
deps: []
}
}),
+ Angulartics2Module.forRoot([Angulartics2GoogleAnalytics, Angulartics2DSpace]),
ServerModule,
AppModule
],
@@ -53,6 +56,10 @@ export function createTranslateLoader() {
provide: Angulartics2GoogleAnalytics,
useClass: AngularticsMock
},
+ {
+ provide: Angulartics2DSpace,
+ useClass: AngularticsMock
+ },
{
provide: AuthService,
useClass: ServerAuthService
diff --git a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
index 99d92d2af8..907f70b941 100644
--- a/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
+++ b/themes/mantis/app/entity-groups/journal-entities/item-pages/journal-volume/journal-volume.component.html
@@ -4,7 +4,7 @@
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
similarity index 79%
rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
index 15529a1bd5..ee78d9c653 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.html
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.html
@@ -53,18 +53,6 @@
+
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
similarity index 86%
rename from themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
rename to themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
index 54651aede0..4a1d2516da 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss
@@ -1,4 +1,4 @@
-@import 'src/app/entity-groups/research-entities/item-pages/orgunit/orgunit.component.scss';
+@import 'src/app/entity-groups/research-entities/item-pages/org-unit/org-unit.component.scss';
:host {
> * {
diff --git a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
index bb5cb1b787..1679f9354d 100644
--- a/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
+++ b/themes/mantis/app/entity-groups/research-entities/item-pages/person/person.component.html
@@ -79,7 +79,10 @@
{{"item.page.person.search.title" | translate}}
-
-
+
+
diff --git a/webpack/webpack.common.js b/webpack/webpack.common.js
index 028815d958..e63ae024ed 100644
--- a/webpack/webpack.common.js
+++ b/webpack/webpack.common.js
@@ -15,6 +15,9 @@ module.exports = (env) => {
let copyWebpackOptions = [{
from: path.join(__dirname, '..', 'node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'),
to: path.join('assets', 'fonts')
+ }, {
+ from: path.join(__dirname, '..', 'resources', 'fonts'),
+ to: path.join('assets', 'fonts')
}, {
from: path.join(__dirname, '..', 'resources', 'images'),
to: path.join('assets', 'images')
@@ -24,6 +27,15 @@ module.exports = (env) => {
}
];
+ const themeFonts = path.join(themePath, 'resources', 'fonts');
+ if(theme && fs.existsSync(themeFonts)) {
+ copyWebpackOptions.push({
+ from: themeFonts,
+ to: path.join('assets', 'fonts') ,
+ force: true,
+ });
+ }
+
const themeImages = path.join(themePath, 'resources', 'images');
if(theme && fs.existsSync(themeImages)) {
copyWebpackOptions.push({
@@ -107,12 +119,6 @@ module.exports = (env) => {
sourceMap: true
}
},
- {
- loader: 'resolve-url-loader',
- options: {
- sourceMap: true
- }
- },
{
loader: 'sass-loader',
options: {
@@ -120,6 +126,12 @@ module.exports = (env) => {
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
}
},
+ {
+ loader: 'resolve-url-loader',
+ options: {
+ sourceMap: true
+ }
+ },
{
loader: 'sass-resources-loader',
options: {
@@ -145,23 +157,23 @@ module.exports = (env) => {
sourceMap: true
}
},
- {
- loader: 'resolve-url-loader',
- options: {
- sourceMap: true
- }
- },
{
loader: 'sass-loader',
options: {
sourceMap: true,
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
}
- }
+ },
+ {
+ loader: 'resolve-url-loader',
+ options: {
+ sourceMap: true
+ }
+ },
]
},
{
- test: /\.html$/,
+ test: /\.(html|eot|ttf|otf|svg|woff|woff2)$/,
loader: 'raw-loader'
}
]
diff --git a/webpack/webpack.test.js b/webpack/webpack.test.js
index 83e6e44e79..de53de31c4 100644
--- a/webpack/webpack.test.js
+++ b/webpack/webpack.test.js
@@ -160,12 +160,6 @@ module.exports = function (env) {
sourceMap: true
}
},
- {
- loader: 'resolve-url-loader',
- options: {
- sourceMap: true
- }
- },
{
loader: 'sass-loader',
options: {
@@ -173,6 +167,12 @@ module.exports = function (env) {
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
}
},
+ {
+ loader: 'resolve-url-loader',
+ options: {
+ sourceMap: true
+ }
+ },
{
loader: 'sass-resources-loader',
options: {
@@ -198,19 +198,19 @@ module.exports = function (env) {
sourceMap: true
}
},
- {
- loader: 'resolve-url-loader',
- options: {
- sourceMap: true
- }
- },
{
loader: 'sass-loader',
options: {
sourceMap: true,
includePaths: [projectRoot('./'), path.join(themePath, 'styles')]
}
- }
+ },
+ {
+ loader: 'resolve-url-loader',
+ options: {
+ sourceMap: true
+ }
+ },
]
},
diff --git a/yarn.lock b/yarn.lock
index 69f4a072ae..c3f9e59a4d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -35,6 +35,13 @@
dependencies:
tslib "^1.9.0"
+"@angular/cdk@^6.4.7":
+ version "6.4.7"
+ resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-6.4.7.tgz#1549b304dd412e82bd854cc55a7d5c6772ee0411"
+ integrity sha512-18x0U66fLD5kGQWZ9n3nb75xQouXlWs7kUDaTd8HTrHpT1s2QIAqlLd1KxfrYiVhsEC2jPQaoiae7VnBlcvkBg==
+ dependencies:
+ tslib "^1.7.1"
+
"@angular/cli@^6.1.5":
version "6.1.5"
resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-6.1.5.tgz#312c062631285ff06fd07ecde8afe22cdef5a0e1"
@@ -2093,15 +2100,14 @@ cliui@^5.0.0:
strip-ansi "^5.2.0"
wrap-ansi "^5.1.0"
-clone-deep@^2.0.1:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-2.0.2.tgz#00db3a1e173656730d1188c3d6aced6d7ea97713"
- integrity sha512-SZegPTKjCgpQH63E+eN6mVEEPdQBOUzjyJm5Pora4lrwWRFS8I0QAxV/KD6vV/i0WuijHZWQC1fMsPEdxfdVCQ==
+clone-deep@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387"
+ integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==
dependencies:
- for-own "^1.0.0"
is-plain-object "^2.0.4"
- kind-of "^6.0.0"
- shallow-clone "^1.0.0"
+ kind-of "^6.0.2"
+ shallow-clone "^3.0.0"
clone-stats@^0.0.1:
version "0.0.1"
@@ -4121,11 +4127,6 @@ font-awesome@4.7.0:
resolved "https://registry.yarnpkg.com/font-awesome/-/font-awesome-4.7.0.tgz#8fa8cf0411a1a31afd07b06d2902bb9fc815a133"
integrity sha1-j6jPBBGhoxr9B7BtKQK7n8gVoTM=
-for-in@^0.1.3:
- version "0.1.8"
- resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1"
- integrity sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=
-
for-in@^1.0.1, for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -4138,13 +4139,6 @@ for-own@^0.1.4:
dependencies:
for-in "^1.0.1"
-for-own@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b"
- integrity sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=
- dependencies:
- for-in "^1.0.1"
-
forever-agent@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
@@ -6148,7 +6142,7 @@ loader-utils@^0.2.12, loader-utils@^0.2.15, loader-utils@^0.2.16:
json5 "^0.5.0"
object-assign "^4.0.1"
-loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0:
+loader-utils@^1.0.0, loader-utils@^1.0.2, loader-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd"
integrity sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=
@@ -6157,7 +6151,7 @@ loader-utils@^1.0.0, loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1
emojis-list "^2.0.0"
json5 "^0.5.0"
-loader-utils@^1.0.4:
+loader-utils@^1.0.1, loader-utils@^1.0.4:
version "1.2.3"
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
dependencies:
@@ -6371,11 +6365,6 @@ lodash.startswith@^4.2.1:
resolved "https://registry.yarnpkg.com/lodash.startswith/-/lodash.startswith-4.2.1.tgz#c598c4adce188a27e53145731cdc6c0e7177600c"
integrity sha1-xZjErc4YiiflMUVzHNxsDnF3YAw=
-lodash.tail@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664"
- integrity sha1-0jM6NtnncXyK0vfKyv7HwytERmQ=
-
lodash.template@^3.0.0:
version "3.6.2"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f"
@@ -6849,14 +6838,6 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mixin-object@^2.0.1:
- version "2.0.1"
- resolved "https://registry.yarnpkg.com/mixin-object/-/mixin-object-2.0.1.tgz#4fb949441dab182540f1fe035ba60e1947a5e57e"
- integrity sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=
- dependencies:
- for-in "^0.1.3"
- is-extendable "^0.1.1"
-
mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
@@ -9654,17 +9635,16 @@ sass-graph@^2.2.4:
scss-tokenizer "^0.2.3"
yargs "^7.0.0"
-sass-loader@7.1.0:
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.1.0.tgz#16fd5138cb8b424bf8a759528a1972d72aad069d"
- integrity sha512-+G+BKGglmZM2GUSfT9TLuEp6tzehHPjAMoRRItOojWIqIGPloVCMhNIQuG639eJ+y033PaGTSjLaTHts8Kw79w==
+sass-loader@^7.1.0:
+ version "7.3.1"
+ resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-7.3.1.tgz#a5bf68a04bcea1c13ff842d747150f7ab7d0d23f"
+ integrity sha512-tuU7+zm0pTCynKYHpdqaPpe+MMTQ76I9TPZ7i4/5dZsigE350shQWe5EZNl5dBidM49TPET75tNqRbcsUZWeNA==
dependencies:
- clone-deep "^2.0.1"
+ clone-deep "^4.0.1"
loader-utils "^1.0.1"
- lodash.tail "^4.1.1"
neo-async "^2.5.0"
- pify "^3.0.0"
- semver "^5.5.0"
+ pify "^4.0.1"
+ semver "^6.3.0"
sass-resources-loader@^2.0.0:
version "2.0.0"
@@ -9769,7 +9749,7 @@ semver-intersect@^1.1.2:
dependencies:
semver "^5.0.0"
-"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0:
+"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.0.0, semver@^5.0.3, semver@^5.1.0, semver@^5.3.0:
version "5.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477"
integrity sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==
@@ -9779,7 +9759,12 @@ semver@^5.0.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==
-semver@^6.1.1:
+semver@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+semver@^6.1.1, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@@ -9910,14 +9895,12 @@ sha.js@^2.4.0, sha.js@^2.4.8:
inherits "^2.0.1"
safe-buffer "^5.0.1"
-shallow-clone@^1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-1.0.0.tgz#4480cd06e882ef68b2ad88a3ea54832e2c48b571"
- integrity sha512-oeXreoKR/SyNJtRJMAKPDSvd28OqEwG4eR/xc856cRGBII7gX9lvAqDxusPm0846z/w/hWYjI1NpKwJ00NHzRA==
+shallow-clone@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3"
+ integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==
dependencies:
- is-extendable "^0.1.1"
- kind-of "^5.0.0"
- mixin-object "^2.0.1"
+ kind-of "^6.0.2"
shebang-command@^1.2.0:
version "1.2.0"
@@ -10833,6 +10816,11 @@ tsickle@^0.32.1:
source-map "^0.6.0"
source-map-support "^0.5.0"
+tslib@^1.7.1:
+ version "1.10.0"
+ resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
+ integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
+
tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0:
version "1.9.3"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"