fix some e2e tests and handle default org

This commit is contained in:
conblem
2025-06-18 13:18:30 +02:00
parent a0bdab37a1
commit 7c63140db2
13 changed files with 70 additions and 48 deletions

View File

@@ -86,9 +86,9 @@ jobs:
with: with:
build_image_name: "ghcr.io/zitadel/zitadel-build" build_image_name: "ghcr.io/zitadel/zitadel-build"
# e2e: e2e:
# uses: ./.github/workflows/e2e.yml uses: ./.github/workflows/e2e.yml
# needs: [compile] needs: [compile]
release: release:
uses: ./.github/workflows/release.yml uses: ./.github/workflows/release.yml
@@ -98,7 +98,7 @@ jobs:
issues: write issues: write
pull-requests: write pull-requests: write
needs: 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' }} if: ${{ github.event_name == 'workflow_dispatch' }}
secrets: secrets:
GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }} GCR_JSON_KEY_BASE64: ${{ secrets.GCR_JSON_KEY_BASE64 }}

View File

@@ -5,7 +5,7 @@
[cdkConnectedOverlayOrigin]="trigger" [cdkConnectedOverlayOrigin]="trigger"
[cdkConnectedOverlayOpen]="isOpen" [cdkConnectedOverlayOpen]="isOpen"
[cdkConnectedOverlayPositionStrategy]="positionStrategy()" [cdkConnectedOverlayPositionStrategy]="positionStrategy()"
[cdkConnectedOverlayScrollStrategy]="scrollStrategy()" [cdkConnectedOverlayScrollStrategy]="scrollStrategy"
[cdkConnectedOverlayHasBackdrop]="isHandset()" [cdkConnectedOverlayHasBackdrop]="isHandset()"
(overlayOutsideClick)="closed.emit()" (overlayOutsideClick)="closed.emit()"
> >

View File

@@ -12,13 +12,7 @@ import {
Signal, Signal,
untracked, untracked,
} from '@angular/core'; } from '@angular/core';
import { import { CdkConnectedOverlay, CdkOverlayOrigin, FlexibleConnectedPositionStrategy, Overlay } from '@angular/cdk/overlay';
CdkConnectedOverlay,
CdkOverlayOrigin,
FlexibleConnectedPositionStrategy,
Overlay,
ScrollStrategy,
} from '@angular/cdk/overlay';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { toSignal } from '@angular/core/rxjs-interop'; import { toSignal } from '@angular/core/rxjs-interop';
@@ -48,7 +42,7 @@ export class HeaderDropdownComponent implements OnInit {
protected readonly isOpen$ = new ReplaySubject<boolean>(1); protected readonly isOpen$ = new ReplaySubject<boolean>(1);
protected readonly isHandset: Signal<boolean>; protected readonly isHandset: Signal<boolean>;
protected readonly positionStrategy: Signal<FlexibleConnectedPositionStrategy>; protected readonly positionStrategy: Signal<FlexibleConnectedPositionStrategy>;
protected readonly scrollStrategy: Signal<ScrollStrategy>; protected readonly scrollStrategy = this.overlay.scrollStrategies.block();
constructor( constructor(
private readonly overlay: Overlay, private readonly overlay: Overlay,
@@ -57,7 +51,6 @@ export class HeaderDropdownComponent implements OnInit {
) { ) {
this.isHandset = this.getIsHandset(); this.isHandset = this.getIsHandset();
this.positionStrategy = this.getPositionStrategy(this.isHandset); this.positionStrategy = this.getPositionStrategy(this.isHandset);
this.scrollStrategy = this.getScrollStrategy(this.isHandset);
} }
ngOnInit(): void { ngOnInit(): void {
@@ -101,8 +94,4 @@ export class HeaderDropdownComponent implements OnInit {
: undefined!, : undefined!,
); );
} }
private getScrollStrategy(isHandset: Signal<boolean>): Signal<ScrollStrategy> {
return computed(() => (isHandset() ? this.overlay.scrollStrategies.block() : undefined!));
}
} }

View File

