Browse Source

Merge remote-tracking branch 'upstream/dev' into spellcheck

pull/14374/head
Sean Killeen 3 years ago
parent
commit
b645162ace
  1. 18
      docs/en/UI/Angular/Account-Module.md
  2. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs
  3. 1
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs
  4. 8
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs
  5. 2
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs
  6. 9
      framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RemoveUnnecessaryPortsStep.cs
  7. 2
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json
  8. 2
      modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json
  9. 2
      modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Dockerfile
  10. 5
      npm/ng-packs/packages/account/src/lib/account.module.ts
  11. 47
      npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts
  12. 1
      npm/ng-packs/packages/account/src/lib/models/config-options.ts
  13. 1
      npm/ng-packs/packages/account/src/lib/tokens/index.ts
  14. 5
      npm/ng-packs/packages/account/src/lib/tokens/re-login-confirmation.token.ts
  15. 1
      npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html
  16. 19
      npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts
  17. 62
      templates/app-nolayers/angular/src/app/home/home.component.spec.ts
  18. 3
      templates/app-nolayers/angular/src/test.ts
  19. 100
      templates/app/angular/src/app/home/home.component.spec.ts
  20. 4
      templates/app/angular/src/test.ts
  21. 20
      templates/module/angular/projects/my-project-name/src/lib/my-project-name.component.spec.ts
  22. 15
      templates/module/angular/projects/my-project-name/src/lib/my-project-name.service.spec.ts

18
docs/en/UI/Angular/Account-Module.md

@ -95,6 +95,24 @@ export class AppRoutingModule {}
Before v4.3, the "My account" link in the current user dropdown on the top bar redirected the user to MVC's profile management page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account module instead.
### Personal Info Page Confirm Message
When the user changes their own data on the personal settings tab in My Account, The data can not update the CurrentUser key of Application-Configuration. The information of the user is stored in claims. The only way to apply this information to the CurrentUser of Application-Configuration is user should log out and log in. When the Refresh-Token feature is implemented, it will be fixed. So We've added a confirmation alert.
If you want to disable these warning, You should set `isPersonalSettingsChangedConfirmationActive` false
```js
// app-routing.module.ts
const routes: Routes = [
//...
{
path: 'account',
loadChildren: () => import('@volo/abp.ng.account/public').then(m => m.AccountPublicModule.forLazy({ isPersonalSettingsChangedConfirmationActive:false })),
},
//...
export class AppRoutingModule {}
```
### Security Logs Page [COMMERCIAL]
Before v4.3, the "Security Logs" link in the current user dropdown on the top bar redirected the user to MVC's security logs page. As of v4.3, if you added the account module to your project, the same link will land on a page in the Angular UI account public module instead.

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Bundling/BundlingService.cs

@ -108,15 +108,15 @@ public class BundlingService : IBundlingService, ITransientDependency
Parameters = parameters
};
scriptContext.BundleDefinitions.AddIfNotContains(
x => x.Source == "_framework/blazor.webassembly.js",
() => new BundleDefinition { Source = "_framework/blazor.webassembly.js" });
foreach (var bundleDefinition in bundleDefinitions)
{
var contributor = CreateContributorInstance(bundleDefinition.BundleContributorType);
contributor.AddScripts(scriptContext);
}
scriptContext.BundleDefinitions.AddIfNotContains(
x => x.Source == "_framework/blazor.webassembly.js",
() => new BundleDefinition { Source = "_framework/blazor.webassembly.js" });
return scriptContext;
}

1
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Licensing/DeveloperApiKeyResult.cs

