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 <max@caos.ch>
This commit is contained in:
Miguel Cabrerizo 2023-07-18 08:45:34 +02:00 committed by GitHub
parent e1b3cda98a
commit 7b44209bfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 328 additions and 70 deletions

View File

@ -16,6 +16,7 @@
color="primary" color="primary"
class="cnsl-action-button" class="cnsl-action-button"
mat-raised-button mat-raised-button
data-e2e="create-project-role-button"
> >
<mat-icon data-e2e="add-new-role" class="icon">add</mat-icon> <mat-icon data-e2e="add-new-role" class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span> <span>{{ 'ACTIONS.NEW' | translate }}</span>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,20 +6,8 @@
> >
<ng-container *ngIf="currentCreateStep === 1"> <ng-container *ngIf="currentCreateStep === 1">
<h1>{{ 'PROJECT.GRANT.CREATE.SEL_ORG' | translate }}</h1> <h1>{{ 'PROJECT.GRANT.CREATE.SEL_ORG' | translate }}</h1>
<p>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_DESC' | translate }}</p> <cnsl-search-org-autocomplete class="block" (selectionChanged)="selectOrg($event)"> </cnsl-search-org-autocomplete>
<span *ngIf="org"> {{ 'PROJECT.GRANT.CREATE.FOR_ORG' | translate }} {{ org.name }} - {{ org.primaryDomain }} </span>
<form (ngSubmit)="searchOrg(domain.value)">
<cnsl-form-field class="org-domain">
<cnsl-label>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_FORMFIELD' | translate }}</cnsl-label>
<input cnslInput #domain />
</cnsl-form-field>
<button [disabled]="domain.value.length === 0" color="primary" type="submit" class="domain-button" mat-raised-button>
{{ 'PROJECT.GRANT.CREATE.SEL_ORG_BUTTON' | translate }}
</button>
</form>
<span *ngIf="org"> {{ 'PROJECT.GRANT.CREATE.FOR_ORG' | translate }} {{ org.name }} </span>
</ng-container> </ng-container>
<ng-container *ngIf="currentCreateStep === 2"> <ng-container *ngIf="currentCreateStep === 2">
@ -37,7 +25,15 @@
<div class="btn-container"> <div class="btn-container">
<ng-container *ngIf="currentCreateStep === 1"> <ng-container *ngIf="currentCreateStep === 1">
<button [disabled]="!org" (click)="next()" color="primary" mat-raised-button class="big-button" cdkFocusInitial> <button
[disabled]="!org"
(click)="next()"
color="primary"
mat-raised-button
class="big-button"
cdkFocusInitial
data-e2e="project-grant-continue"
>
{{ 'ACTIONS.CONTINUE' | translate }} {{ 'ACTIONS.CONTINUE' | translate }}
</button> </button>
</ng-container> </ng-container>
@ -46,7 +42,15 @@
<button (click)="previous()" mat-stroked-button class="small-button"> <button (click)="previous()" mat-stroked-button class="small-button">
{{ 'ACTIONS.BACK' | translate }} {{ 'ACTIONS.BACK' | translate }}
</button> </button>
<button color="primary" [disabled]="!org" (click)="addGrant()" mat-raised-button class="big-button" cdkFocusInitial> <button
color="primary"
[disabled]="!org"
(click)="addGrant()"
mat-raised-button
class="big-button"
cdkFocusInitial
data-e2e="save-project-grant-button"
>
{{ 'ACTIONS.SAVE' | translate }} {{ 'ACTIONS.SAVE' | translate }}
</button> </button>
</ng-container> </ng-container>

View File

@ -88,6 +88,10 @@ export class ProjectGrantCreateComponent implements OnInit, OnDestroy {
} }
} }
public selectOrg(org: Org.AsObject): void {
this.org = org;
}
public selectRoles(roles: string[]): void { public selectRoles(roles: string[]): void {
this.rolesKeyList = roles; this.rolesKeyList = roles;
} }

