diff --git a/Jenkinsfile b/Jenkinsfile index 557a72035..7d60a49d7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -74,13 +74,74 @@ pipeline { } } } - post{ - always{ - recordIssues tools: [esLint(pattern: '**/eslint_report.xml')] + post { + always { + recordIssues qualityGates: [[threshold: 1, type: 'TOTAL', unstable: true]], tools: [esLint(pattern: '**/eslint_report.xml')] + } + } + } + + stage('Distribution Build') { + steps { + nvm('') { + catchError(buildResult: 'UNSTABLE', stageResult: 'UNSTABLE') { + sh 'npm run dist' + } } } } + // Deploy develop branch even if tests fail if the code builds, as it'll be an unstable snapshot but we should still deploy + stage('Deploy develop to Artifactory') { + when { + branch 'develop' + } + steps { + rtUpload( + serverId: 'cs-artifactory', + spec: '''{ + "files": [ + { + "pattern": "dist/mdm-ui-*.tgz", + "target": "artifacts-snapshots/mauroDataMapper/mdm-ui/" + } + ] + }''', + ) + rtPublishBuildInfo( + serverId: 'cs-artifactory', + ) + } + } + + stage('Deploy main to Artifactory') { + when { + allOf { + branch 'main' + expression { + currentBuild.currentResult == 'SUCCESS' + } + } + + } + steps { + rtUpload( + serverId: 'cs-artifactory', + spec: '''{ + "files": [ + { + "pattern": "dist/mdm-ui-*.tgz", + "target": "artifacts/mauroDataMapper/mdm-ui/" + } + ] + }''', + ) + rtPublishBuildInfo( + serverId: 'cs-artifactory', + ) + } + } + stage('Sonarqube') { when { branch 'develop' @@ -95,6 +156,28 @@ pipeline { } } } + + stage('Continuous Deployment') { + when { + allOf { + branch 'develop' + expression { + currentBuild.currentResult == 'SUCCESS' + } + } + } + steps { + script { + try { + println("Triggering the [continuous-deployment] job") + build quietPeriod: 300, wait: false, job: 'continuous-deployment' + } catch (hudson.AbortException ignored) { + println("Cannot trigger the [continuous-deployment] job as it doesn't exist") + } + } + } + } + } post { always { @@ -108,7 +191,6 @@ pipeline { reportTitles : 'Test' ]) outputTestResults() - slackNotification() zulipNotification(topic: 'mdm-ui') } } diff --git a/package-lock.json b/package-lock.json index 489df93cc..2d3c40c6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { "name": "mdm-ui", - "version": "6.5.0", + "version": "6.6.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "version": "6.5.0", + "version": "6.6.0", "hasInstallScript": true, "dependencies": { "@angular-eslint/template-parser": "0.5.0-beta.3", @@ -24,7 +24,7 @@ "@bpmn-io/dmn-migrate": "^0.4.3", "@ctrl/ngx-codemirror": "4.0.1", "@fortawesome/fontawesome-free": "5.15.1", - "@maurodatamapper/mdm-resources": "git+https://github.com/MauroDataMapper/mdm-resources.git#4.9.0", + "@maurodatamapper/mdm-resources": "git+https://github.com/MauroDataMapper/mdm-resources.git#4.10.0", "@uirouter/angular": "7.0.0", "@uirouter/core": "6.0.6", "@uirouter/rx": "0.6.5", @@ -2984,7 +2984,7 @@ }, "node_modules/@maurodatamapper/mdm-resources": { "version": "4.10.0", - "resolved": "git+ssh://git@github.com/MauroDataMapper/mdm-resources.git#6ede9f470c25957af9160c6ffc05f7dd75198cf4", + "resolved": "git+ssh://git@github.com/MauroDataMapper/mdm-resources.git#57006ec4e3c65d3ff3cc6e57aaf209a66bc1f4d5", "license": "Apache-2.0" }, "node_modules/@ngtools/webpack": { @@ -25376,8 +25376,8 @@ } }, "@maurodatamapper/mdm-resources": { - "version": "git+ssh://git@github.com/MauroDataMapper/mdm-resources.git#6ede9f470c25957af9160c6ffc05f7dd75198cf4", - "from": "@maurodatamapper/mdm-resources@git+https://github.com/MauroDataMapper/mdm-resources.git#4.9.0" + "version": "git+ssh://git@github.com/MauroDataMapper/mdm-resources.git#57006ec4e3c65d3ff3cc6e57aaf209a66bc1f4d5", + "from": "@maurodatamapper/mdm-resources@git+https://github.com/MauroDataMapper/mdm-resources.git#4.10.0" }, "@ngtools/webpack": { "version": "9.1.12", diff --git a/package.json b/package.json index 83124c36c..81218a908 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mdm-ui", - "version": "6.5.0", + "version": "6.6.0", "scripts": { "ng": "ng", "start": "ng serve", @@ -12,8 +12,8 @@ "test-watch": "jest --watch", "test-clearCache": "jest --clearCache", "lint": "ng lint", - "eslint": "tsc --noEmit && eslint . --ext ts --quiet --fix", - "eslint-nofix": "tsc --noEmit && eslint . --ext ts --quiet", + "eslint": "tsc --noEmit && eslint . --ext ts --fix", + "eslint-nofix": "tsc --noEmit && eslint . --ext ts", "eslint-report": "eslint . --ext ts --format checkstyle -o eslint_report.xml", "e2e": "ng e2e", "postinstall": "ngcc", @@ -38,7 +38,7 @@ "@bpmn-io/dmn-migrate": "^0.4.3", "@ctrl/ngx-codemirror": "4.0.1", "@fortawesome/fontawesome-free": "5.15.1", - "@maurodatamapper/mdm-resources": "git+https://github.com/MauroDataMapper/mdm-resources.git#4.9.0", + "@maurodatamapper/mdm-resources": "git+https://github.com/MauroDataMapper/mdm-resources.git#4.10.0", "@uirouter/angular": "7.0.0", "@uirouter/core": "6.0.6", "@uirouter/rx": "0.6.5", diff --git a/src/app/admin/api-property/api-property.component.ts b/src/app/admin/api-property/api-property.component.ts index b6c7e24dc..86d05f203 100644 --- a/src/app/admin/api-property/api-property.component.ts +++ b/src/app/admin/api-property/api-property.component.ts @@ -87,76 +87,6 @@ export class ApiPropertyComponent implements OnInit { } } - private createFormGroup() { - this.formGroup = new FormGroup({ - key: new FormControl(this.property.metadata.key, [Validators.required]), // eslint-disable-line @typescript-eslint/unbound-method - category: new FormControl(this.property.metadata.category, [Validators.required]), // eslint-disable-line @typescript-eslint/unbound-method - publiclyVisible: new FormControl({ value: this.property.metadata.publiclyVisible, disabled: this.property.metadata.isSystem }), - value: new FormControl(this.property.original?.value, [Validators.required]) // eslint-disable-line @typescript-eslint/unbound-method - }); - } - - private getBlankMetadata() { - return { - key: '', - category: '', - publiclyVisible: false, - editType: ApiPropertyEditType.Value, - isSystem: false - }; - } - - private loadExistingProperty() { - this.resources.apiProperties - .get(this.id) - .pipe( - map((response: ApiPropertyResponse): ApiPropertyEditableState => { - const original = response.body; - const metadata = propertyMetadata.find(p => p.key === original.key) ?? { - key: original.key, - category: original.category, - isSystem: false, - publiclyVisible: original.publiclyVisible, - editType: ApiPropertyEditType.Value - }; - - return { - metadata, - original - }; - }), - catchError(errors => { - this.messageHandler.showError('There was a problem getting the property.', errors); - return []; - }) - ) - .subscribe((data: ApiPropertyEditableState) => { - this.property = data; - this.createFormGroup(); - }); - } - - private loadAvailableSystemProperties() { - this.resources.apiProperties - .list() - .pipe( - map((response: ApiPropertyIndexResponse) => { - return response.body.items; - }), - catchError(errors => { - this.messageHandler.showError('There was a problem getting the properties.', errors); - return []; - }) - ) - .subscribe((data: ApiProperty[]) => { - this.systemProperties = propertyMetadata.filter(m => data.every(p => p.key !== m.key)); - this.property = { - metadata: this.getBlankMetadata() - }; - this.createFormGroup(); - }); - } - systemPropertyChanged(change: MatSelectChange) { if (change.value) { this.property.metadata = propertyMetadata.find(m => m.key === change.value); @@ -243,6 +173,76 @@ export class ApiPropertyComponent implements OnInit { } } + private createFormGroup() { + this.formGroup = new FormGroup({ + key: new FormControl(this.property.metadata.key, [Validators.required]), // eslint-disable-line @typescript-eslint/unbound-method + category: new FormControl(this.property.metadata.category, [Validators.required]), // eslint-disable-line @typescript-eslint/unbound-method + publiclyVisible: new FormControl({ value: this.property.metadata.publiclyVisible, disabled: this.property.metadata.isSystem }), + value: new FormControl(this.property.original?.value, [Validators.required]) // eslint-disable-line @typescript-eslint/unbound-method + }); + } + + private getBlankMetadata() { + return { + key: '', + category: '', + publiclyVisible: false, + editType: ApiPropertyEditType.Value, + isSystem: false + }; + } + + private loadExistingProperty() { + this.resources.apiProperties + .get(this.id) + .pipe( + map((response: ApiPropertyResponse): ApiPropertyEditableState => { + const original = response.body; + const metadata = propertyMetadata.find(p => p.key === original.key) ?? { + key: original.key, + category: original.category, + isSystem: false, + publiclyVisible: original.publiclyVisible, + editType: ApiPropertyEditType.Value + }; + + return { + metadata, + original + }; + }), + catchError(errors => { + this.messageHandler.showError('There was a problem getting the property.', errors); + return []; + }) + ) + .subscribe((data: ApiPropertyEditableState) => { + this.property = data; + this.createFormGroup(); + }); + } + + private loadAvailableSystemProperties() { + this.resources.apiProperties + .list() + .pipe( + map((response: ApiPropertyIndexResponse) => { + return response.body.items; + }), + catchError(errors => { + this.messageHandler.showError('There was a problem getting the properties.', errors); + return []; + }) + ) + .subscribe((data: ApiProperty[]) => { + this.systemProperties = propertyMetadata.filter(m => data.every(p => p.key !== m.key)); + this.property = { + metadata: this.getBlankMetadata() + }; + this.createFormGroup(); + }); + } + private navigateToParent() { this.editing.stop(); this.stateHandler.Go( diff --git a/src/app/admin/openid-connect-provider-table/openid-connect-provider-table.component.ts b/src/app/admin/openid-connect-provider-table/openid-connect-provider-table.component.ts index c8b12a15c..10afb56ee 100644 --- a/src/app/admin/openid-connect-provider-table/openid-connect-provider-table.component.ts +++ b/src/app/admin/openid-connect-provider-table/openid-connect-provider-table.component.ts @@ -22,7 +22,7 @@ import { MatSort, SortDirection } from '@angular/material/sort'; import { MatTableDataSource } from '@angular/material/table'; import { Title } from '@angular/platform-browser'; import { ModulesResponse, OpenIdConnectProvider, OpenIdConnectProvidersIndexResponse } from '@maurodatamapper/mdm-resources'; -import { OpenIdConnectModuleName } from '@mdm/model/openid-connect.model'; +import { openIdConnectModuleName } from '@mdm/model/openid-connect.model'; import { MdmResourcesService } from '@mdm/modules/resources'; import { GridService, MessageHandlerService, SharedService, StateHandlerService } from '@mdm/services'; import { MdmPaginatorComponent } from '@mdm/shared/mdm-paginator/mdm-paginator'; @@ -78,7 +78,7 @@ export class OpenidConnectProviderTableComponent implements OnInit, AfterViewIni finalize(() => this.loading = false) ) .subscribe((response: ModulesResponse) => { - this.moduleLoaded = response.body.findIndex(module => module.name === OpenIdConnectModuleName) !== -1; + this.moduleLoaded = response.body.findIndex(module => module.name === openIdConnectModuleName) !== -1; if (this.moduleLoaded) { this.initialise(); } diff --git a/src/app/admin/subscribed-catalogue/subscribed-catalogue.component.ts b/src/app/admin/subscribed-catalogue/subscribed-catalogue.component.ts index ddcdf6237..bd17b2f25 100644 --- a/src/app/admin/subscribed-catalogue/subscribed-catalogue.component.ts +++ b/src/app/admin/subscribed-catalogue/subscribed-catalogue.component.ts @@ -118,11 +118,6 @@ export class SubscribedCatalogueComponent implements OnInit { }); } - private navigateToParent() { - this.editingService.stop(); - this.stateHandler.Go('appContainer.adminArea.subscribedCatalogues'); - } - validate() { let isValid = true; this.errors = {}; @@ -144,4 +139,9 @@ export class SubscribedCatalogueComponent implements OnInit { return isValid; } + + private navigateToParent() { + this.editingService.stop(); + this.stateHandler.Go('appContainer.adminArea.subscribedCatalogues'); + } } diff --git a/src/app/admin/user/user.component.ts b/src/app/admin/user/user.component.ts index b3275e813..32693012f 100644 --- a/src/app/admin/user/user.component.ts +++ b/src/app/admin/user/user.component.ts @@ -204,11 +204,6 @@ export class UserComponent implements OnInit { }); }; - private navigateToParent() { - this.editingService.stop(); - this.stateHandler.Go('admin.users'); - } - onGroupSelect = (groups) => { this.user.groups = []; for (const val of this.allGroups) { @@ -220,4 +215,9 @@ export class UserComponent implements OnInit { } } }; + + private navigateToParent() { + this.editingService.stop(); + this.stateHandler.Go('admin.users'); + } } diff --git a/src/app/data-type/data-type-detail/data-type-detail.component.html b/src/app/data-type/data-type-detail/data-type-detail.component.html index 7770fbc15..a2b8ea48a 100644 --- a/src/app/data-type/data-type-detail/data-type-detail.component.html +++ b/src/app/data-type/data-type-detail/data-type-detail.component.html @@ -56,7 +56,7 @@

- +

+ + + {{ parentLabel }}:
-
@@ -77,6 +77,9 @@

+ + + {{ parentLabel }}: Create a New Version - - -

+ + + + diff --git a/src/app/modals/modal.module.ts b/src/app/modals/modal.module.ts index a65274094..93d2ade56 100644 --- a/src/app/modals/modal.module.ts +++ b/src/app/modals/modal.module.ts @@ -49,7 +49,7 @@ import { MarkupDisplayModalComponent } from './markup-display-modal/markup-displ import { DefaultProfileEditorModalComponent } from './default-profile-editor-modal/default-profile-editor-modal.component'; import { PipesModule } from '@mdm/modules/pipes/pipes.module'; -const DefaultAceConfig: AceConfigInterface = { +const defaultAceConfig: AceConfigInterface = { }; @NgModule({ @@ -95,7 +95,7 @@ const DefaultAceConfig: AceConfigInterface = { ModalService, { provide: ACE_CONFIG, - useValue: DefaultAceConfig + useValue: defaultAceConfig } ], exports: [ diff --git a/src/app/model/access.ts b/src/app/model/access.ts index a7a091155..222971131 100644 --- a/src/app/model/access.ts +++ b/src/app/model/access.ts @@ -44,4 +44,5 @@ export interface Access { canMoveToVersionedFolder: boolean; canReadAfterFinalised: boolean; canEditAfterFinalise: boolean; + canMergeInto: boolean; } \ No newline at end of file diff --git a/src/app/model/openid-connect.model.ts b/src/app/model/openid-connect.model.ts index 80dc6b4ca..119e89e3a 100644 --- a/src/app/model/openid-connect.model.ts +++ b/src/app/model/openid-connect.model.ts @@ -17,4 +17,4 @@ limitations under the License. SPDX-License-Identifier: Apache-2.0 */ -export const OpenIdConnectModuleName = 'mdm.pluginAuthenticationOpenidConnect'; \ No newline at end of file +export const openIdConnectModuleName = 'mdm.pluginAuthenticationOpenidConnect'; \ No newline at end of file diff --git a/src/app/services/content-search.handler.service.ts b/src/app/services/content-search.handler.service.ts index f807a0542..d9e649d33 100644 --- a/src/app/services/content-search.handler.service.ts +++ b/src/app/services/content-search.handler.service.ts @@ -22,7 +22,6 @@ import { ValidatorService } from './validator.service'; import { ElementTypesService } from './element-types.service'; import { DatePipe } from '@angular/common'; import { Observable } from 'rxjs'; -import { ContainerDomainType } from '@maurodatamapper/mdm-resources'; @Injectable({ providedIn: 'root' @@ -97,28 +96,26 @@ export class ContentSearchHandlerService { pageSize: limit, pageIndex: offset * limit }); - } else if (contextElement.domainType === 'Folder') { - return this.resources.tree.search(ContainerDomainType.Folders, searchText, - { - searchTerm: searchText, - limit, - offset, - domainTypes, - labelOnly, - dataModelTypes, - classifiers, - classifierFilter, - - lastUpdatedAfter, - lastUpdatedBefore, - - createdAfter, - createdBefore, - - pageSize: limit, - pageIndex: offset * limit - - }); + } + else if (contextElement.domainType === 'Folder') { + return this.resources.folder.search( + contextElement.id, + { + searchTerm: searchText, + limit, + offset, + domainTypes, + labelOnly, + dataModelTypes, + + lastUpdatedAfter, + lastUpdatedBefore, + + createdAfter, + createdBefore, + pageSize: limit, + pageIndex: offset * limit + }); } else if (contextElement.domainType === 'DataModel') { return this.resources.dataModel.search(contextElement.id, { searchTerm: searchText, diff --git a/src/app/services/editing.service.ts b/src/app/services/editing.service.ts index 6c1079d3a..9b679d8db 100644 --- a/src/app/services/editing.service.ts +++ b/src/app/services/editing.service.ts @@ -177,14 +177,6 @@ export class EditingService { */ confirmLeave = (): boolean => this.confirmStop('Are you sure you want to leave this view? Any unsaved changes will be lost.'); - private confirmStop(message: string): boolean { - if (!this._isEditing) { - return true; - } - - return confirm(message); - } - /** * Confirm if it is safe to leave a view to transition to another asynchronously. * @@ -206,6 +198,14 @@ export class EditingService { */ confirmCancelAsync = (): Observable => this.confirmStopAsync('Are you sure you want to cancel? Any unsaved changes will be lost.'); + private confirmStop(message: string): boolean { + if (!this._isEditing) { + return true; + } + + return confirm(message); + } + private confirmStopAsync(message: string): Observable { if (!this._isEditing) { return of(true); diff --git a/src/app/services/handlers/security-handler.service.ts b/src/app/services/handlers/security-handler.service.ts index 9dc974e10..45e338ed5 100644 --- a/src/app/services/handlers/security-handler.service.ts +++ b/src/app/services/handlers/security-handler.service.ts @@ -332,7 +332,8 @@ export class SecurityHandlerService { canMoveToFolder: element.availableActions?.includes('moveToFolder'), canMoveToVersionedFolder: element.availableActions?.includes('moveToVersionedFolder'), canReadAfterFinalised: element.availableActions?.includes('finalisedReadActions'), - canEditAfterFinalise: element.availableActions?.includes('finalisedEditActions') + canEditAfterFinalise: element.availableActions?.includes('finalisedEditActions'), + canMergeInto: element.availableActions?.includes('mergeInto') }; if ((element as Finalisable).finalised !== undefined) { diff --git a/src/app/shared/path-name/path-name.component.ts b/src/app/shared/path-name/path-name.component.ts index 5761d2d4e..884d01821 100644 --- a/src/app/shared/path-name/path-name.component.ts +++ b/src/app/shared/path-name/path-name.component.ts @@ -16,7 +16,7 @@ limitations under the License. SPDX-License-Identifier: Apache-2.0 */ -import { Component, Input, OnInit } from '@angular/core'; +import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; import { PathElement } from './path-name.model'; import { PathNameService } from './path-name.service'; @@ -25,7 +25,7 @@ import { PathNameService } from './path-name.service'; templateUrl: './path-name.component.html', styleUrls: ['./path-name.component.scss'] }) -export class PathNameComponent implements OnInit { +export class PathNameComponent implements OnInit, OnChanges { @Input() path: string; @Input() suffixIcon?: string; @Input() suffixLabel?: string; @@ -36,6 +36,16 @@ export class PathNameComponent implements OnInit { constructor(private pathNames: PathNameService) { } ngOnInit(): void { + this.parse(); + } + + ngOnChanges(changes: SimpleChanges): void { + if (changes.path) { + this.parse(); + } + } + + private parse() { this.elements = this.pathNames.parse(this.path); } } diff --git a/src/app/shared/profile-data-view/profile-data-view.component.ts b/src/app/shared/profile-data-view/profile-data-view.component.ts index 591a3f438..1380c0116 100644 --- a/src/app/shared/profile-data-view/profile-data-view.component.ts +++ b/src/app/shared/profile-data-view/profile-data-view.component.ts @@ -39,8 +39,8 @@ import { doiProfileNamespace, getDefaultProfileData, ProfileDataViewType, Profil }) export class ProfileDataViewComponent implements OnInit, OnChanges { @Input() catalogueItem: Modelable & ModelableDetail & SecurableModel & { - model?: Uuid; [key: string]: any; + model?: Uuid; }; @Output() savingDefault = new EventEmitter(); diff --git a/src/app/term/term-details/term-details.component.html b/src/app/term/term-details/term-details.component.html index 6946d675e..3c6c07430 100644 --- a/src/app/term/term-details/term-details.component.html +++ b/src/app/term/term-details/term-details.component.html @@ -62,6 +62,9 @@

+ + + {{ parentLabel }}: { limit = limit ? limit : 30; offset = offset ? offset : 0; this.pagination = { @@ -182,7 +184,7 @@ export class TerminologyComponent limit, offset }); - } + }; onTermSelect(term) { this.stateHandler.Go( diff --git a/src/app/versioned-folder/versioned-folder-detail/versioned-folder-detail.component.html b/src/app/versioned-folder/versioned-folder-detail/versioned-folder-detail.component.html index 554d6ac6f..dcd5e50e5 100644 --- a/src/app/versioned-folder/versioned-folder-detail/versioned-folder-detail.component.html +++ b/src/app/versioned-folder/versioned-folder-detail/versioned-folder-detail.component.html @@ -60,7 +60,7 @@ > Create a New Version -