@ -13,6 +13,7 @@ public class DeveloperApiKeyResult
public string ErrorMessage { get; set; }
public LicenseErrorType? ErrorType { get; set; }
public LicenseType LicenseType { get; set; }
public bool IsTrialLicense { get; set; }
public enum LicenseErrorType
{

8
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/DatabaseManagementSystemChangeStep.cs

@ -98,7 +98,13 @@ public class DatabaseManagementSystemChangeStep : ProjectBuildPipelineStep
var oldUseMethod = "UseSqlServer";
var efCoreModuleClass = context.Files.First(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
var efCoreModuleClass = context.Files.FirstOrDefault(f => f.Name.EndsWith("EntityFrameworkCoreModule.cs", StringComparison.OrdinalIgnoreCase));
if(efCoreModuleClass == null)
{
return;
}
efCoreModuleClass.ReplaceText(oldUseMethod, newUseMethodForEfModule);
var dbContextFactoryFile = context.Files.FirstOrDefault(f => f.Name.EndsWith($"{(_hasDbMigrations ? "Migrations" : string.Empty)}DbContextFactoryBase.cs", StringComparison.OrdinalIgnoreCase))

2
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Files/FileEntryExtensions.cs

@ -120,7 +120,7 @@ public static class FileEntryExtensions
}
}
if ((i + 1 < lines.Length) && (lines[i + 1].Contains("<TEMPLATE-REMOVE")))
if (i < lines.Length - 1 && lines[i+1].Contains("<TEMPLATE-REMOVE"))
{
continue;
}

9
framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Templates/RemoveUnnecessaryPortsStep.cs

