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

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">
<h1>{{ 'PROJECT.GRANT.CREATE.SEL_ORG' | translate }}</h1>
<p>{{ 'PROJECT.GRANT.CREATE.SEL_ORG_DESC' | translate }}</p>
<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>
<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>
</ng-container>
<ng-container *ngIf="currentCreateStep === 2">
@ -37,7 +25,15 @@
<div class="btn-container">
<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 }}
</button>
</ng-container>
@ -46,7 +42,15 @@
<button (click)="previous()" mat-stroked-button class="small-button">
{{ 'ACTIONS.BACK' | translate }}
</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 }}
</button>
</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 {
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 { 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 { ProjectGrantCreateComponent } from './project-grant-create.component';
@ -38,6 +39,7 @@ import { ProjectGrantCreateComponent } from './project-grant-create.component';
MatProgressSpinnerModule,
FormsModule,
TranslateModule,
SearchOrgAutocompleteModule,
],
})
export default class ProjectGrantCreateModule {}

View File

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

View File

@ -12,15 +12,15 @@
<div class="newrole">
<cnsl-form-field class="formfield">
<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 class="formfield">
<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 class="formfield">
<cnsl-label>{{ 'PROJECT.ROLE.GROUP' | translate }}</cnsl-label>
<input cnslInput formControlName="group" />
<input cnslInput formControlName="group" data-e2e="role-group-input" />
</cnsl-form-field>
</div>
<button
@ -36,7 +36,14 @@
</ng-container>
</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 }}
</button>

View File

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

View File

