From 7c63140db23947fcdc8271a49e076286872beb9c Mon Sep 17 00:00:00 2001 From: conblem Date: Wed, 18 Jun 2025 13:18:30 +0200 Subject: [PATCH] fix some e2e tests and handle default org --- .github/workflows/build.yml | 8 +++---- .../header-dropdown.component.html | 2 +- .../header-dropdown.component.ts | 15 ++----------- .../new-header/new-header.component.html | 22 +++++++------------ .../new-header/new-header.component.ts | 15 ++++++++++++- .../organization-selector.component.ts | 11 +++++++++- .../domain-policy/domain-policy.component.ts | 1 - .../orgs/org-detail/org-detail.component.ts | 3 ++- .../user-detail/user-detail.component.ts | 1 - .../user-table/user-table.component.ts | 9 +++----- console/src/app/services/new-admin.service.ts | 10 +++++++-- console/src/app/services/new-auth.service.ts | 13 +++++++++++ .../app/services/new-organization.service.ts | 8 ++++--- 13 files changed, 70 insertions(+), 48 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3711a7d4e1..f06c4a959c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -86,9 +86,9 @@ jobs: with: build_image_name: "ghcr.io/zitadel/zitadel-build" -# e2e: -# uses: ./.github/workflows/e2e.yml -# needs: [compile] + e2e: + uses: ./.github/workflows/e2e.yml + needs: [compile] release: uses: ./.github/workflows/release.yml @@ -98,7 +98,7 @@ jobs: issues: write pull-requests: write needs: - [version, core-unit-test, core-integration-test, lint, container] #, e2e] + [version, core-unit-test, core-integration-test, lint, container, e2e] if: ${{ github.event_name == 'workflow_dispatch' }} secrets: GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }} diff --git a/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.html b/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.html index 177bc46919..6e0503081b 100644 --- a/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.html +++ b/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.html @@ -5,7 +5,7 @@ [cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOpen]="isOpen" [cdkConnectedOverlayPositionStrategy]="positionStrategy()" - [cdkConnectedOverlayScrollStrategy]="scrollStrategy()" + [cdkConnectedOverlayScrollStrategy]="scrollStrategy" [cdkConnectedOverlayHasBackdrop]="isHandset()" (overlayOutsideClick)="closed.emit()" > diff --git a/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.ts b/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.ts index f4200bfaaf..5f12a1660e 100644 --- a/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.ts +++ b/console/src/app/modules/new-header/header-dropdown/header-dropdown.component.ts @@ -12,13 +12,7 @@ import { Signal, untracked, } from '@angular/core'; -import { - CdkConnectedOverlay, - CdkOverlayOrigin, - FlexibleConnectedPositionStrategy, - Overlay, - ScrollStrategy, -} from '@angular/cdk/overlay'; +import { CdkConnectedOverlay, CdkOverlayOrigin, FlexibleConnectedPositionStrategy, Overlay } from '@angular/cdk/overlay'; import { BreakpointObserver } from '@angular/cdk/layout'; import { map } from 'rxjs/operators'; import { toSignal } from '@angular/core/rxjs-interop'; @@ -48,7 +42,7 @@ export class HeaderDropdownComponent implements OnInit { protected readonly isOpen$ = new ReplaySubject(1); protected readonly isHandset: Signal; protected readonly positionStrategy: Signal; - protected readonly scrollStrategy: Signal; + protected readonly scrollStrategy = this.overlay.scrollStrategies.block(); constructor( private readonly overlay: Overlay, @@ -57,7 +51,6 @@ export class HeaderDropdownComponent implements OnInit { ) { this.isHandset = this.getIsHandset(); this.positionStrategy = this.getPositionStrategy(this.isHandset); - this.scrollStrategy = this.getScrollStrategy(this.isHandset); } ngOnInit(): void { @@ -101,8 +94,4 @@ export class HeaderDropdownComponent implements OnInit { : undefined!, ); } - - private getScrollStrategy(isHandset: Signal): Signal { - return computed(() => (isHandset() ? this.overlay.scrollStrategies.block() : undefined!)); - } } diff --git a/console/src/app/modules/new-header/new-header.component.html b/console/src/app/modules/new-header/new-header.component.html index df3e195e2a..17c2b368c8 100644 --- a/console/src/app/modules/new-header/new-header.component.html +++ b/console/src/app/modules/new-header/new-header.component.html @@ -1,5 +1,5 @@
- + - - - - {{ org.name }} - - - - - + + + {{ org.name }} + + + +
diff --git a/console/src/app/modules/new-header/new-header.component.ts b/console/src/app/modules/new-header/new-header.component.ts index e475708757..0cc978f828 100644 --- a/console/src/app/modules/new-header/new-header.component.ts +++ b/console/src/app/modules/new-header/new-header.component.ts @@ -16,6 +16,7 @@ import { map } from 'rxjs/operators'; import { toSignal } from '@angular/core/rxjs-interop'; import { BreakpointObserver } from '@angular/cdk/layout'; import { NewAdminService } from '../../services/new-admin.service'; +import { NewAuthService } from '../../services/new-auth.service'; @Component({ selector: 'cnsl-new-header', @@ -39,8 +40,12 @@ import { NewAdminService } from '../../services/new-admin.service'; ], }) export class NewHeaderComponent { + protected readonly listMyZitadelPermissionsQuery = this.newAuthService.listMyZitadelPermissionsQuery(); protected readonly myInstanceQuery = this.adminService.getMyInstanceQuery(); - protected readonly organizationsQuery = injectQuery(() => this.newOrganizationService.listOrganizationsQueryOptions()); + protected readonly organizationsQuery = injectQuery(() => ({ + ...this.newOrganizationService.listOrganizationsQueryOptions(), + enabled: (this.listMyZitadelPermissionsQuery.data() ?? []).includes('org.read'), + })); protected readonly isInstanceDropdownOpen = signal(false); protected readonly isOrgDropdownOpen = signal(false); protected readonly instanceSelectorSecondStep = signal(false); @@ -52,8 +57,16 @@ export class NewHeaderComponent { private readonly toastService: ToastService, private readonly breakpointObserver: BreakpointObserver, private readonly adminService: NewAdminService, + private readonly newAuthService: NewAuthService, ) { this.isHandset = this.getIsHandset(); + + effect(() => { + if (this.listMyZitadelPermissionsQuery.isError()) { + this.toastService.showError(this.listMyZitadelPermissionsQuery.error()); + } + }); + effect(() => { if (this.organizationsQuery.isError()) { this.toastService.showError(this.organizationsQuery.error()); diff --git a/console/src/app/modules/new-header/organization-selector/organization-selector.component.ts b/console/src/app/modules/new-header/organization-selector/organization-selector.component.ts index 10fee1632c..d98b9dbb79 100644 --- a/console/src/app/modules/new-header/organization-selector/organization-selector.component.ts +++ b/console/src/app/modules/new-header/organization-selector/organization-selector.component.ts @@ -91,6 +91,15 @@ export class OrganizationSelectorComponent { toast.showError(this.activeOrg.error()); } }); + + effect(() => { + const orgId = newOrganizationService.orgId(); + const orgs = this.organizationsQuery.data()?.pages[0]?.result; + if (orgId || !orgs || orgs.length === 0) { + return; + } + const _ = newOrganizationService.setOrgId(orgs[0].id); + }); } private buildForm() { @@ -123,7 +132,7 @@ export class OrganizationSelectorComponent { return injectInfiniteQuery(() => { const query = nameQuery(); return { - queryKey: ['listOrganizationsInfinite', query], + queryKey: ['organization', 'listOrganizationsInfinite', query], queryFn: ({ pageParam, signal }) => this.newOrganizationService.listOrganizations(pageParam, signal), initialPageParam: { query: { diff --git a/console/src/app/modules/policies/domain-policy/domain-policy.component.ts b/console/src/app/modules/policies/domain-policy/domain-policy.component.ts index ccad5b2ff2..749b477a2c 100644 --- a/console/src/app/modules/policies/domain-policy/domain-policy.component.ts +++ b/console/src/app/modules/policies/domain-policy/domain-policy.component.ts @@ -39,7 +39,6 @@ export class DomainPolicyComponent implements OnInit, OnDestroy { private toast: ToastService, private injector: Injector, private adminService: AdminService, - private storageService: StorageService, private readonly newOrganizationService: NewOrganizationService, ) {} diff --git a/console/src/app/pages/orgs/org-detail/org-detail.component.ts b/console/src/app/pages/orgs/org-detail/org-detail.component.ts index 2c81089ed2..7187a10a04 100644 --- a/console/src/app/pages/orgs/org-detail/org-detail.component.ts +++ b/console/src/app/pages/orgs/org-detail/org-detail.component.ts @@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, effect, OnInit, signal } from '@angular/c import { MatDialog } from '@angular/material/dialog'; import { Router } from '@angular/router'; import { BehaviorSubject, from, lastValueFrom, Observable, of } from 'rxjs'; -import { catchError, distinctUntilChanged, finalize, map } from 'rxjs/operators'; +import { catchError, finalize, map } from 'rxjs/operators'; import { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component'; import { ChangeType } from 'src/app/modules/changes/changes.component'; import { InfoSectionType } from 'src/app/modules/info-section/info-section.component'; @@ -159,6 +159,7 @@ export class OrgDetailComponent implements OnInit { try { await this.deleteOrgMutation.mutateAsync(); + this.toast.showInfo('ORG.TOAST.DELETED', true); await this.router.navigate(['/orgs']); } catch (error) { this.toast.showError(error); diff --git a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.ts b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.ts index 90de097f35..5eb48d8c78 100644 --- a/console/src/app/pages/users/user-detail/user-detail/user-detail.component.ts +++ b/console/src/app/pages/users/user-detail/user-detail/user-detail.component.ts @@ -37,7 +37,6 @@ import { combineLatestWith, defer, EMPTY, - identity, mergeWith, Observable, ObservedValueOf, diff --git a/console/src/app/pages/users/user-list/user-table/user-table.component.ts b/console/src/app/pages/users/user-list/user-table/user-table.component.ts index 67a10ace42..cb8815f4dd 100644 --- a/console/src/app/pages/users/user-list/user-table/user-table.component.ts +++ b/console/src/app/pages/users/user-list/user-table/user-table.component.ts @@ -41,10 +41,8 @@ import { Type, UserFieldName } from '@zitadel/proto/zitadel/user/v2/query_pb'; import { UserState, User } from '@zitadel/proto/zitadel/user/v2/user_pb'; import { MessageInitShape } from '@bufbuild/protobuf'; import { ListUsersRequestSchema, ListUsersResponse } from '@zitadel/proto/zitadel/user/v2/user_service_pb'; -import { AuthenticationService } from 'src/app/services/authentication.service'; -import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; import { UserState as UserStateV1 } from 'src/app/proto/generated/zitadel/user_pb'; -import { NewOrganizationService } from '../../../../services/new-organization.service'; +import { NewOrganizationService } from 'src/app/services/new-organization.service'; type Query = Exclude< Exclude['queries'], undefined>[number]['query'], @@ -124,8 +122,6 @@ export class UserTableComponent implements OnInit { private readonly dialog: MatDialog, private readonly route: ActivatedRoute, private readonly destroyRef: DestroyRef, - private readonly authenticationService: AuthenticationService, - private readonly authService: GrpcAuthService, private readonly newOrganizationService: NewOrganizationService, ) { this.type$ = this.getType$().pipe(shareReplay({ refCount: true, bufferSize: 1 })); @@ -232,9 +228,10 @@ export class UserTableComponent implements OnInit { } private getQueries(type$: Observable): Observable { + const orgId$ = toObservable(this.newOrganizationService.orgId).pipe(filter(Boolean)); return this.searchQueries$.pipe( startWith([]), - combineLatestWith(type$, toObservable(this.newOrganizationService.getOrgId())), + combineLatestWith(type$, orgId$), map(([queries, type, organizationId]) => { const mappedQueries = queries.map((q) => this.searchQueryToV2(q.toObject())); diff --git a/console/src/app/services/new-admin.service.ts b/console/src/app/services/new-admin.service.ts index bbf8609487..f8d5bf0718 100644 --- a/console/src/app/services/new-admin.service.ts +++ b/console/src/app/services/new-admin.service.ts @@ -8,12 +8,16 @@ import { SetUpOrgResponse, } from '@zitadel/proto/zitadel/admin_pb'; import { injectQuery } from '@tanstack/angular-query-experimental'; +import { NewAuthService } from './new-auth.service'; @Injectable({ providedIn: 'root', }) export class NewAdminService { - constructor(private readonly grpcService: GrpcService) {} + constructor( + private readonly grpcService: GrpcService, + private readonly authService: NewAuthService, + ) {} public setupOrg(req: MessageInitShape): Promise { return this.grpcService.adminNew.setupOrg(req); @@ -28,9 +32,11 @@ export class NewAdminService { } public getMyInstanceQuery() { + const listMyZitadelPermissionsQuery = this.authService.listMyZitadelPermissionsQuery(); return injectQuery(() => ({ queryKey: ['admin', 'getMyInstance'], - queryFn: ({ signal }) => this.getMyInstance(signal), + queryFn: async () => this.getMyInstance(), + enabled: (listMyZitadelPermissionsQuery.data() ?? []).includes('iam.write'), })); } } diff --git a/console/src/app/services/new-auth.service.ts b/console/src/app/services/new-auth.service.ts index b7a4463168..d503fe397b 100644 --- a/console/src/app/services/new-auth.service.ts +++ b/console/src/app/services/new-auth.service.ts @@ -18,7 +18,9 @@ import { RemoveMyAuthFactorOTPSMSResponse, ListMyMetadataResponse, VerifyMyPhoneResponse, + ListMyZitadelPermissionsResponse, } from '@zitadel/proto/zitadel/auth_pb'; +import { injectQuery } from '@tanstack/angular-query-experimental'; @Injectable({ providedIn: 'root', @@ -65,4 +67,15 @@ export class NewAuthService { public getMyPasswordComplexityPolicy(): Promise { return this.grpcService.authNew.getMyPasswordComplexityPolicy({}); } + + public listMyZitadelPermissions(): Promise { + return this.grpcService.authNew.listMyZitadelPermissions({}); + } + + public listMyZitadelPermissionsQuery() { + return injectQuery(() => ({ + queryKey: ['auth', 'listMyZitadelPermissions'], + queryFn: () => this.listMyZitadelPermissions().then(({ result }) => result), + })); + } } diff --git a/console/src/app/services/new-organization.service.ts b/console/src/app/services/new-organization.service.ts index beaf8c00cc..3b02fa238e 100644 --- a/console/src/app/services/new-organization.service.ts +++ b/console/src/app/services/new-organization.service.ts @@ -44,6 +44,8 @@ export class NewOrganizationService { } public async setOrgId(orgId?: string) { + console.log('beboop', orgId); + console.trace(orgId); const organization = await this.queryClient.fetchQuery(this.organizationByIdQueryOptions(orgId ?? this.getOrgId()())); if (organization) { this.storage.setItem(StorageKey.organizationId, orgId, StorageLocation.session); @@ -73,7 +75,7 @@ export class NewOrganizationService { }; return queryOptions({ - queryKey: ['listOrganizations', req], + queryKey: ['organization', 'listOrganizations', req], queryFn: organizationId ? () => this.listOrganizations(req).then((resp) => resp.result.find(Boolean) ?? null) : skipToken, @@ -93,7 +95,7 @@ export class NewOrganizationService { public listOrganizationsQueryKey(req?: MessageInitShape) { if (!req) { - return ['listOrganizations']; + return ['organization', 'listOrganizations']; } // needed because angular query isn't able to serialize a bigint key @@ -103,7 +105,7 @@ export class NewOrganizationService { ...(query ? { query } : {}), }; - return ['listOrganizations', queryKey]; + return ['organization', 'listOrganizations', queryKey]; } public listOrganizations(