From 804ffeeb176d572c002e0682a9a3f99fc14111da Mon Sep 17 00:00:00 2001 From: imx-sync-bot Date: Fri, 1 Sep 2023 11:08:35 +0000 Subject: [PATCH 1/2] Automated sync from source branch v91 --- .commit | 2 +- .../kpi-overview/kpi-overview.component.html | 6 +- .../kpi-overview/kpi-overview.component.scss | 58 ++++++++----- .../kpi-overview/kpi-overview.component.ts | 6 +- .../att/src/lib/new-user/new-user.module.ts | 39 ++++----- .../new-user/user-activation.component.html | 18 +--- .../lib/new-user/user-activation.component.ts | 80 +++++++++++------ .../policy-violations.service.ts | 4 +- .../bulk-item/bulk-item.component.scss | 6 ++ .../qer-app-portal/src/app/app.service.ts | 2 +- .../src/app/app.component.ts | 8 +- .../create-new-identity.component.ts | 8 +- .../justification/justification-type.enum.ts | 4 +- .../optional-items-sidesheet.component.ts | 11 +-- .../product-selection.component.ts | 9 +- .../service-item-edit.component.html | 4 +- .../service-item-edit.component.ts | 85 +++++++++++-------- .../businessowner-chartsummary.component.html | 23 ++++- .../businessowner-chartsummary.component.scss | 46 +++++++++- ...sinessowner-chartsummary.component.spec.ts | 19 ++++- .../businessowner-chartsummary.component.ts | 61 ++++++++++++- 21 files changed, 352 insertions(+), 147 deletions(-) diff --git a/.commit b/.commit index 7e4447fcf..17f4be054 100644 --- a/.commit +++ b/.commit @@ -1 +1 @@ -e7d60a81cd6c956aa923db9d327aff35a7086cf9 +ff4ac3907cfcb600aa23b23e1aeb401dcf3d68cd diff --git a/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.html b/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.html index 15e38cf91..bc9067beb 100644 --- a/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.html +++ b/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.html @@ -15,7 +15,7 @@

{{ '#LDS#Failed' | translate }}

{{ chart.Display }}
- @@ -32,7 +32,7 @@

{{ '#LDS#Passed' | translate }}

{{ chart.Display }}
- @@ -46,7 +46,7 @@

{{ '#LDS#Passed' | translate }}