@ -1508,13 +1508,11 @@
"SEL_ROLES": "Selektiere die gewünschten Rollen für das Erstellen der Berechtigung.",
"SEL_PROJECT": "Suchen nach Projekt",
"SEL_USER": "Benutzer auswählen",
"SEL_ORG": "Suchen nach Domain",
"SEL_ORG_DESC": "Gebe die vollständige Domain ein, um Resultate zu erhalten.",
"ORG_TITLE": "Organisation",
"SEL_ORG": "Durchsuchen Sie eine Organisation",
"SEL_ORG_DESC": "Suchen Sie nach der zu gewährenden Organisation.",
"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.",
"SEL_ORG_FORMFIELD": "Vollständige Domain",
"SEL_ORG_BUTTON": "Suche Organisation",
"SEL_ORG_FORMFIELD": "Organisation",
"FOR_ORG": "Die Berechtigung wird erstellt für:"
},
"DETAIL": {

View File

@ -1501,7 +1501,7 @@
"GRANT": {
"EMPTY": "No granted organization.",
"TITLE": "Project Grants",
"DESCRIPTION": "Allow an other organization to use your project.",
"DESCRIPTION": "Allow another organization to use your project.",
"EDITTITLE": "Edit roles",
"CREATE": {
"TITLE": "Create Organization Grant",
@ -1509,13 +1509,11 @@
"SEL_PROJECT": "Search for a project",
"SEL_ROLES": "Select the roles you want to be added to the grant",
"SEL_USER": "Select users",
"SEL_ORG": "Set the domain",
"SEL_ORG_DESC": "Enter the complete domain to specify the organization to grant.",
"ORG_TITLE": "Organization",
"SEL_ORG": "Search an organization",
"SEL_ORG_DESC": "Search the organization to grant.",
"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.",
"SEL_ORG_FORMFIELD": "Complete Domain",
"SEL_ORG_BUTTON": "Search Organization",
"SEL_ORG_FORMFIELD": "Organization",
"FOR_ORG": "The grant is created for:"
},
"DETAIL": {

View File

@ -1509,13 +1509,11 @@
"SEL_PROJECT": "Buscar un proyecto",
"SEL_ROLES": "Selecciona los roles que quieres que se añadan a la concesión",
"SEL_USER": "Seleccionar usuarios",
"SEL_ORG": "Establecer el dominio",
"SEL_ORG_DESC": "Introduce el dominio completo para especificar la organización concesionaria.",
"ORG_TITLE": "Organización",
"SEL_ORG": "Buscar una organización",
"SEL_ORG_DESC": "Busca la organización concesionaria.",
"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.",
"SEL_ORG_FORMFIELD": "Completar dominio",
"SEL_ORG_BUTTON": "Buscar organización",
"SEL_ORG_FORMFIELD": "Organización",
"FOR_ORG": "La concesión se creó para:"
},
"DETAIL": {

View File

@ -1508,13 +1508,11 @@
"SEL_PROJECT": "Rechercher un projet",
"SEL_ROLES": "Sélectionnez les rôles que vous souhaitez ajouter à l'autorisation.",
"SEL_USER": "Sélectionnez les utilisateurs",
"SEL_ORG": "Définir le domaine",
"SEL_ORG_DESC": "Entrez le domaine complet pour spécifier l'organisation à accorder.",
"ORG_TITLE": "Organisation",
"SEL_ORG": "Rechercher une organisation",
"SEL_ORG_DESC": "Rechercher l'organisme à accorder",
"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.",
"SEL_ORG_FORMFIELD": "Domaine complet",
"SEL_ORG_BUTTON": "Rechercher une organisation",
"SEL_ORG_FORMFIELD": "Organisation",
"FOR_ORG": "L'autorisation est créée pour"
},
"DETAIL": {

View File

@ -1508,13 +1508,11 @@
"SEL_PROJECT": "Cerca un progetto",
"SEL_ROLES": "Seleziona i ruoli che vuoi aggiungere",
"SEL_USER": "Seleziona utenti",
"SEL_ORG": "Impostare il dominio",
"SEL_ORG_DESC": "Inserisci il dominio completo per specificare l'organizzazione da concedere.",
"ORG_TITLE": "Organizzazione",
"SEL_ORG": "Cerca un'organizzazione",
"SEL_ORG_DESC": "Cerca l'organizzazione da concedere.",
"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.",
"SEL_ORG_FORMFIELD": "Dominio completo",
"SEL_ORG_BUTTON": "Ricerca organizzazione",
"SEL_ORG_FORMFIELD": "Organizzazione",
"FOR_ORG": "Org grant \u00e8 creato per:"
},
"DETAIL": {

View File

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

View File

@ -1508,13 +1508,11 @@
"SEL_PROJECT": "Wyszukaj projekt",
"SEL_ROLES": "Wybierz role, które mają zostać dodane do udzielenia ",
"SEL_USER": "Wybierz użytkowników",
"SEL_ORG": "Ustaw domenę",
"SEL_ORG_DESC": "Wprowadź pełną domenę, aby określić organizację, której chcesz udzielić dostępu.",
"ORG_TITLE": "Organizacja",
"SEL_ORG": "Wyszukaj organizację",
"SEL_ORG_DESC": "Wyszukaj organizację, której chcesz przyznać.",
"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.",
"SEL_ORG_FORMFIELD": "Pełna domena",
"SEL_ORG_BUTTON": "Wyszukaj organizację",
"SEL_ORG_FORMFIELD": "Organizacja",
"FOR_ORG": "Dostęp udzielany:"
},
"DETAIL": {

View File

@ -1507,13 +1507,11 @@
"SEL_PROJECT": "搜索项目",
"SEL_ROLES": "选择要添加到授权中的角色",
"SEL_USER": "选择一个或多个用户",
"SEL_ORG": "选择一个组织",
"SEL_ORG_DESC": "输入完整的域以指定要授予的组织。",
"ORG_TITLE": "组织",
"SEL_ORG": "搜索组织",
"SEL_ORG_DESC": "搜索要授予的组织",
"ORG_DESCRIPTION": "您即将授予组织 {{name}} 的用户。",
"ORG_DESCRIPTION_DESC": "切换上面标题中的上下文以授予另一个组织的用户。",
"SEL_ORG_FORMFIELD": "完整域名",
"SEL_ORG_BUTTON": "搜索组织",
"SEL_ORG_FORMFIELD": "组织",
"FOR_ORG": "授予组织:"
},
"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.
<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.
@ -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" />
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.
4. You should now see the granted organization in the section **grants**.

View File

@ -1,19 +1,23 @@
import { Context } from 'support/commands';
import { ensureProjectDoesntExist, ensureProjectExists } from '../../support/api/projects';
import { ensureOrgExists } from 'support/api/orgs';
describe('projects', () => {
beforeEach(() => {
cy.context().as('ctx');
});
const defaultOrg = 'e2eorgnewdefault';
const testProjectNameCreate = 'e2eprojectcreate';
const testProjectNameDelete = 'e2eprojectdelete';
describe('add project', () => {
beforeEach(`ensure it doesn't exist already`, () => {
cy.get<Context>('@ctx').then((ctx) => {
ensureProjectDoesntExist(ctx.api, testProjectNameCreate);
cy.visit(`/projects`);
ensureOrgExists(ctx, defaultOrg).then(() => {
ensureProjectDoesntExist(ctx.api, testProjectNameCreate);
cy.visit(`/projects`);
});
});
});
@ -27,10 +31,49 @@ describe('projects', () => {
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', () => {
beforeEach('ensure it exists', () => {
cy.get<Context>('@ctx').then((ctx) => {
ensureProjectExists(ctx.api, testProjectNameDelete);
ensureProjectExists(ctx.api, testProjectNameDelete).as('projectId');
cy.visit(`/projects`);
});
});