View File

@ -15,6 +15,7 @@ import { InputModule } from 'src/app/modules/input/input.module';
import { ProjectRolesTableModule } from 'src/app/modules/project-roles-table/project-roles-table.module'; import { ProjectRolesTableModule } from 'src/app/modules/project-roles-table/project-roles-table.module';
import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module'; import { HasRolePipeModule } from 'src/app/pipes/has-role-pipe/has-role-pipe.module';
import { SearchOrgAutocompleteModule } from 'src/app/modules/search-org-autocomplete/search-org-autocomplete.module';
import { ProjectGrantCreateRoutingModule } from './project-grant-create-routing.module'; import { ProjectGrantCreateRoutingModule } from './project-grant-create-routing.module';
import { ProjectGrantCreateComponent } from './project-grant-create.component'; import { ProjectGrantCreateComponent } from './project-grant-create.component';
@ -38,6 +39,7 @@ import { ProjectGrantCreateComponent } from './project-grant-create.component';
MatProgressSpinnerModule, MatProgressSpinnerModule,
FormsModule, FormsModule,
TranslateModule, TranslateModule,
SearchOrgAutocompleteModule,
], ],
}) })
export default class ProjectGrantCreateModule {} export default class ProjectGrantCreateModule {}

View File

@ -17,6 +17,7 @@
class="cnsl-action-button" class="cnsl-action-button"
mat-raised-button mat-raised-button
[routerLink]="['/projects', projectId, 'projectgrants', 'create']" [routerLink]="['/projects', projectId, 'projectgrants', 'create']"
data-e2e="create-project-grant-button"
> >
<mat-icon class="icon">add</mat-icon> <mat-icon class="icon">add</mat-icon>
<span>{{ 'ACTIONS.NEW' | translate }}</span> <span>{{ 'ACTIONS.NEW' | translate }}</span>

View File

@ -12,15 +12,15 @@
<div class="newrole"> <div class="newrole">
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ 'PROJECT.ROLE.KEY' | translate }}</cnsl-label> <cnsl-label>{{ 'PROJECT.ROLE.KEY' | translate }}</cnsl-label>
<input cnslInput formControlName="key" /> <input cnslInput formControlName="key" data-e2e="role-key-input" />
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ 'PROJECT.ROLE.DISPLAY_NAME' | translate }}</cnsl-label> <cnsl-label>{{ 'PROJECT.ROLE.DISPLAY_NAME' | translate }}</cnsl-label>
<input cnslInput formControlName="displayName" /> <input cnslInput formControlName="displayName" data-e2e="role-display-name-input" />
</cnsl-form-field> </cnsl-form-field>
<cnsl-form-field class="formfield"> <cnsl-form-field class="formfield">
<cnsl-label>{{ 'PROJECT.ROLE.GROUP' | translate }}</cnsl-label> <cnsl-label>{{ 'PROJECT.ROLE.GROUP' | translate }}</cnsl-label>
<input cnslInput formControlName="group" /> <input cnslInput formControlName="group" data-e2e="role-group-input" />
</cnsl-form-field> </cnsl-form-field>
</div> </div>
<button <button
@ -36,7 +36,14 @@
</ng-container> </ng-container>
</div> </div>
<button class="add-line-btn" color="primary" type="button" mat-stroked-button (click)="addEntry()"> <button
class="add-line-btn"
color="primary"
type="button"
data-e2e="new-project-role-button"
mat-stroked-button
(click)="addEntry()"
>
{{ 'PROJECT.ROLE.ADDNEWLINE' | translate }} {{ 'PROJECT.ROLE.ADDNEWLINE' | translate }}
</button> </button>

View File