@ -58,8 +58,13 @@ public class RemoveUnnecessaryPortsStep : ProjectBuildPipelineStep
f.Name.Contains("MyCompanyName.MyProjectName.DbMigrator") && f.Name.EndsWith("appsettings.json"));
var appSettingsJsonObject = JObject.Parse(dbMigratorAppSettings.Content);
var authServerJsonObject = (JObject)appSettingsJsonObject["IdentityServer"] ?? (JObject)appSettingsJsonObject["OpenIddict"];
var clientsJsonObject = (JObject)authServerJsonObject["Clients"] ?? (JObject)authServerJsonObject["Applications"];
var authServerJsonObject = (JObject)appSettingsJsonObject?["IdentityServer"] ?? (JObject)appSettingsJsonObject["OpenIddict"];
var clientsJsonObject = (JObject)authServerJsonObject?["Clients"] ?? (JObject)authServerJsonObject?["Applications"];
if (clientsJsonObject == null)
{
return;
}
if (context.BuildArgs.UiFramework != UiFramework.Blazor)
{

2
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/en.json

@ -34,6 +34,8 @@
"DisplayName:PhoneNumber": "Phone number",
"PersonalSettings": "Personal settings",
"PersonalSettingsSaved": "Personal settings saved",
"PersonalSettingsChangedConfirmationModalTitle": "Personal info changed",
"PersonalSettingsChangedConfirmationModalDescription": "If you want to apply these changes, you have to login. Do you want to log out?",
"PasswordChanged": "Password changed",
"NewPasswordConfirmFailed": "Please confirm the new password.",
"NewPasswordSameAsOld": "New password must be different from the old one.",

2
modules/account/src/Volo.Abp.Account.Application.Contracts/Volo/Abp/Account/Localization/Resources/tr.json

@ -34,6 +34,8 @@
"DisplayName:PhoneNumber": "Telefon numarası",
"PersonalSettings": "Kişisel ayarlar",
"PersonalSettingsSaved": "Kişisel ayarlar kaydedildi",
"PersonalSettingsChangedConfirmationModalTitle": "Personal info changed",
"PersonalSettingsChangedConfirmationModalDescription": "If you want to apply these changes, you have to login. Do you want to log out?",
"PasswordChanged": "Şifre değiştirildi",
"NewPasswordConfirmFailed": "Lütfen yeni şifreyi onaylayın.",
"NewPasswordSameAsOld": "Yeni şifre eski şifre ile aynı olamaz.",

2
modules/basic-theme/test/Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.Demo/Dockerfile

@ -1,4 +1,4 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0.0-bullseye-slim AS base
FROM mcr.microsoft.com/dotnet/aspnet:7.0-bullseye-slim AS base
WORKDIR /app
EXPOSE 80
COPY bin/Release/publish .

5
npm/ng-packs/packages/account/src/lib/account.module.ts

@ -15,6 +15,7 @@ import { accountConfigOptionsFactory } from './utils/factory-utils';
import { AuthenticationFlowGuard } from './guards/authentication-flow.guard';
import { ForgotPasswordComponent } from './components/forgot-password/forgot-password.component';
import { ResetPasswordComponent } from './components/reset-password/reset-password.component';
import { RE_LOGIN_CONFIRMATION_TOKEN } from './tokens';
import { UiExtensionsModule } from '@abp/ng.theme.shared/extensions';
import { ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS } from './tokens/extensions.token';
import { AccountExtensionsGuard } from './guards/extensions.guard';
@ -55,6 +56,10 @@ export class AccountModule {
useFactory: accountConfigOptionsFactory,
deps: [ACCOUNT_CONFIG_OPTIONS],
},
{
provide: RE_LOGIN_CONFIRMATION_TOKEN,
useValue: options.isPersonalSettingsChangedConfirmationActive ?? true,
},
{
provide: ACCOUNT_EDIT_FORM_PROP_CONTRIBUTORS,
useValue: options.editFormPropContributors,

47
npm/ng-packs/packages/account/src/lib/components/personal-settings/personal-settings.component.ts

@ -1,10 +1,12 @@
import { ProfileDto, ProfileService } from '@abp/ng.account.core/proxy';
import { ToasterService } from '@abp/ng.theme.shared';
import { Component, Injector, OnInit, ViewEncapsulation } from '@angular/core';
import { Confirmation, ConfirmationService, ToasterService } from '@abp/ng.theme.shared';
import { Component, Inject, Injector, OnInit } from '@angular/core';
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
import { finalize } from 'rxjs/operators';
import { finalize, filter } from 'rxjs/operators';
import { Account } from '../../models/account';
import { ManageProfileStateService } from '../../services/manage-profile.state.service';
import { AuthService } from '@abp/ng.core';
import { RE_LOGIN_CONFIRMATION_TOKEN } from '../../tokens';
import {
EXTENSIONS_IDENTIFIER,
FormPropData,
@ -34,12 +36,17 @@ export class PersonalSettingsComponent
form: UntypedFormGroup;
inProgress: boolean;
private profile: ProfileDto;
constructor(
private fb: UntypedFormBuilder,
private toasterService: ToasterService,
private profileService: ProfileService,
private manageProfileState: ManageProfileStateService,
private readonly authService: AuthService,
private confirmationService: ConfirmationService,
@Inject(RE_LOGIN_CONFIRMATION_TOKEN)
private isPersonalSettingsChangedConfirmationActive: boolean,
protected injector: Injector,
) {}
@ -58,6 +65,7 @@ export class PersonalSettingsComponent
submit() {
if (this.form.invalid) return;
const isLogOutConfirmMessageVisible = this.isLogoutConfirmMessageActive();
this.inProgress = true;
this.profileService
.update(this.form.value)
@ -65,6 +73,39 @@ export class PersonalSettingsComponent
.subscribe(profile => {
this.manageProfileState.setProfile(profile);
this.toasterService.success('AbpAccount::PersonalSettingsSaved', 'Success', { life: 5000 });
if (isLogOutConfirmMessageVisible) {
this.showLogoutConfirmMessage();
}
});
}
isDataSame(oldValue, newValue) {
return Object.entries(oldValue).some(([key, value]) => {
if (key in newValue) {
return value !== newValue[key];
}
return false;
});
}
logoutConfirmation = () => {
this.authService.logout().subscribe();
};
private isLogoutConfirmMessageActive() {
if (!this.isPersonalSettingsChangedConfirmationActive) {
return false;
}
return this.isDataSame(this.profile, this.form.value);
}
private showLogoutConfirmMessage() {
this.confirmationService
.info(
'AbpAccount::PersonalSettingsChangedConfirmationModalDescription',
'AbpAccount::PersonalSettingsChangedConfirmationModalTitle',
)
.pipe(filter(status => status === Confirmation.Status.confirm))
.subscribe(this.logoutConfirmation);
}
}

1
npm/ng-packs/packages/account/src/lib/models/config-options.ts

@ -4,6 +4,7 @@ import { UpdateProfileDto } from '@abp/ng.account.core/proxy';
export interface AccountConfigOptions {
redirectUrl?: string;
isPersonalSettingsChangedConfirmationActive?: boolean;
editFormPropContributors?: AccountEditFormPropContributors;
}
export type AccountEditFormPropContributors = Partial<{

1
npm/ng-packs/packages/account/src/lib/tokens/index.ts

@ -1,2 +1,3 @@
export * from './config-options.token';
export * from './re-login-confirmation.token';
export * from './extensions.token';

5
npm/ng-packs/packages/account/src/lib/tokens/re-login-confirmation.token.ts

@ -0,0 +1,5 @@
import { InjectionToken } from '@angular/core';
export const RE_LOGIN_CONFIRMATION_TOKEN = new InjectionToken<boolean>(
'RE_LOGIN_CONFIRMATION_TOKEN',
);

1
npm/ng-packs/packages/components/tree/src/lib/components/tree.component.html

@ -16,7 +16,6 @@
></nz-tree>
<ng-template #treeTemplate let-node>
<div
class="node-wrapper"
[class.selected]="isNodeSelected(node)"
[title]="node.title"
(click)="onSelectedNodeChange(node)"

19
npm/ng-packs/packages/components/tree/src/lib/components/tree.component.ts

@ -3,6 +3,7 @@ import {
ContentChild,
EventEmitter,
Input,
OnChanges,
Output,
TemplateRef,
ViewEncapsulation,
@ -24,7 +25,7 @@ export type DropEvent = NzFormatEmitEvent & { pos: number };
],
encapsulation: ViewEncapsulation.None,
})
export class TreeComponent {
export class TreeComponent implements OnChanges {
dropPosition: number;
dropdowns = {} as { [key: string]: NgbDropdown };
@ -44,19 +45,31 @@ export class TreeComponent {
@Input() nodes = [];
@Input() expandedKeys: string[] = [];
@Input() selectedNode: any;
@Input() changeCheckboxWithNode: boolean;
@Input() changedNodeValues = [];
@Input() isNodeSelected = node => this.selectedNode?.id === node.key;
@Input() beforeDrop = (event: NzFormatBeforeDropEvent) => {
this.dropPosition = event.pos;
return of(false);
};
ngOnChanges() {
this.checkedKeys = [...this.changedNodeValues];
}
onSelectedNodeChange(node) {
this.selectedNode = node.origin.entity;
this.selectedNodeChange.emit(node.origin.entity);
if (this.changeCheckboxWithNode) {
this.selectedNodeChange.emit(node);
this.checkedKeys = [...this.changedNodeValues];
this.checkedKeysChange.emit(this.changedNodeValues);
} else {
this.selectedNodeChange.emit(node.origin.entity);
}
}
onCheckboxChange(event) {
this.checkedKeys = [...event.keys];
this.checkedKeys = this.changedNodeValues = [...event.keys];
this.checkedKeysChange.emit(event.keys);
}

62
templates/app-nolayers/angular/src/app/home/home.component.spec.ts

@ -0,0 +1,62 @@
import { CoreTestingModule } from '@abp/ng.core/testing';
import { ThemeBasicTestingModule } from '@abp/ng.theme.basic/testing';
import { ThemeSharedTestingModule } from '@abp/ng.theme.shared/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { NgxValidateCoreModule } from '@ngx-validate/core';
import { HomeComponent } from './home.component';
import { OAuthService } from 'angular-oauth2-oidc';
import { AuthService } from '@abp/ng.core';
describe('HomeComponent', () => {
let fixture: ComponentFixture<HomeComponent>;
const mockOAuthService = jasmine.createSpyObj('OAuthService', ['hasValidAccessToken']);
const mockAuthService = jasmine.createSpyObj('AuthService', ['navigateToLogin']);
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [HomeComponent],
imports: [
CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(),
ThemeBasicTestingModule.withConfig(),
NgxValidateCoreModule,
],
providers: [
/* mock providers here */
{
provide: OAuthService,
useValue: mockOAuthService,
},
{
provide: AuthService,
useValue: mockAuthService,
},
],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
fixture.detectChanges();
});
it('should be initiated', () => {
expect(fixture.componentInstance).toBeTruthy();
});
describe('when login state is true', () => {
beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(true);
});
it('hasLoggedIn should be true', () => {
expect(fixture.componentInstance.hasLoggedIn).toBeTrue();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled();
});
it('button should not be exists', () => {
const element = fixture.nativeElement;
const cardTitle = element.querySelector('.card-title');
expect(cardTitle).toBeTruthy();
});
});
});