@@ -1,5 +1,5 @@
<div class="new-header-wrapper"> <div class="new-header-wrapper">
<ng-container *ngIf="(['iam.read$', 'iam.write$'] | hasRole | async) && myInstanceQuery.data()?.instance as instance"> <ng-container *ngIf="myInstanceQuery.data()?.instance as instance">
<ng-container *ngTemplateOutlet="slash"></ng-container> <ng-container *ngTemplateOutlet="slash"></ng-container>
<cnsl-header-button <cnsl-header-button
cdkOverlayOrigin cdkOverlayOrigin
@@ -26,19 +26,13 @@
></cnsl-organization-selector> ></cnsl-organization-selector>
</cnsl-header-dropdown> </cnsl-header-dropdown>
</ng-container> </ng-container>
<ng-container *ngIf="activeOrganizationQuery.data() as org">
<ng-container *ngTemplateOutlet="slash"></ng-container> <ng-container *ngTemplateOutlet="slash"></ng-container>
<cnsl-header-button <cnsl-header-button cdkOverlayOrigin #orgTrigger="cdkOverlayOrigin" (click)="isOrgDropdownOpen.set(!isOrgDropdownOpen())">
cdkOverlayOrigin <ng-container *ngIf="activeOrganizationQuery.data() as org">{{ org.name }}</ng-container>
#orgTrigger="cdkOverlayOrigin"
(click)="isOrgDropdownOpen.set(!isOrgDropdownOpen())"
>
{{ org.name }}
</cnsl-header-button> </cnsl-header-button>
<cnsl-header-dropdown [trigger]="orgTrigger" [isOpen]="isOrgDropdownOpen()" (closed)="isOrgDropdownOpen.set(false)"> <cnsl-header-dropdown [trigger]="orgTrigger" [isOpen]="isOrgDropdownOpen()" (closed)="isOrgDropdownOpen.set(false)">
<cnsl-organization-selector (orgChanged)="isOrgDropdownOpen.set(false)"></cnsl-organization-selector> <cnsl-organization-selector (orgChanged)="isOrgDropdownOpen.set(false)"></cnsl-organization-selector>
</cnsl-header-dropdown> </cnsl-header-dropdown>
</ng-container>
</div> </div>
<ng-template #slash> <ng-template #slash>

View File