@ -1502,12 +1502,11 @@
"SEL_PROJECT": "Търсене на проект", "SEL_PROJECT": "Търсене на проект",
"SEL_ROLES": "Изберете ролите, които искате да бъдат добавени към субсидията", "SEL_ROLES": "Изберете ролите, които искате да бъдат добавени към субсидията",
"SEL_USER": "Изберете потребители", "SEL_USER": "Изберете потребители",
"SEL_ORG": "Задайте домейна", "SEL_ORG": "Търсете организация",
"SEL_ORG_DESC": "Въведете пълния домейн, за да посочите организацията, която да предоставите.", "SEL_ORG_DESC": "Потърсете организацията за отпускане.",
"ORG_TITLE": "Организация",
"ORG_DESCRIPTION": "На път сте да предоставите потребител за организацията {{name}}.", "ORG_DESCRIPTION": "На път сте да предоставите потребител за организацията {{name}}.",
"ORG_DESCRIPTION_DESC": "Превключете контекста в заглавката по-горе, за да предоставите потребител за друга организация.", "ORG_DESCRIPTION_DESC": "Превключете контекста в заглавката по-горе, за да предоставите потребител за друга организация.",
"SEL_ORG_FORMFIELD": "Пълен домейн", "SEL_ORG_FORMFIELD": "Организация",
"SEL_ORG_BUTTON": "Организация на търсенето", "SEL_ORG_BUTTON": "Организация на търсенето",
"FOR_ORG": "Безвъзмездната помощ е създадена за:" "FOR_ORG": "Безвъзмездната помощ е създадена за:"
}, },

View File