3
templates/app-nolayers/angular/src/test.ts

@ -1,11 +1,10 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import 'zone.js/testing';
declare const require: {
context(

100
templates/app/angular/src/app/home/home.component.spec.ts

@ -0,0 +1,100 @@
import { CoreTestingModule } from "@abp/ng.core/testing";
import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing";
import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing";
import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing";
import { NgxValidateCoreModule } from "@ngx-validate/core";
import { HomeComponent } from "./home.component";
import { OAuthService } from 'angular-oauth2-oidc';
import { AuthService } from '@abp/ng.core';
describe("HomeComponent", () => {
let fixture: ComponentFixture<HomeComponent>;
const mockOAuthService = jasmine.createSpyObj('OAuthService', ['hasValidAccessToken'])
const mockAuthService = jasmine.createSpyObj('AuthService', ['navigateToLogin'])
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [HomeComponent],
imports: [
CoreTestingModule.withConfig(),
ThemeSharedTestingModule.withConfig(),
ThemeBasicTestingModule.withConfig(),
NgxValidateCoreModule,
],
providers: [
/* mock providers here */
{
provide: OAuthService,
useValue: mockOAuthService
},
{
provide: AuthService,
useValue: mockAuthService
}
],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(HomeComponent);
fixture.detectChanges();
});
it("should be initiated", () => {
expect(fixture.componentInstance).toBeTruthy();
});
describe('when login state is true', () => {
beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(true)
});
it("hasLoggedIn should be true", () => {
expect(fixture.componentInstance.hasLoggedIn).toBeTrue();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled()
})
it("button should not be exists", () => {
const element = fixture.nativeElement
const button = element.querySelector('[role="button"]')
expect(button).toBeNull()
})
})
describe('when login state is false', () => {
beforeAll(() => {
mockOAuthService.hasValidAccessToken.and.returnValue(false)
});
it("hasLoggedIn should be false", () => {
expect(fixture.componentInstance.hasLoggedIn).toBeFalse();
expect(mockOAuthService.hasValidAccessToken).toHaveBeenCalled()
})
it("button should be exists", () => {
const element = fixture.nativeElement
const button = element.querySelector('[role="button"]')
expect(button).toBeDefined()
})
describe('when button clicked', () => {
beforeEach(() => {
const element = fixture.nativeElement
const button = element.querySelector('[role="button"]')
button.click()
});
it("navigateToLogin have been called", () => {
expect(mockAuthService.navigateToLogin).toHaveBeenCalled()
})
})
})
});

