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:
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 }}

View File

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

View File

@@ -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<boolean>(1);
protected readonly isHandset: Signal<boolean>;
protected readonly positionStrategy: Signal<FlexibleConnectedPositionStrategy>;
protected readonly scrollStrategy: Signal<ScrollStrategy>;
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<boolean>): Signal<ScrollStrategy> {
return computed(() => (isHandset() ? this.overlay.scrollStrategies.block() : undefined!));
}
}

View File

@@ -1,5 +1,5 @@
<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>
<cnsl-header-button
cdkOverlayOrigin
@@ -26,19 +26,13 @@
></cnsl-organization-selector>
</cnsl-header-dropdown>
</ng-container>
<ng-container *ngIf="activeOrganizationQuery.data() as org">
<ng-container *ngTemplateOutlet="slash"></ng-container>
<cnsl-header-button
cdkOverlayOrigin
#orgTrigger="cdkOverlayOrigin"
(click)="isOrgDropdownOpen.set(!isOrgDropdownOpen())"
>
{{ org.name }}
</cnsl-header-button>
<cnsl-header-dropdown [trigger]="orgTrigger" [isOpen]="isOrgDropdownOpen()" (closed)="isOrgDropdownOpen.set(false)">
<cnsl-organization-selector (orgChanged)="isOrgDropdownOpen.set(false)"></cnsl-organization-selector>
</cnsl-header-dropdown>
</ng-container>
<ng-container *ngTemplateOutlet="slash"></ng-container>
<cnsl-header-button cdkOverlayOrigin #orgTrigger="cdkOverlayOrigin" (click)="isOrgDropdownOpen.set(!isOrgDropdownOpen())">
<ng-container *ngIf="activeOrganizationQuery.data() as org">{{ org.name }}</ng-container>
</cnsl-header-button>
<cnsl-header-dropdown [trigger]="orgTrigger" [isOpen]="isOrgDropdownOpen()" (closed)="isOrgDropdownOpen.set(false)">
<cnsl-organization-selector (orgChanged)="isOrgDropdownOpen.set(false)"></cnsl-organization-selector>
</cnsl-header-dropdown>
</div>
<ng-template #slash>

View File

@@ -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());

View File

@@ -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: {

View File

@@ -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,
) {}

View File

@@ -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);

View File

@@ -37,7 +37,6 @@ import {
combineLatestWith,
defer,
EMPTY,
identity,
mergeWith,
Observable,
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 { 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<MessageInitShape<typeof ListUsersRequestSchema>['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<Type>): Observable<Query[]> {
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()));

View File

@@ -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<typeof SetUpOrgRequestSchema>): Promise<SetUpOrgResponse> {
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'),
}));
}
}

View File

@@ -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<GetMyPasswordComplexityPolicyResponse> {
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) {
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<typeof ListOrganizationsRequestSchema>) {
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(