{{ (data.failed ? '#LDS#Failed': '#LDS#Passed') | translate }}
-
diff --git a/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.scss b/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.scss index 5448cf563..345bc0c18 100644 --- a/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.scss +++ b/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.scss @@ -1,5 +1,4 @@ -@use '@angular/material' as mat; -@import '@elemental-ui/core/src/styles/_palette.scss'; +@import '@elemental-ui/core/src/styles/_eui_palette.scss'; :host{ flex: 1 1 auto; @@ -9,12 +8,12 @@ .imx-kpi-site { padding: 16px 40px; - background-color: mat.get-color-from-palette($asher-gray-palette, 100); height: auto; min-height: 100%; display: flex; flex-direction: column; flex: 1 1 auto; + overflow: auto; } li { @@ -22,7 +21,6 @@ li { margin: 0 10px 10px 0; .mat-card { - background-color: $white; width: 340px; height: 68px; display: grid; @@ -40,13 +38,13 @@ li { h2 { font-size: 20px; - color: $black-3; + color: $color-gray-30; margin-bottom: 8px; } p { font-size: 14px; - color: $black-9; + color: $color-gray-80; margin-top: 0; } @@ -94,8 +92,6 @@ h4 { font-size: 24px; line-height: 38px; font-weight: 900; - margin: 0 20px 0 0px; - color: $aspen-green; grid-column: 1; } @@ -106,6 +102,7 @@ h4 { > li > .mat-card { display: flex; height: 100%; + justify-content: space-between; } } @@ -124,9 +121,6 @@ h4 { margin: 0px 0 10px; } -.imx-fail { - color: $phoenix-red; -} .imx-title-text { text-align: center; @@ -143,18 +137,44 @@ h4 { display: flex; flex-direction: column; align-items: center; - margin-bottom: 10px; - margin-top: 10px; - background-color: mat.get-color-from-palette($asher-gray-palette, 100); - height: auto; - min-height: calc(100% - 20px); + justify-content: center; + height: 100%; h2 { font-weight: 600; - margin-top: 30px; + } +} + +:host { + .imx-kpi-site { + background-color: $color-gray-2; + } + + .imx-aob-kpi-empty { + background-color: $color-gray-2; + color: $color-gray-50; + } + + .imx-pass { + color: $color-green-60; + } + + .imx-fail { + color: $color-red-60; + } +} + + +::ng-deep .kpi-overview-modal { + .imx-title-text { + color: $color-gray-80; + } + + .imx-pass { + color: $color-green-60; } - >.eui-icon { - color: $black-b; + .imx-fail { + color: $color-red-60; } } diff --git a/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.ts b/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.ts index 7337bc65e..eae3e7c65 100644 --- a/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.ts +++ b/imxweb/projects/aob/src/lib/kpi/kpi-overview/kpi-overview.component.ts @@ -110,7 +110,7 @@ export class KpiOverviewComponent implements OnChanges { */ public showDetails(chart: ChartDto, isFail: boolean): void { if (!this.hasDetails(chart)) { return; } - this.matDialog.open(this.chartDialog, { data: { chart: this.getChartData(chart), failed: isFail }, width: '600px', autoFocus: false }); + this.matDialog.open(this.chartDialog, { data: { chart: this.getChartData(chart), failed: isFail }, width: '600px', autoFocus: false, panelClass: 'kpi-overview-modal' }); } /** @@ -189,6 +189,10 @@ export class KpiOverviewComponent implements OnChanges { text: this.errorThreshhold } ]; + lineChartOptions.size = { + height: 400, + width: 500 + } this.logger.debug(this, 'Options', lineChartOptions.options); return lineChartOptions.options; } diff --git a/imxweb/projects/att/src/lib/new-user/new-user.module.ts b/imxweb/projects/att/src/lib/new-user/new-user.module.ts index 3fafb0e94..044db5c3f 100644 --- a/imxweb/projects/att/src/lib/new-user/new-user.module.ts +++ b/imxweb/projects/att/src/lib/new-user/new-user.module.ts @@ -34,7 +34,17 @@ import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { Router, Routes } from '@angular/router'; import { EuiCoreModule } from '@elemental-ui/core'; import { TranslateModule } from '@ngx-translate/core'; -import { AuthConfigProvider, AuthenticationService, CaptchaModule, CdrModule, CustomAuthFlow, MastHeadModule, ParameterizedTextModule, UserMessageModule } from 'qbm'; +import { + AuthConfigProvider, + AuthenticationService, + CaptchaModule, + CdrModule, + CustomAuthFlow, + MastHeadModule, + ParameterizedTextModule, + UserMessageModule, + ClassloggerService, +} from 'qbm'; import { ApiService } from '../api.service'; import { ConfirmDialogComponent } from './confirm-dialog.component'; import { NewUserComponent } from './new-user.component'; @@ -44,8 +54,8 @@ import { UserActivationComponent } from './user-activation.component'; const routes: Routes = [ { path: 'useractivation', - component: UserActivationComponent - } + component: UserActivationComponent, + }, ]; @NgModule({ @@ -63,49 +73,40 @@ const routes: Routes = [ ParameterizedTextModule, UserMessageModule, TranslateModule, - EuiCoreModule - ], - declarations: [ - OpenSidesheetComponent, - NewUserComponent, - UserActivationComponent, - ConfirmDialogComponent + EuiCoreModule, ], + declarations: [OpenSidesheetComponent, NewUserComponent, UserActivationComponent, ConfirmDialogComponent], }) export class NewUserModule { - constructor(authService: AuthenticationService, router: Router, apiService: ApiService) { - + constructor(authService: AuthenticationService, router: Router, apiService: ApiService, private readonly logger: ClassloggerService) { const newUserAuthProvider: AuthConfigProvider = { display: '#LDS#Create new account', name: 'NewUser', authProps: [], - customAuthFlow: new NewUserFlow() + customAuthFlow: new NewUserFlow(), }; - apiService.client.register_featureconfig_get().then(c => { + apiService.client.register_featureconfig_get().then((c) => { if (c.SelfRegistrationEnabled) { authService.registerAuthConfigProvider(newUserAuthProvider); } }); - + this.logger.info(this, '▶️ NewUser initialized'); this.addRoutes(router); } private addRoutes(router: Router): void { const config = router.config; - routes.forEach(route => { + routes.forEach((route) => { config.unshift(route); }); router.resetConfig(config); } - } class NewUserFlow implements CustomAuthFlow { - public getEntryComponent() { return OpenSidesheetComponent; } } - diff --git a/imxweb/projects/att/src/lib/new-user/user-activation.component.html b/imxweb/projects/att/src/lib/new-user/user-activation.component.html index 117a33442..32cf910b1 100644 --- a/imxweb/projects/att/src/lib/new-user/user-activation.component.html +++ b/imxweb/projects/att/src/lib/new-user/user-activation.component.html @@ -9,21 +9,9 @@

{{ '#LDS#Heading Confirm Email Address' | translate }}< - - - - {{ldsAlreadyCompleted| translate}} - {{ldsConfirmationText| translate}} - {{ldsRegistrationDenied| translate}} - - - {{ldsCompletionFailed| translate}} - {{ldsResendEmail| translate}} - {{ldsLoadingProblems| translate}} - - + {{ldsText| translate}}
- @@ -32,4 +20,4 @@

{{ '#LDS#Heading Confirm Email Address' | translate }}<

- \ No newline at end of file + diff --git a/imxweb/projects/att/src/lib/new-user/user-activation.component.ts b/imxweb/projects/att/src/lib/new-user/user-activation.component.ts index 14b0abe5a..4508a4658 100644 --- a/imxweb/projects/att/src/lib/new-user/user-activation.component.ts +++ b/imxweb/projects/att/src/lib/new-user/user-activation.component.ts @@ -24,35 +24,28 @@ * */ -import { Component, OnDestroy, OnInit } from '@angular/core'; +import { Component, OnDestroy } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; import { EuiLoadingService } from '@elemental-ui/core'; import { TranslateService } from '@ngx-translate/core'; import { PersonActivationDto } from 'imx-api-att'; -import { AuthenticationService, SessionState, SnackBarService } from 'qbm'; +import { AuthenticationService, SessionState, SnackBarService, SplashService } from 'qbm'; import { Subscription } from 'rxjs'; import { ApiService } from '../api.service'; @Component({ templateUrl: './user-activation.component.html', - styleUrls: ['./user-activation.component.scss'] + styleUrls: ['./user-activation.component.scss'], }) export class UserActivationComponent implements OnDestroy { - - public missingCase = true; public busy = true; public data: PersonActivationDto; - public ldsConfirmationText = '#LDS#Confirm your email address and activate your account or send the confirmation email again (if the passcode has expired) by clicking one of the following buttons.'; - public ldsAlreadyCompleted = '#LDS#You already have completed the registration process. Please log in with your credentials. If you have forgotten your credentials, ask your manager for a passcode.'; - public ldsRegistrationDenied = '#LDS#Your registration was denied.'; - public ldsCompletionFailed = '#LDS#The registration process could not be completed. The submitted registration process could not be found. You may already have completed the registration process. Please log in with your credentials. If you have forgotten your credentials, ask your manager for a passcode.'; - public ldsResendEmail = '#LDS#The registration process could not be completed. Please click "Send confirmation email again" and follow the instructions in the new confirmation email.'; - public ldsSendAgain = '#LDS#Send confirmation email again'; - public ldsLoadingProblems = '#LDS#This attestation case has already been approved or denied.'; - public hasProblems = false; + public ldsText: string; + public hasProblems = false; private readonly subscription: Subscription; private passcode: string; + private uidCase: string; constructor( private readonly attApiService: ApiService, @@ -61,32 +54,35 @@ export class UserActivationComponent implements OnDestroy { private readonly authService: AuthenticationService, private readonly translateService: TranslateService, private readonly busyService: EuiLoadingService, + private readonly splashService: SplashService, private readonly router: Router ) { - this.subscription = route.queryParamMap.subscribe(async p => { + this.subscription = route.queryParamMap.subscribe(async (params) => { this.busy = true; try { - const uidCase = p.get('aeweb_UID_AttestationCase'); - this.missingCase = !uidCase; - if (this.missingCase) { + this.uidCase = params.get('aeweb_UID_AttestationCase'); + this.passcode = params.get('aeweb_PassCode'); + if (!this.hasFormat) { return; } - this.passcode = p.get('aeweb_PassCode'); - this.data = await this.attApiService.client.passwordreset_activation_init_post(uidCase); + this.data = await this.attApiService.client.passwordreset_activation_init_post(this.uidCase); - const preferredCulture = this.data.Culture; - if (preferredCulture) { - // apply preferred culture - this.useCulture(preferredCulture); - } + // Apply culture + this.data?.Culture ? this.useCulture(this.data.Culture) : null; } catch { this.hasProblems = true; } finally { + this.determineText(); + this.splashService.close(); this.busy = false; } }); } + public get hasFormat(): boolean { + return !!this.uidCase && !!this.passcode; + } + public ngOnDestroy(): void { this.subscription?.unsubscribe(); } @@ -96,8 +92,7 @@ export class UserActivationComponent implements OnDestroy { try { await this.attApiService.client.passwordreset_activation_resendemail_post(); this.snackbar.open({ key: '#LDS#An email has been sent to your specified email address.' }, '#LDS#Close'); - } - catch { + } catch { this.hasProblems = true; } finally { this.busyService.hide(overlayRef); @@ -109,7 +104,7 @@ export class UserActivationComponent implements OnDestroy { try { await this.authService.processLogin(async () => { const s = await this.attApiService.client.passwordreset_activation_confirm_post({ - PassCode: this.passcode + PassCode: this.passcode, }); return new SessionState(s); }); @@ -122,6 +117,37 @@ export class UserActivationComponent implements OnDestroy { } } + private determineText(): void { + if (this.data) { + // We got a response from the server and can show the state of the case + switch (this.data.ApprovalState) { + case 0: + this.ldsText = + '#LDS#You already have completed the registration process. Please log in with your credentials. If you have forgotten your credentials, ask your manager for a passcode.'; + break; + case 1: + this.ldsText = + '#LDS#Confirm your email address and activate your account or send the confirmation email again (if the passcode has expired) by clicking one of the following buttons.'; + break; + case 3: + this.ldsText = '#LDS#Your registration was denied.'; + break; + } + return; + } + + // Here something went wrong and either the params were missing or the server failed + if (this.hasFormat && this.hasProblems) { + this.ldsText = '#LDS#This attestation case has already been approved or denied.'; + } else if (this.hasFormat && !this.hasProblems) { + this.ldsText = + '#LDS#The registration process could not be completed. Please click "Send confirmation email again" and follow the instructions in the new confirmation email.'; + } else if (!this.hasFormat && !this.hasProblems) { + this.ldsText = + '#LDS#The registration process could not be completed. The submitted registration process could not be found. You may already have completed the registration process. Please log in with your credentials. If you have forgotten your credentials, ask your manager for a passcode.'; + } + } + private async useCulture(culture: string): Promise { this.translateService.setDefaultLang(culture); await this.translateService.use(culture).toPromise(); diff --git a/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts b/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts index a3e183766..273b0a09e 100644 --- a/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts +++ b/imxweb/projects/pol/src/lib/policy-violations/policy-violations.service.ts @@ -91,7 +91,7 @@ export class PolicyViolationsService { setTimeout(() => busyIndicator = this.busyService.show()); try { - justification = await this.justificationService.createCdr(JustificationType.approve); + justification = await this.justificationService.createCdr(JustificationType.approvePolicyViolation); } finally { setTimeout(() => this.busyService.hide(busyIndicator)); } @@ -128,7 +128,7 @@ export class PolicyViolationsService { setTimeout(() => busyIndicator = this.busyService.show()); try { - justification = await this.justificationService.createCdr(JustificationType.deny); + justification = await this.justificationService.createCdr(JustificationType.denyPolicyViolation); } finally { setTimeout(() => this.busyService.hide(busyIndicator)); } diff --git a/imxweb/projects/qbm/src/lib/bulk-property-editor/bulk-item/bulk-item.component.scss b/imxweb/projects/qbm/src/lib/bulk-property-editor/bulk-item/bulk-item.component.scss index 191950e7f..ff76a6f63 100644 --- a/imxweb/projects/qbm/src/lib/bulk-property-editor/bulk-item/bulk-item.component.scss +++ b/imxweb/projects/qbm/src/lib/bulk-property-editor/bulk-item/bulk-item.component.scss @@ -17,6 +17,12 @@ button { justify-content: end; } +.mat-expansion-panel-header { + min-height: 24px; + height: auto; + padding: 12px 24px; +} + .mat-expansion-panel-header-description, .mat-expansion-panel-header-title { align-items: center; } diff --git a/imxweb/projects/qer-app-portal/src/app/app.service.ts b/imxweb/projects/qer-app-portal/src/app/app.service.ts index 1632396ca..c0f98f849 100644 --- a/imxweb/projects/qer-app-portal/src/app/app.service.ts +++ b/imxweb/projects/qer-app-portal/src/app/app.service.ts @@ -93,7 +93,7 @@ export class AppService { private async setTitle(): Promise { const imxConfig = await this.config.getImxConfig(); const name = imxConfig.ProductName || Globals.QIM_ProductNameFull; - this.config.Config.Title = await this.translateService.get('#LDS#Heading Web Portal').toPromise(); + this.config.Config.Title = await this.translateService.get('#LDS#Web Portal').toPromise(); const title = `${name} ${this.config.Config.Title}`; this.title.setTitle(title); diff --git a/imxweb/projects/qer-app-pwdportal/src/app/app.component.ts b/imxweb/projects/qer-app-pwdportal/src/app/app.component.ts index 46e8ab935..f8da17db8 100644 --- a/imxweb/projects/qer-app-pwdportal/src/app/app.component.ts +++ b/imxweb/projects/qer-app-pwdportal/src/app/app.component.ts @@ -46,7 +46,7 @@ export class AppComponent implements OnInit, OnDestroy { private readonly authentication: AuthenticationService, private readonly router: Router, private readonly splash: SplashService, - private readonly config: AppConfigService, + private readonly config: AppConfigService ) { this.subscriptions.push( this.authentication.onSessionResponse.subscribe(async (sessionState: ISessionState) => { @@ -54,7 +54,7 @@ export class AppComponent implements OnInit, OnDestroy { // Needs to close here when there is an error on sessionState this.splash.close(); } else { - if (sessionState.IsLoggedOut) { + if (sessionState.IsLoggedOut && !this.isOnUserActivation()) { this.showPageContent = false; } } @@ -79,6 +79,10 @@ export class AppComponent implements OnInit, OnDestroy { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } + private isOnUserActivation(): boolean { + return this.router.url.startsWith('/useractivation'); + } + private setupRouter(): void { this.router.events.subscribe((event: RouterEvent) => { if (event instanceof NavigationStart) { diff --git a/imxweb/projects/qer/src/lib/identities/create-new-identity/create-new-identity.component.ts b/imxweb/projects/qer/src/lib/identities/create-new-identity/create-new-identity.component.ts index b71fd4d93..4a4687c3b 100644 --- a/imxweb/projects/qer/src/lib/identities/create-new-identity/create-new-identity.component.ts +++ b/imxweb/projects/qer/src/lib/identities/create-new-identity/create-new-identity.component.ts @@ -99,7 +99,7 @@ export class CreateNewIdentityComponent implements OnDestroy { try { await this.data.selectedIdentity.GetEntity().Commit(false); this.snackbar.open({ key: '#LDS#The identity has been successfully created.' }); - this.sidesheetRef.close(); + this.sidesheetRef.close(true); } finally { this.busyService.hide(overlayRef); } @@ -192,14 +192,14 @@ export class CreateNewIdentityComponent implements OnDestroy { private setup(): void { - this.subscriptions.push(this.sidesheetRef.closeClicked().subscribe(async (result) => { + this.subscriptions.push(this.sidesheetRef.closeClicked().subscribe(async () => { if (this.identityForm.dirty) { const close = await this.confirm.confirmLeaveWithUnsavedChanges(); if (close) { - this.sidesheetRef.close(); + this.sidesheetRef.close(false); } } else { - this.sidesheetRef.close(result); + this.sidesheetRef.close(false); } })); diff --git a/imxweb/projects/qer/src/lib/justification/justification-type.enum.ts b/imxweb/projects/qer/src/lib/justification/justification-type.enum.ts index dd6cb3ece..05b8c2f71 100644 --- a/imxweb/projects/qer/src/lib/justification/justification-type.enum.ts +++ b/imxweb/projects/qer/src/lib/justification/justification-type.enum.ts @@ -32,5 +32,7 @@ export enum JustificationType { deny = 16, denyAttestation = 32, approveRuleViolation = 64, - denyRuleViolation = 128 + denyRuleViolation = 128, + approvePolicyViolation = 256, + denyPolicyViolation = 512 } diff --git a/imxweb/projects/qer/src/lib/product-selection/optional-items-sidesheet/optional-items-sidesheet.component.ts b/imxweb/projects/qer/src/lib/product-selection/optional-items-sidesheet/optional-items-sidesheet.component.ts index 0dbde6309..94f096e84 100644 --- a/imxweb/projects/qer/src/lib/product-selection/optional-items-sidesheet/optional-items-sidesheet.component.ts +++ b/imxweb/projects/qer/src/lib/product-selection/optional-items-sidesheet/optional-items-sidesheet.component.ts @@ -176,15 +176,16 @@ export class OptionalItemsSidesheetComponent implements OnInit, OnDestroy { children.forEach((child) => { child.isIndeterminate = !parent.isChecked || parent.isIndeterminate; child.parentChecked = parent.isChecked; - // Change selection count for checked and optional items based on indetermacy - if (!child.isMandatory && child.isChecked) { - child.isIndeterminate ? this.selected -= 1 : this.selected += 1; + // Change selection count for checked and optional items based on indetermacy, we can stop walking if an optional is not checked + if (child.isChecked) { + if (!child.isMandatory) { + child.isIndeterminate ? (this.selected -= 1) : (this.selected += 1); + } + this.walkChildren(child, [...child.Mandatory, ...child.Optional]); } - this.walkChildren(child, [...child.Mandatory, ...child.Optional]); }); } - public onChange(value: MatCheckboxChange, leaf: ServiceItemHierarchyExtended): void { // Change state of this leaf value.checked ? (this.selected += 1) : (this.selected -= 1); diff --git a/imxweb/projects/qer/src/lib/product-selection/product-selection.component.ts b/imxweb/projects/qer/src/lib/product-selection/product-selection.component.ts index 6bc87f66d..7c9ec9b51 100644 --- a/imxweb/projects/qer/src/lib/product-selection/product-selection.component.ts +++ b/imxweb/projects/qer/src/lib/product-selection/product-selection.component.ts @@ -410,14 +410,14 @@ export class ProductSelectionComponent implements OnInit, OnDestroy { const outgoingOrder: ServiceItemOrder = { serviceItems: [serviceItem], }; - this.projectConfig.ITShopConfig.VI_ITShop_AddOptionalProductsOnInsert + this.projectConfig?.ITShopConfig?.VI_ITShop_AddOptionalProductsOnInsert ? await this.openOptionalSideSheet(outgoingOrder) : await this.orderSelected(outgoingOrder, this.selectedTemplates, this.selectedRoles); } public async openOptionalSideSheet(outgoingOrder: ServiceItemOrder): Promise { const serviceItemTree = await this.optionalItemsService.checkForOptionalTree(outgoingOrder.serviceItems, this.recipients); - if (serviceItemTree?.totalOptional > 0) { + if (serviceItemTree?.totalOptional && serviceItemTree?.totalOptional > 0) { const selectedOptionalOrder: ServiceItemOrder = await this.sideSheetService .open(OptionalItemsSidesheetComponent, { title: this.translate.instant('#LDS#Heading Optional Products'), @@ -434,11 +434,14 @@ export class ProductSelectionComponent implements OnInit, OnDestroy { }) .afterClosed() .toPromise(); - // If there was an order, then continue, otherwise do nothing + // OptionalItemsSidesheet: If the user click on the AddToCart button add the selected items to the cart, otherwise do nothing if (selectedOptionalOrder) { outgoingOrder.requestables = selectedOptionalOrder.requestables; await this.orderSelected(outgoingOrder, this.selectedTemplates, this.selectedRoles); } + } else { + // if there are no optional items go ahead with the order + await this.orderSelected(outgoingOrder, this.selectedTemplates, this.selectedRoles); } } diff --git a/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.html b/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.html index 5c6ca45c9..286ca4288 100644 --- a/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.html +++ b/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.html @@ -7,8 +7,8 @@
- -
\ No newline at end of file + diff --git a/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.ts b/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.ts index 133bb9fec..9c705882b 100644 --- a/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.ts +++ b/imxweb/projects/qer/src/lib/product-selection/service-item-edit/service-item-edit.component.ts @@ -40,54 +40,71 @@ export class ServiceItemEditComponent { '#LDS#Specify additional information for the following products. For all other products, you do not have to specify any additional information.'; constructor( - @Inject(EUI_SIDESHEET_DATA) public readonly bulkItems: BulkItem[], + @Inject(EUI_SIDESHEET_DATA) public bulkItems: BulkItem[], private readonly confirmationService: ConfirmationService, private readonly ldsReplace: LdsReplacePipe, private readonly translate: TranslateService, private readonly sideSheetRef: EuiSidesheetRef ) { - this.sideSheetRef.closeClicked().subscribe((__) => this.cancel()); + this.sideSheetRef.closeClicked().subscribe(() => this.confirmCancel()); } - public async close(): Promise { - const bulkItemsWithNoDecision = this.bulkItems.filter((bulkItem) => bulkItem.status === BulkItemStatus.unknown); - if (bulkItemsWithNoDecision && bulkItemsWithNoDecision.length > 0) { - const skipProductMessage = - '#LDS#You have not specified additional information for one product. This is equivalent to skipping. Are you sure you do not want to add the product to your shopping cart?'; - const skipProductsMessage = this.ldsReplace.transform( - await this.translate - .get( - '#LDS#You have not specified additional information for {0} products. This is equivalent to skipping. Are you sure you do not want to add the products to your shopping cart?' - ) - .toPromise(), - bulkItemsWithNoDecision.length - ); - - if ( - await this.confirmationService.confirm({ - Title: bulkItemsWithNoDecision.length > 1 ? '#LDS#Heading Skip Products' : '#LDS#Heading Skip Product', - Message: bulkItemsWithNoDecision.length > 1 ? skipProductsMessage : skipProductMessage, - }) - ) { - return this.sideSheetRef.close(true); - } + /** + * The user wants to abort the process, if they confirm then we set all statuses to skipped and proceed, otherwise stay on sidesheet + */ + public async confirmCancel(): Promise { + if ( + await this.confirmationService.confirm({ + Title: '#LDS#Heading Cancel Request Process', + Message: '#LDS#Are you sure you want to cancel the request process and not add the products to your shopping cart?', + }) + ) { + this.bulkItems = this.bulkItems.map((item) => { + return { ...item, status: BulkItemStatus.skipped }; + }); + return this.sideSheetRef.close(false); } - return this.sideSheetRef.close(true); } - private async cancel(): Promise { - const cancel = await this.confirmationService.confirm({ - Title: '#LDS#Heading Cancel Request Process', - Message: '#LDS#Are you sure you want to cancel the request process and not add the products to your shopping cart?', - }); + /** + * The user has chosen to submit, if all statuses are clear, then proceed. Otherwise confirm if there are still unknown statuses that we will skip, else we stay on the sidesheet + */ + public async submit(): Promise { + const bulkItemsWithNoDecision = this.bulkItems.filter((bulkItem) => bulkItem.status === BulkItemStatus.unknown); + // All items have a decision, we can move on + if (bulkItemsWithNoDecision?.length === 0) { + return this.sideSheetRef.close(true); + } + // We have some items that need more info + const skipProductMessage = + '#LDS#You have not specified additional information for one product. This is equivalent to skipping. Are you sure you do not want to add the product to your shopping cart?'; + const skipProductsMessage = this.ldsReplace.transform( + await this.translate + .get( + '#LDS#You have not specified additional information for {0} products. This is equivalent to skipping. Are you sure you do not want to add the products to your shopping cart?' + ) + .toPromise(), + bulkItemsWithNoDecision.length + ); - if (cancel) { - this.bulkItems.forEach((item) => (item.status = BulkItemStatus.skipped)); - return this.sideSheetRef.close(false); + // Confirm we will skip the unknown statuses, if they decline we won't close the sidesheet + if ( + await this.confirmationService.confirm({ + Title: bulkItemsWithNoDecision.length > 1 ? '#LDS#Heading Skip Products' : '#LDS#Heading Skip Product', + Message: bulkItemsWithNoDecision.length > 1 ? skipProductsMessage : skipProductMessage, + }) + ) { + this.bulkItems = this.bulkItems.map((item) => + item.status === BulkItemStatus.unknown ? { ...item, status: BulkItemStatus.skipped } : item + ); + return this.sideSheetRef.close(true); } } + /** + * @ignore Used to see if there is at least one status set + */ public hasBulkItemsWithDecision(): boolean { - return this.bulkItems.filter((bulkItem) => bulkItem.status !== BulkItemStatus.unknown).length > 0; + return this.bulkItems.some((bulkItem) => bulkItem.status !== BulkItemStatus.unknown); } } diff --git a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.html b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.html index 8ba7a5d0f..495db821a 100644 --- a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.html +++ b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.html @@ -17,7 +17,7 @@ - @@ -31,7 +31,6 @@

{{'#LDS#View and manage employees who report directly to you.' | translate}}

-
+ + + + + +
{{'#LDS#Heading My Direct Reports' | translate}}
+
+ +
{{'#LDS#View and manage employees who report directly to you.' | translate}}
+
+ + + +
+
\ No newline at end of file diff --git a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.scss b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.scss index 11eecc3ec..e07509d0d 100644 --- a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.scss +++ b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.scss @@ -1,9 +1,10 @@ @use '@angular/material' as mat; @import '@elemental-ui/core/src/styles/_palette.scss'; +@import "../../../../../qbm/src/lib/tile/tile-variables.scss"; :host { display: flex; - flex-wrap: wrap ; + flex-wrap: wrap; } .imx-valuelist { @@ -106,3 +107,46 @@ .imx-colright { text-align: right; } + +:host > .imx-create-identity-tile { + ::ng-deep .mat-card { + height: 140px; + display: flex; + width: $tile-width; + min-width: $tile-width; + flex-direction: column; + cursor: auto; + padding: 25px; + padding-bottom: 10px; + } + + .mat-card-title { + font-size: 1.15em; + font-weight: 600; + display: flex; + cursor: auto; + } + + .mat-card-subtitle { + flex: 1; + font-size: small; + text-align: left; + overflow: auto; + overflow-x: hidden; + color: $black-9; + margin-bottom: 0; + } + + .mat-card-actions { + margin-left: 0; + margin-right: 0; + display: flex; + } + + .mat-card-actions:last-child { + margin-bottom: 0; + margin-left: -16px; + margin-top: -10px; + } +} + diff --git a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.spec.ts b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.spec.ts index 070a994e2..e66d1e24d 100644 --- a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.spec.ts +++ b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.spec.ts @@ -30,7 +30,7 @@ import { Router } from '@angular/router'; import { EuiLoadingService, EuiSidesheetService } from '@elemental-ui/core'; import { configureTestSuite } from 'ng-bullet'; -import { OwnershipInformation } from 'imx-api-qer'; +import { OwnershipInformation, PortalPersonReportsInteractive } from 'imx-api-qer'; import { clearStylesFromDOM } from 'qbm'; import { BusinessOwnerChartSummaryComponent } from './businessowner-chartsummary.component'; import { UserModelService } from '../../user/user-model.service'; @@ -38,6 +38,8 @@ import { QerApiService } from '../../qer-api-client.service'; import { of } from 'rxjs'; import { ProjectConfigurationService } from '../../project-configuration/project-configuration.service'; import { QerPermissionsService } from '../../admin/qer-permissions.service'; +import { IdentitiesService } from '../../identities/identities.service'; +import { IdentitiesCommonTestData } from '../../identities/test/common-test-mocks.spec'; @Component({ selector: 'imx-tile', @@ -95,6 +97,13 @@ describe('BusinessOwnerChartSummaryComponent', () => { } }; + const personReportInteractive = { + GetEntity: () => IdentitiesCommonTestData.mockEntity, + DefaultEmailAddress: { Column: IdentitiesCommonTestData.mockEntityColumn }, + UID_PersonHead: { Column: IdentitiesCommonTestData.mockEntityColumn }, + IsInActive: { Column: IdentitiesCommonTestData.mockEntityColumn }, + } as PortalPersonReportsInteractive; + const sideSheetTestHelper = new class { afterClosedResult = false; readonly servicestub = { @@ -127,6 +136,14 @@ describe('BusinessOwnerChartSummaryComponent', () => { provide: ProjectConfigurationService, useValue: mockConfigService, }, + { + provide: IdentitiesService, + useValue: { + createEmptyEntity: jasmine.createSpy('createEmptyEntity').and.returnValue(Promise.resolve( + personReportInteractive + )), + } + }, { provide: QerApiService, useValue: { diff --git a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts index 8173fafc5..9b79f6d63 100644 --- a/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts +++ b/imxweb/projects/qer/src/lib/wport/businessowner-chartsummary/businessowner-chartsummary.component.ts @@ -34,6 +34,9 @@ import { QerApiService } from '../../qer-api-client.service'; import { ProjectConfigurationService } from '../../project-configuration/project-configuration.service'; import { IdentitySidesheetComponent } from '../../identities/identity-sidesheet/identity-sidesheet.component'; import { QerPermissionsService } from '../../admin/qer-permissions.service'; +import { CreateNewIdentityComponent } from '../../identities/create-new-identity/create-new-identity.component'; +import { TranslateService } from '@ngx-translate/core'; +import { IdentitiesService } from '../../identities/identities.service'; @Component({ templateUrl: './businessowner-chartsummary.component.html', @@ -44,6 +47,7 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { public reports: PortalPersonReports[]; public ownerships: OwnershipInformation[]; public viewReady: boolean; + public allReportsCount: number; private projectConfig: ProjectConfig; @@ -54,8 +58,10 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { private readonly sideSheet: EuiSidesheetService, private readonly errorHandler: ErrorHandler, private readonly configService: ProjectConfigurationService, + private readonly identitiesService: IdentitiesService, + private readonly translate: TranslateService, private readonly userModelService: UserModelService, - private readonly qerPermissions: QerPermissionsService + public readonly qerPermissions: QerPermissionsService ) { } public async ngOnInit(): Promise { @@ -67,9 +73,7 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { this.projectConfig = await this.configService.getConfig(); - await this.loadDirectReports(); - - this.viewReady = true; + await this.getData(); } finally { setTimeout(() => this.busyService.hide(overlayRef)); } @@ -116,10 +120,45 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { await this.loadDirectReports(); } + public async openCreateNewIdentitySidesheet(): Promise { + const identityCreated = await this.sideSheet.open(CreateNewIdentityComponent, { + title: await this.translate.get('#LDS#Heading Create Identity').toPromise(), + headerColour: 'iris-blue', + bodyColour: 'asher-gray', + padding: '0px', + width: 'max(650px, 65%)', + disableClose: true, + testId: 'create-new-identity-sidesheet', + icon: 'contactinfo', + data: { + selectedIdentity: await this.identitiesService.createEmptyEntity(), + projectConfig: this.projectConfig + } + }).afterClosed().toPromise(); + + if (identityCreated) { + let overlayRef: OverlayRef; + setTimeout(() => overlayRef = this.busyService.show()); + try { + await this.getData(); + } finally { + setTimeout(() => this.busyService.hide(overlayRef)); + } + } } + public openOwnership(ownerShip: OwnershipInformation): void { this.router.navigate(['resp', ownerShip.TableName]); } + private async getData(): Promise { + this.viewReady = false; + await this.loadIndirectOrDirectReports(); + if (this.allReportsCount > 0 ) { + await this.loadDirectReports(); + } + this.viewReady = true; + } + private async loadDirectReports(): Promise { let overlayRef: OverlayRef; setTimeout(() => overlayRef = this.busyService.show()); @@ -134,4 +173,18 @@ export class BusinessOwnerChartSummaryComponent implements OnInit { setTimeout(() => this.busyService.hide(overlayRef)); } } + + private async loadIndirectOrDirectReports(): Promise { + let overlayRef: OverlayRef; + setTimeout(() => overlayRef = this.busyService.show()); + try { + if (await this.qerPermissions.isPersonManager()) { + this.allReportsCount = (await this.qerClient.typedClient.PortalPersonReports.Get({ + PageSize: -1 + })).totalCount; + } + } finally { + setTimeout(() => this.busyService.hide(overlayRef)); + } + } } From 6baccd8293c6800a41f0a0d5441773b2a81e70de Mon Sep 17 00:00:00 2001 From: Hanno Bunjes Date: Fri, 1 Sep 2023 13:24:03 +0200 Subject: [PATCH 2/2] updated readme --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 800a42ee3..ccaf11c41 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,20 @@ ## Change log +### September 1, 2023 + +The `v91` branch has been updated with fixes for the following issues. + +- 426598 The counter of selected optional products was sometimes showing an incorrect value +- 427961/37144 Adding items to the shopping cart did not work +- 426872 Policy violation was not using the correct set of standard justifications +- 426767 Fixed UI layout in request approval sidesheet +- 415340 Password Reset Portal was sometimes hanging at the loading screen +- 424223 Refactoring of sidesheet closing (https://github.com/OneIdentity/IdentityManager.Imx/pull/65) +- 419508 Application Governance KPIs were not correctly adapting to the screens ize +- 423861/36856/423948 Fix UI when a manager has only indirect, but no direct reports +- 421566 Fix LDS keys for product names + ### June 23, 2023 This update addresses the following security issues.