@@ -16,6 +16,7 @@ import { map } from 'rxjs/operators';
import { toSignal } from '@angular/core/rxjs-interop'; import { toSignal } from '@angular/core/rxjs-interop';
import { BreakpointObserver } from '@angular/cdk/layout'; import { BreakpointObserver } from '@angular/cdk/layout';
import { NewAdminService } from '../../services/new-admin.service'; import { NewAdminService } from '../../services/new-admin.service';
import { NewAuthService } from '../../services/new-auth.service';
@Component({ @Component({
selector: 'cnsl-new-header', selector: 'cnsl-new-header',
@@ -39,8 +40,12 @@ import { NewAdminService } from '../../services/new-admin.service';
], ],
}) })
export class NewHeaderComponent { export class NewHeaderComponent {
protected readonly listMyZitadelPermissionsQuery = this.newAuthService.listMyZitadelPermissionsQuery();
protected readonly myInstanceQuery = this.adminService.getMyInstanceQuery(); 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 isInstanceDropdownOpen = signal(false);
protected readonly isOrgDropdownOpen = signal(false); protected readonly isOrgDropdownOpen = signal(false);
protected readonly instanceSelectorSecondStep = signal(false); protected readonly instanceSelectorSecondStep = signal(false);
@@ -52,8 +57,16 @@ export class NewHeaderComponent {
private readonly toastService: ToastService, private readonly toastService: ToastService,
private readonly breakpointObserver: BreakpointObserver, private readonly breakpointObserver: BreakpointObserver,
private readonly adminService: NewAdminService, private readonly adminService: NewAdminService,
private readonly newAuthService: NewAuthService,
) { ) {
this.isHandset = this.getIsHandset(); this.isHandset = this.getIsHandset();
effect(() => {
if (this.listMyZitadelPermissionsQuery.isError()) {
this.toastService.showError(this.listMyZitadelPermissionsQuery.error());
}
});
effect(() => { effect(() => {
if (this.organizationsQuery.isError()) { if (this.organizationsQuery.isError()) {
this.toastService.showError(this.organizationsQuery.error()); this.toastService.showError(this.organizationsQuery.error());

View File

@@ -91,6 +91,15 @@ export class OrganizationSelectorComponent {
toast.showError(this.activeOrg.error()); 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() { private buildForm() {
@@ -123,7 +132,7 @@ export class OrganizationSelectorComponent {
return injectInfiniteQuery(() => { return injectInfiniteQuery(() => {
const query = nameQuery(); const query = nameQuery();
return { return {
queryKey: ['listOrganizationsInfinite', query], queryKey: ['organization', 'listOrganizationsInfinite', query],
queryFn: ({ pageParam, signal }) => this.newOrganizationService.listOrganizations(pageParam, signal), queryFn: ({ pageParam, signal }) => this.newOrganizationService.listOrganizations(pageParam, signal),
initialPageParam: { initialPageParam: {
query: { query: {

View File

@@ -39,7 +39,6 @@ export class DomainPolicyComponent implements OnInit, OnDestroy {
private toast: ToastService, private toast: ToastService,
private injector: Injector, private injector: Injector,
private adminService: AdminService, private adminService: AdminService,
private storageService: StorageService,
private readonly newOrganizationService: NewOrganizationService, private readonly newOrganizationService: NewOrganizationService,
) {} ) {}

View File

@@ -2,7 +2,7 @@ import { ChangeDetectorRef, Component, effect, OnInit, signal } from '@angular/c
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { BehaviorSubject, from, lastValueFrom, Observable, of } from 'rxjs'; 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 { CreationType, MemberCreateDialogComponent } from 'src/app/modules/add-member-dialog/member-create-dialog.component';
import { ChangeType } from 'src/app/modules/changes/changes.component'; import { ChangeType } from 'src/app/modules/changes/changes.component';
import { InfoSectionType } from 'src/app/modules/info-section/info-section.component'; import { InfoSectionType } from 'src/app/modules/info-section/info-section.component';
@@ -159,6 +159,7 @@ export class OrgDetailComponent implements OnInit {
try { try {
await this.deleteOrgMutation.mutateAsync(); await this.deleteOrgMutation.mutateAsync();
this.toast.showInfo('ORG.TOAST.DELETED', true);
await this.router.navigate(['/orgs']); await this.router.navigate(['/orgs']);
} catch (error) { } catch (error) {
this.toast.showError(error); this.toast.showError(error);

View File

@@ -37,7 +37,6 @@ import {
combineLatestWith, combineLatestWith,
defer, defer,
EMPTY, EMPTY,
identity,
mergeWith, mergeWith,
Observable, Observable,
ObservedValueOf, ObservedValueOf,

View File

@@ -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 { UserState, User } from '@zitadel/proto/zitadel/user/v2/user_pb';
import { MessageInitShape } from '@bufbuild/protobuf'; import { MessageInitShape } from '@bufbuild/protobuf';
import { ListUsersRequestSchema, ListUsersResponse } from '@zitadel/proto/zitadel/user/v2/user_service_pb'; 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 { 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< type Query = Exclude<
Exclude<MessageInitShape<typeof ListUsersRequestSchema>['queries'], undefined>[number]['query'], Exclude<MessageInitShape<typeof ListUsersRequestSchema>['queries'], undefined>[number]['query'],
@@ -124,8 +122,6 @@ export class UserTableComponent implements OnInit {
private readonly dialog: MatDialog, private readonly dialog: MatDialog,
private readonly route: ActivatedRoute, private readonly route: ActivatedRoute,
private readonly destroyRef: DestroyRef, private readonly destroyRef: DestroyRef,
private readonly authenticationService: AuthenticationService,
private readonly authService: GrpcAuthService,
private readonly newOrganizationService: NewOrganizationService, private readonly newOrganizationService: NewOrganizationService,
) { ) {
this.type$ = this.getType$().pipe(shareReplay({ refCount: true, bufferSize: 1 })); this.type$ = this.getType$().pipe(shareReplay({ refCount: true, bufferSize: 1 }));
@@ -232,9 +228,10 @@ export class UserTableComponent implements OnInit {
} }
private getQueries(type$: Observable<Type>): Observable<Query[]> { private getQueries(type$: Observable<Type>): Observable<Query[]> {
const orgId$ = toObservable(this.newOrganizationService.orgId).pipe(filter(Boolean));
return this.searchQueries$.pipe( return this.searchQueries$.pipe(
startWith([]), startWith([]),
combineLatestWith(type$, toObservable(this.newOrganizationService.getOrgId())), combineLatestWith(type$, orgId$),
map(([queries, type, organizationId]) => { map(([queries, type, organizationId]) => {
const mappedQueries = queries.map((q) => this.searchQueryToV2(q.toObject())); const mappedQueries = queries.map((q) => this.searchQueryToV2(q.toObject()));

View File

@@ -8,12 +8,16 @@ import {
SetUpOrgResponse, SetUpOrgResponse,
} from '@zitadel/proto/zitadel/admin_pb'; } from '@zitadel/proto/zitadel/admin_pb';
import { injectQuery } from '@tanstack/angular-query-experimental'; import { injectQuery } from '@tanstack/angular-query-experimental';
import { NewAuthService } from './new-auth.service';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
}) })
export class NewAdminService { export class NewAdminService {
constructor(private readonly grpcService: GrpcService) {} constructor(
private readonly grpcService: GrpcService,
private readonly authService: NewAuthService,
) {}
public setupOrg(req: MessageInitShape<typeof SetUpOrgRequestSchema>): Promise<SetUpOrgResponse> { public setupOrg(req: MessageInitShape<typeof SetUpOrgRequestSchema>): Promise<SetUpOrgResponse> {
return this.grpcService.adminNew.setupOrg(req); return this.grpcService.adminNew.setupOrg(req);
@@ -28,9 +32,11 @@ export class NewAdminService {
} }
public getMyInstanceQuery() { public getMyInstanceQuery() {
const listMyZitadelPermissionsQuery = this.authService.listMyZitadelPermissionsQuery();
return injectQuery(() => ({ return injectQuery(() => ({
queryKey: ['admin', 'getMyInstance'], queryKey: ['admin', 'getMyInstance'],
queryFn: ({ signal }) => this.getMyInstance(signal), queryFn: async () => this.getMyInstance(),
enabled: (listMyZitadelPermissionsQuery.data() ?? []).includes('iam.write'),
})); }));
} }
} }

View File

@@ -18,7 +18,9 @@ import {
RemoveMyAuthFactorOTPSMSResponse, RemoveMyAuthFactorOTPSMSResponse,
ListMyMetadataResponse, ListMyMetadataResponse,
VerifyMyPhoneResponse, VerifyMyPhoneResponse,
ListMyZitadelPermissionsResponse,
} from '@zitadel/proto/zitadel/auth_pb'; } from '@zitadel/proto/zitadel/auth_pb';
import { injectQuery } from '@tanstack/angular-query-experimental';
@Injectable({ @Injectable({
providedIn: 'root', providedIn: 'root',
@@ -65,4 +67,15 @@ export class NewAuthService {
public getMyPasswordComplexityPolicy(): Promise<GetMyPasswordComplexityPolicyResponse> { public getMyPasswordComplexityPolicy(): Promise<GetMyPasswordComplexityPolicyResponse> {
return this.grpcService.authNew.getMyPasswordComplexityPolicy({}); return this.grpcService.authNew.getMyPasswordComplexityPolicy({});
} }
public listMyZitadelPermissions(): Promise<ListMyZitadelPermissionsResponse> {
return this.grpcService.authNew.listMyZitadelPermissions({});
}
public listMyZitadelPermissionsQuery() {
return injectQuery(() => ({
queryKey: ['auth', 'listMyZitadelPermissions'],
queryFn: () => this.listMyZitadelPermissions().then(({ result }) => result),
}));
}
} }

View File

@@ -44,6 +44,8 @@ export class NewOrganizationService {
} }
public async setOrgId(orgId?: string) { public async setOrgId(orgId?: string) {
console.log('beboop', orgId);
console.trace(orgId);
const organization = await this.queryClient.fetchQuery(this.organizationByIdQueryOptions(orgId ?? this.getOrgId()())); const organization = await this.queryClient.fetchQuery(this.organizationByIdQueryOptions(orgId ?? this.getOrgId()()));
if (organization) { if (organization) {
this.storage.setItem(StorageKey.organizationId, orgId, StorageLocation.session); this.storage.setItem(StorageKey.organizationId, orgId, StorageLocation.session);
@@ -73,7 +75,7 @@ export class NewOrganizationService {
}; };
return queryOptions({ return queryOptions({
queryKey: ['listOrganizations', req], queryKey: ['organization', 'listOrganizations', req],
queryFn: organizationId queryFn: organizationId
? () => this.listOrganizations(req).then((resp) => resp.result.find(Boolean) ?? null) ? () => this.listOrganizations(req).then((resp) => resp.result.find(Boolean) ?? null)
: skipToken, : skipToken,
@@ -93,7 +95,7 @@ export class NewOrganizationService {
public listOrganizationsQueryKey(req?: MessageInitShape<typeof ListOrganizationsRequestSchema>) { public listOrganizationsQueryKey(req?: MessageInitShape<typeof ListOrganizationsRequestSchema>) {
if (!req) { if (!req) {
return ['listOrganizations']; return ['organization', 'listOrganizations'];
} }
// needed because angular query isn't able to serialize a bigint key // needed because angular query isn't able to serialize a bigint key
@@ -103,7 +105,7 @@ export class NewOrganizationService {
...(query ? { query } : {}), ...(query ? { query } : {}),
}; };
return ['listOrganizations', queryKey]; return ['organization', 'listOrganizations', queryKey];
} }
public listOrganizations( public listOrganizations(