4
templates/app/angular/src/test.ts

@ -1,11 +1,11 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files
import 'zone.js/testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting,
} from '@angular/platform-browser-dynamic/testing';
import 'zone.js/testing';
declare const require: {
context(

20
templates/module/angular/projects/my-project-name/src/lib/my-project-name.component.spec.ts

@ -1,16 +1,24 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MyProjectNameComponent } from './my-project-name.component';
import { MyProjectNameComponent } from './components/my-project-name.component';
import { MyProjectNameService } from '@my-company-name/my-project-name';
import { of } from 'rxjs';
describe('MyProjectNameComponent', () => {
let component: MyProjectNameComponent;
let fixture: ComponentFixture<MyProjectNameComponent>;
const mockMyProjectNameService = jasmine.createSpyObj('MyProjectNameService', {
sample: of([]),
});
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ MyProjectNameComponent ]
})
.compileComponents();
declarations: [MyProjectNameComponent],
providers: [
{
provide: MyProjectNameService,
useValue: mockMyProjectNameService,
},
],
}).compileComponents();
}));
beforeEach(() => {

15
templates/module/angular/projects/my-project-name/src/lib/my-project-name.service.spec.ts

@ -1,12 +1,19 @@
import { TestBed } from '@angular/core/testing';
import { MyProjectNameService } from './my-project-name.service';
import { MyProjectNameService } from './services/my-project-name.service';
import { RestService } from '@abp/ng.core';
describe('MyProjectNameService', () => {
let service: MyProjectNameService;
const mockRestService = jasmine.createSpyObj('RestService', ['request']);
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [
{
provide: RestService,
useValue: mockRestService,
},
],
});
service = TestBed.inject(MyProjectNameService);
});

Loading…
Cancel
Save