@ -1508,13 +1508,11 @@
"SEL_ROLES": "Selektiere die gewünschten Rollen für das Erstellen der Berechtigung.", "SEL_ROLES": "Selektiere die gewünschten Rollen für das Erstellen der Berechtigung.",
"SEL_PROJECT": "Suchen nach Projekt", "SEL_PROJECT": "Suchen nach Projekt",
"SEL_USER": "Benutzer auswählen", "SEL_USER": "Benutzer auswählen",
"SEL_ORG": "Suchen nach Domain", "SEL_ORG": "Durchsuchen Sie eine Organisation",
"SEL_ORG_DESC": "Gebe die vollständige Domain ein, um Resultate zu erhalten.", "SEL_ORG_DESC": "Suchen Sie nach der zu gewährenden Organisation.",
"ORG_TITLE": "Organisation",
"ORG_DESCRIPTION": "Du bist im Begriff, einen Benutzer für die Organisation {{name}} zu berechtigen.", "ORG_DESCRIPTION": "Du bist im Begriff, einen Benutzer für die Organisation {{name}} zu berechtigen.",
"ORG_DESCRIPTION_DESC": "Wechsle den Kontext, um die Organisation zu wechseln.", "ORG_DESCRIPTION_DESC": "Wechsle den Kontext, um die Organisation zu wechseln.",
"SEL_ORG_FORMFIELD": "Vollständige Domain", "SEL_ORG_FORMFIELD": "Organisation",
"SEL_ORG_BUTTON": "Suche Organisation",
"FOR_ORG": "Die Berechtigung wird erstellt für:" "FOR_ORG": "Die Berechtigung wird erstellt für:"
}, },
"DETAIL": { "DETAIL": {

View File

@ -1501,7 +1501,7 @@
"GRANT": { "GRANT": {
"EMPTY": "No granted organization.", "EMPTY": "No granted organization.",
"TITLE": "Project Grants", "TITLE": "Project Grants",
"DESCRIPTION": "Allow an other organization to use your project.", "DESCRIPTION": "Allow another organization to use your project.",
"EDITTITLE": "Edit roles", "EDITTITLE": "Edit roles",
"CREATE": { "CREATE": {
"TITLE": "Create Organization Grant", "TITLE": "Create Organization Grant",
@ -1509,13 +1509,11 @@
"SEL_PROJECT": "Search for a project", "SEL_PROJECT": "Search for a project",
"SEL_ROLES": "Select the roles you want to be added to the grant", "SEL_ROLES": "Select the roles you want to be added to the grant",
"SEL_USER": "Select users", "SEL_USER": "Select users",
"SEL_ORG": "Set the domain", "SEL_ORG": "Search an organization",
"SEL_ORG_DESC": "Enter the complete domain to specify the organization to grant.", "SEL_ORG_DESC": "Search the organization to grant.",
"ORG_TITLE": "Organization",
"ORG_DESCRIPTION": "You are about to grant a user for the organization {{name}}.", "ORG_DESCRIPTION": "You are about to grant a user for the organization {{name}}.",
"ORG_DESCRIPTION_DESC": "Switch the context in the header above to grant a user for another organization.", "ORG_DESCRIPTION_DESC": "Switch the context in the header above to grant a user for another organization.",
"SEL_ORG_FORMFIELD": "Complete Domain", "SEL_ORG_FORMFIELD": "Organization",
"SEL_ORG_BUTTON": "Search Organization",
"FOR_ORG": "The grant is created for:" "FOR_ORG": "The grant is created for:"
}, },
"DETAIL": { "DETAIL": {

View File

@ -1509,13 +1509,11 @@
"SEL_PROJECT": "Buscar un proyecto", "SEL_PROJECT": "Buscar un proyecto",
"SEL_ROLES": "Selecciona los roles que quieres que se añadan a la concesión", "SEL_ROLES": "Selecciona los roles que quieres que se añadan a la concesión",
"SEL_USER": "Seleccionar usuarios", "SEL_USER": "Seleccionar usuarios",
"SEL_ORG": "Establecer el dominio", "SEL_ORG": "Buscar una organización",
"SEL_ORG_DESC": "Introduce el dominio completo para especificar la organización concesionaria.", "SEL_ORG_DESC": "Busca la organización concesionaria.",
"ORG_TITLE": "Organización",
"ORG_DESCRIPTION": "Estás a punto de conceder acceso a un usuario para la organización {{name}}.", "ORG_DESCRIPTION": "Estás a punto de conceder acceso a un usuario para la organización {{name}}.",
"ORG_DESCRIPTION_DESC": "Cambia el contexto en la cabecera superior para conceder acceso a un usuario para otra organización.", "ORG_DESCRIPTION_DESC": "Cambia el contexto en la cabecera superior para conceder acceso a un usuario para otra organización.",
"SEL_ORG_FORMFIELD": "Completar dominio", "SEL_ORG_FORMFIELD": "Organización",
"SEL_ORG_BUTTON": "Buscar organización",
"FOR_ORG": "La concesión se creó para:" "FOR_ORG": "La concesión se creó para:"
}, },
"DETAIL": { "DETAIL": {

View File

@ -1508,13 +1508,11 @@
"SEL_PROJECT": "Rechercher un projet", "SEL_PROJECT": "Rechercher un projet",
"SEL_ROLES": "Sélectionnez les rôles que vous souhaitez ajouter à l'autorisation.", "SEL_ROLES": "Sélectionnez les rôles que vous souhaitez ajouter à l'autorisation.",
"SEL_USER": "Sélectionnez les utilisateurs", "SEL_USER": "Sélectionnez les utilisateurs",
"SEL_ORG": "Définir le domaine", "SEL_ORG": "Rechercher une organisation",
"SEL_ORG_DESC": "Entrez le domaine complet pour spécifier l'organisation à accorder.", "SEL_ORG_DESC": "Rechercher l'organisme à accorder",
"ORG_TITLE": "Organisation",
"ORG_DESCRIPTION": "Vous êtes sur le point d'accorder un utilisateur pour l'organisation{{name}}.", "ORG_DESCRIPTION": "Vous êtes sur le point d'accorder un utilisateur pour l'organisation{{name}}.",
"ORG_DESCRIPTION_DESC": "Changez le contexte dans l'en-tête ci-dessus pour accorder un utilisateur pour une autre organisation.", "ORG_DESCRIPTION_DESC": "Changez le contexte dans l'en-tête ci-dessus pour accorder un utilisateur pour une autre organisation.",
"SEL_ORG_FORMFIELD": "Domaine complet", "SEL_ORG_FORMFIELD": "Organisation",
"SEL_ORG_BUTTON": "Rechercher une organisation",
"FOR_ORG": "L'autorisation est créée pour" "FOR_ORG": "L'autorisation est créée pour"
}, },
"DETAIL": { "DETAIL": {

View File

@ -1508,13 +1508,11 @@
"SEL_PROJECT": "Cerca un progetto", "SEL_PROJECT": "Cerca un progetto",
"SEL_ROLES": "Seleziona i ruoli che vuoi aggiungere", "SEL_ROLES": "Seleziona i ruoli che vuoi aggiungere",
"SEL_USER": "Seleziona utenti", "SEL_USER": "Seleziona utenti",
"SEL_ORG": "Impostare il dominio", "SEL_ORG": "Cerca un'organizzazione",
"SEL_ORG_DESC": "Inserisci il dominio completo per specificare l'organizzazione da concedere.", "SEL_ORG_DESC": "Cerca l'organizzazione da concedere.",
"ORG_TITLE": "Organizzazione",
"ORG_DESCRIPTION": "Stai per concedere un utente per l'organizzazione {{name}}.", "ORG_DESCRIPTION": "Stai per concedere un utente per l'organizzazione {{name}}.",
"ORG_DESCRIPTION_DESC": "Cambia il contesto nell'intestazione qui sopra per concedere un utente per un'altra organizzazione.", "ORG_DESCRIPTION_DESC": "Cambia il contesto nell'intestazione qui sopra per concedere un utente per un'altra organizzazione.",
"SEL_ORG_FORMFIELD": "Dominio completo", "SEL_ORG_FORMFIELD": "Organizzazione",
"SEL_ORG_BUTTON": "Ricerca organizzazione",
"FOR_ORG": "Org grant \u00e8 creato per:" "FOR_ORG": "Org grant \u00e8 creato per:"
}, },
"DETAIL": { "DETAIL": {

View File

@ -1504,13 +1504,11 @@
"SEL_PROJECT": "プロジェクトを検索する", "SEL_PROJECT": "プロジェクトを検索する",
"SEL_ROLES": "許可するロールを選択する", "SEL_ROLES": "許可するロールを選択する",
"SEL_USER": "ユーザーを選択する", "SEL_USER": "ユーザーを選択する",
"SEL_ORG": "ドメインを設定する", "SEL_ORG": "組織を検索する",
"SEL_ORG_DESC": "完全なドメインを入力して、アクセスを許可する組織を指定する。", "SEL_ORG_DESC": "付与する組織を検索する",
"ORG_TITLE": "組織",
"ORG_DESCRIPTION": "組織 {{name}} にユーザーをグラントします。", "ORG_DESCRIPTION": "組織 {{name}} にユーザーをグラントします。",
"ORG_DESCRIPTION_DESC": "上記のヘッダーのコンテキストを切り替えることで、別組織のユーザーにグラントできます。", "ORG_DESCRIPTION_DESC": "上記のヘッダーのコンテキストを切り替えることで、別組織のユーザーにグラントできます。",
"SEL_ORG_FORMFIELD": "完全なドメイン", "SEL_ORG_FORMFIELD": "組織",
"SEL_ORG_BUTTON": "組織を検索する",
"FOR_ORG": "グラントが以下に対して作成されます:" "FOR_ORG": "グラントが以下に対して作成されます:"
}, },
"DETAIL": { "DETAIL": {

View File

@ -1508,13 +1508,11 @@
"SEL_PROJECT": "Wyszukaj projekt", "SEL_PROJECT": "Wyszukaj projekt",
"SEL_ROLES": "Wybierz role, które mają zostać dodane do udzielenia ", "SEL_ROLES": "Wybierz role, które mają zostać dodane do udzielenia ",
"SEL_USER": "Wybierz użytkowników", "SEL_USER": "Wybierz użytkowników",
"SEL_ORG": "Ustaw domenę", "SEL_ORG": "Wyszukaj organizację",
"SEL_ORG_DESC": "Wprowadź pełną domenę, aby określić organizację, której chcesz udzielić dostępu.", "SEL_ORG_DESC": "Wyszukaj organizację, której chcesz przyznać.",
"ORG_TITLE": "Organizacja",
"ORG_DESCRIPTION": "Masz zamiar udzielić użytkownikowi dostęp dla organizacji {{name}}.", "ORG_DESCRIPTION": "Masz zamiar udzielić użytkownikowi dostęp dla organizacji {{name}}.",
"ORG_DESCRIPTION_DESC": "Przełącz kontekst w nagłówku powyżej, aby udzielić użytkownikowi dostępu dla innej organizacji.", "ORG_DESCRIPTION_DESC": "Przełącz kontekst w nagłówku powyżej, aby udzielić użytkownikowi dostępu dla innej organizacji.",
"SEL_ORG_FORMFIELD": "Pełna domena", "SEL_ORG_FORMFIELD": "Organizacja",
"SEL_ORG_BUTTON": "Wyszukaj organizację",
"FOR_ORG": "Dostęp udzielany:" "FOR_ORG": "Dostęp udzielany:"
}, },
"DETAIL": { "DETAIL": {

View File

@ -1507,13 +1507,11 @@
"SEL_PROJECT": "搜索项目", "SEL_PROJECT": "搜索项目",
"SEL_ROLES": "选择要添加到授权中的角色", "SEL_ROLES": "选择要添加到授权中的角色",
"SEL_USER": "选择一个或多个用户", "SEL_USER": "选择一个或多个用户",
"SEL_ORG": "选择一个组织", "SEL_ORG": "搜索组织",
"SEL_ORG_DESC": "输入完整的域以指定要授予的组织。", "SEL_ORG_DESC": "搜索要授予的组织",
"ORG_TITLE": "组织",
"ORG_DESCRIPTION": "您即将授予组织 {{name}} 的用户。", "ORG_DESCRIPTION": "您即将授予组织 {{name}} 的用户。",
"ORG_DESCRIPTION_DESC": "切换上面标题中的上下文以授予另一个组织的用户。", "ORG_DESCRIPTION_DESC": "切换上面标题中的上下文以授予另一个组织的用户。",
"SEL_ORG_FORMFIELD": "完整域名", "SEL_ORG_FORMFIELD": "组织",
"SEL_ORG_BUTTON": "搜索组织",
"FOR_ORG": "授予组织:" "FOR_ORG": "授予组织:"
}, },
"DETAIL": { "DETAIL": {

View File

@ -19,7 +19,11 @@ You would have to create roles for administration and your clients in this very
To create a project, navigate to your organization, then projects or directly via <https://{your_domain}.zitadel.cloud/ui/console/projects>, and then click the button to create a new project. To create a project, navigate to your organization, then projects or directly via <https://{your_domain}.zitadel.cloud/ui/console/projects>, and then click the button to create a new project.
<img alt="Empty Project" src="/docs/img/console_projects_empty.png" width="270px" /> <img
alt="Empty Project"
src="/docs/img/console_projects_empty.png"
width="270px"
/>
then enter your project name and continue. then enter your project name and continue.
@ -51,7 +55,7 @@ Organizations can then create authorizations for their users on their own. The p
<img src="/docs/img/guides/console/grantsmenu.png" alt="Grants" width="170px" /> <img src="/docs/img/guides/console/grantsmenu.png" alt="Grants" width="170px" />
2. Enter the domain of the organization you want to grant (go to the organization detail page if you can't remember it), hit the search button and continue. 2. Search the organization you want to grant using the auto complete input and continue.
3. Select some roles you would like to grant to the organization and confirm. 3. Select some roles you would like to grant to the organization and confirm.
4. You should now see the granted organization in the section **grants**. 4. You should now see the granted organization in the section **grants**.

View File

@ -1,19 +1,23 @@
import { Context } from 'support/commands'; import { Context } from 'support/commands';
import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects'; import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects';
import { ensureOrgExists } from 'support/api/orgs';
describe('projects', () => { describe('projects', () => {
beforeEach(() => { beforeEach(() => {
cy.context().as('ctx'); cy.context().as('ctx');
}); });
const defaultOrg = 'e2eorgnewdefault';
const testProjectNameCreate = 'e2eprojectcreate'; const testProjectNameCreate = 'e2eprojectcreate';
const testProjectNameDelete = 'e2eprojectdelete'; const testProjectNameDelete = 'e2eprojectdelete';
describe('add project', () => { describe('add project', () => {
beforeEach(`ensure it doesn't exist already`, () => { beforeEach(`ensure it doesn't exist already`, () => {
cy.get<Context>('@ctx').then((ctx) => { cy.get<Context>('@ctx').then((ctx) => {
ensureProjectDoesntExist(ctx.api, testProjectNameCreate); ensureOrgExists(ctx, defaultOrg).then(() => {
cy.visit(`/projects`); ensureProjectDoesntExist(ctx.api, testProjectNameCreate);
cy.visit(`/projects`);
});
}); });
}); });
@ -27,10 +31,49 @@ describe('projects', () => {
it('should configure a project to assert roles on authentication'); it('should configure a project to assert roles on authentication');
}); });
describe('create project grant', () => {
const testRoleName = 'e2eroleundertestname';
beforeEach('ensure it exists', () => {
cy.get<Context>('@ctx').then((ctx) => {
ensureProjectExists(ctx.api, testProjectNameCreate).as('projectId');
cy.get<number>('@projectId').then((projectId) => {
cy.visit(`/projects/${projectId}`);
});
});
});
it('should add a role', () => {
cy.get('[data-e2e="sidenav-element-roles"]').click();
cy.get('[data-e2e="add-new-role"]').click();
cy.get('[formcontrolname="key"]').type(testRoleName);
cy.get('[formcontrolname="displayName"]').type('e2eroleundertestdisplay');
cy.get('[formcontrolname="group"]').type('e2eroleundertestgroup');
cy.get('[data-e2e="save-button"]').click();
cy.shouldConfirmSuccess();
cy.contains('tr', testRoleName);
});
it('should add a project grant', () => {
const rowSelector = `tr:contains(${testRoleName})`;
cy.get('[data-e2e="sidenav-element-projectgrants"]').click();
cy.get('[data-e2e="create-project-grant-button"]').click();
cy.get('[data-e2e="add-org-input"]').type(defaultOrg);
cy.get('mat-option').contains(defaultOrg).click();
cy.get('button').should('be.enabled');
cy.get('[data-e2e="project-grant-continue"]').first().click();
cy.get(rowSelector).find('input').click({ force: true });
cy.get('[data-e2e="save-project-grant-button"]').click();
cy.contains('tr', defaultOrg);
cy.contains('tr', testRoleName);
});
});
describe('edit project', () => { describe('edit project', () => {
beforeEach('ensure it exists', () => { beforeEach('ensure it exists', () => {
cy.get<Context>('@ctx').then((ctx) => { cy.get<Context>('@ctx').then((ctx) => {
ensureProjectExists(ctx.api, testProjectNameDelete); ensureProjectExists(ctx.api, testProjectNameDelete).as('projectId');
cy.visit(`/projects`); cy.visit(`/projects`);
}); });
}); });