mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-29 18:42:15 +00:00
feat: show all available organizations when creating project grants (#6040)
* feat: show available orgs (project) grants * feat: add e2e for project grant * feat: add bulgarian missing translations * feat: update docs * fix: add @peintnermax suggested changes --------- Co-authored-by: Max Peintner <[email protected]>
This commit is contained in:
co-authored by
Max Peintner
parent
e1b3cda98a
commit
7b44209bfd
@@ -16,6 +16,7 @@
|
||||
color="primary"
|
||||
class="cnsl-action-button"
|
||||
mat-raised-button
|
||||
data-e2e="create-project-role-button"
|
||||
>
|
||||
<mat-icon data-e2e="add-new-role" class="icon">add</mat-icon>
|
||||
<span>{{ 'ACTIONS.NEW' | translate }}</span>
|
||||
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
<form>
|
||||
<cnsl-form-field class="full-width">
|
||||
<cnsl-label>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_FORMFIELD' | translate }}</cnsl-label>
|
||||
<input
|
||||
cnslInput
|
||||
type="text"
|
||||
placeholder="Organization XY"
|
||||
#nameInput
|
||||
[formControl]="myControl"
|
||||
[matAutocomplete]="auto"
|
||||
data-e2e="add-org-input"
|
||||
/>
|
||||
|
||||
<mat-autocomplete #auto="matAutocomplete" (optionSelected)="selected($event)" [displayWith]="displayFn">
|
||||
<mat-option *ngIf="isLoading" class="is-loading">
|
||||
<mat-spinner diameter="30"></mat-spinner>
|
||||
</mat-option>
|
||||
<mat-option *ngFor="let org of filteredOrgs" [value]="org">
|
||||
<div class="org-option" data-e2e="org-option">
|
||||
<div class="org-option-column">
|
||||
<span>{{ org.name }}</span>
|
||||
<span class="fill-space"></span>
|
||||
<span class="smaller cnsl-secondary-text">{{ org.primaryDomain }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</mat-option>
|
||||
</mat-autocomplete>
|
||||
<span class="org-autocomplete-target-desc">
|
||||
{{ 'PROJECT.GRANT.CREATE.SEL_ORG_DESC' | translate }}
|
||||
</span>
|
||||
</cnsl-form-field>
|
||||
</form>
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
.full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
input {
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.org-option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.org-option-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.fill-space {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.smaller {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.org-autocomplete-target-desc {
|
||||
font-size: 14px;
|
||||
display: block;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { SearchOrgAutocompleteComponent } from './search-org-autocomplete.component';
|
||||
|
||||
describe('SearchOrgComponent', () => {
|
||||
let component: SearchOrgAutocompleteComponent;
|
||||
let fixture: ComponentFixture<SearchOrgAutocompleteComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [SearchOrgAutocompleteComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SearchOrgAutocompleteComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { UntypedFormControl } from '@angular/forms';
|
||||
import { MatAutocompleteSelectedEvent } from '@angular/material/autocomplete';
|
||||
import { MatLegacyAutocomplete as MatAutocomplete } from '@angular/material/legacy-autocomplete';
|
||||
import { debounceTime, from, map, Subject, switchMap, takeUntil, tap } from 'rxjs';
|
||||
import { TextQueryMethod } from 'src/app/proto/generated/zitadel/object_pb';
|
||||
import { Org, OrgNameQuery, OrgQuery, OrgState, OrgStateQuery } from 'src/app/proto/generated/zitadel/org_pb';
|
||||
import { AuthenticationService } from 'src/app/services/authentication.service';
|
||||
import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
|
||||
@Component({
|
||||
selector: 'cnsl-search-org-autocomplete',
|
||||
templateUrl: './search-org-autocomplete.component.html',
|
||||
styleUrls: ['./search-org-autocomplete.component.scss'],
|
||||
})
|
||||
export class SearchOrgAutocompleteComponent implements OnInit, OnDestroy {
|
||||
public selectable: boolean = true;
|
||||
public myControl: UntypedFormControl = new UntypedFormControl();
|
||||
public filteredOrgs: Array<Org.AsObject> = [];
|
||||
public isLoading: boolean = false;
|
||||
@ViewChild('auto') public matAutocomplete!: MatAutocomplete;
|
||||
@Output() public selectionChanged: EventEmitter<Org.AsObject> = new EventEmitter();
|
||||
|
||||
private unsubscribed$: Subject<void> = new Subject();
|
||||
constructor(public authService: AuthenticationService, private auth: GrpcAuthService) {
|
||||
this.myControl.valueChanges
|
||||
.pipe(
|
||||
takeUntil(this.unsubscribed$),
|
||||
debounceTime(200),
|
||||
tap(() => (this.isLoading = true)),
|
||||
switchMap((value) => {
|
||||
const stateQuery = new OrgQuery();
|
||||
const orgStateQuery = new OrgStateQuery();
|
||||
orgStateQuery.setState(OrgState.ORG_STATE_ACTIVE);
|
||||
stateQuery.setStateQuery(orgStateQuery);
|
||||
|
||||
let queries: OrgQuery[] = [stateQuery];
|
||||
|
||||
if (value) {
|
||||
const nameQuery = new OrgQuery();
|
||||
const orgNameQuery = new OrgNameQuery();
|
||||
orgNameQuery.setName(value);
|
||||
orgNameQuery.setMethod(TextQueryMethod.TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE);
|
||||
nameQuery.setNameQuery(orgNameQuery);
|
||||
queries = [stateQuery, nameQuery];
|
||||
}
|
||||
|
||||
return from(this.auth.listMyProjectOrgs(undefined, 0, queries)).pipe(
|
||||
map((resp) => {
|
||||
return resp.resultList.sort((left, right) => left.name.localeCompare(right.name));
|
||||
}),
|
||||
);
|
||||
}),
|
||||
)
|
||||
.subscribe((returnValue) => {
|
||||
this.isLoading = false;
|
||||
this.filteredOrgs = returnValue;
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnInit(): void {
|
||||
const query = new OrgQuery();
|
||||
const orgStateQuery = new OrgStateQuery();
|
||||
orgStateQuery.setState(OrgState.ORG_STATE_ACTIVE);
|
||||
query.setStateQuery(orgStateQuery);
|
||||
|
||||
this.auth.listMyProjectOrgs(undefined, 0, [query]).then((orgs) => {
|
||||
this.filteredOrgs = orgs.resultList;
|
||||
});
|
||||
}
|
||||
|
||||
public ngOnDestroy(): void {
|
||||
this.unsubscribed$.next();
|
||||
}
|
||||
|
||||
public displayFn(org?: Org.AsObject): string {
|
||||
return org ? `${org.name}` : '';
|
||||
}
|
||||
|
||||
public selected(event: MatAutocompleteSelectedEvent): void {
|
||||
this.selectionChanged.emit(event.option.value);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatLegacyAutocompleteModule as MatAutocompleteModule } from '@angular/material/legacy-autocomplete';
|
||||
import { MatLegacyButtonModule as MatButtonModule } from '@angular/material/legacy-button';
|
||||
import { MatLegacyChipsModule as MatChipsModule } from '@angular/material/legacy-chips';
|
||||
import { MatLegacyProgressSpinnerModule as MatProgressSpinnerModule } from '@angular/material/legacy-progress-spinner';
|
||||
import { MatLegacySelectModule as MatSelectModule } from '@angular/material/legacy-select';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { InputModule } from 'src/app/modules/input/input.module';
|
||||
|
||||
import { SearchOrgAutocompleteComponent } from './search-org-autocomplete.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [SearchOrgAutocompleteComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatAutocompleteModule,
|
||||
MatChipsModule,
|
||||
MatButtonModule,
|
||||
InputModule,
|
||||
MatIconModule,
|
||||
ReactiveFormsModule,
|
||||
MatProgressSpinnerModule,
|
||||
FormsModule,
|
||||
TranslateModule,
|
||||
MatSelectModule,
|
||||
],
|
||||
exports: [SearchOrgAutocompleteComponent],
|
||||
})
|
||||
export class SearchOrgAutocompleteModule {}
|
||||
Reference in New Issue
Block a user