mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:47:24 +00:00
feat(console): change default organization (#5151)
Adds the possibility to change the default organization from the organization overview
This commit is contained in:
parent
6a97c3e233
commit
3696c1b2d9
@ -52,7 +52,12 @@
|
||||
<th mat-header-cell *matHeaderCellDef>
|
||||
{{ 'ORG.PAGES.NAME' | translate }}
|
||||
</th>
|
||||
<td mat-cell *matCellDef="let org" (click)="setAndNavigateToOrg(org)">{{ org.name }}</td>
|
||||
<td mat-cell *matCellDef="let org" (click)="setAndNavigateToOrg(org)">
|
||||
<span>{{ org.name }}</span
|
||||
><span *ngIf="defaultOrgId === org.id" class="state orgdefaultlabel">{{
|
||||
'ORG.PAGES.DEFAULTLABEL' | translate
|
||||
}}</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="state">
|
||||
@ -88,6 +93,17 @@
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="actions" stickyEnd>
|
||||
<th mat-header-cell *matHeaderCellDef class="user-tr-actions"></th>
|
||||
<td mat-cell *matCellDef="let org" class="user-tr-actions">
|
||||
<cnsl-table-actions [hasActions]="true">
|
||||
<button menuActions mat-menu-item (click)="setDefaultOrg(org)" data-e2e="set-default-button">
|
||||
{{ 'ORG.PAGES.SETASDEFAULT' | translate }}
|
||||
</button>
|
||||
</cnsl-table-actions>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||
</table>
|
||||
|
@ -9,6 +9,10 @@ td {
|
||||
}
|
||||
}
|
||||
|
||||
.orgdefaultlabel {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.cpy-button {
|
||||
visibility: visible;
|
||||
|
@ -11,6 +11,8 @@ import { GrpcAuthService } from 'src/app/services/grpc-auth.service';
|
||||
import { ToastService } from 'src/app/services/toast.service';
|
||||
|
||||
import { PaginatorComponent } from '../paginator/paginator.component';
|
||||
import { AdminService } from 'src/app/services/admin.service';
|
||||
import { ManagementService } from 'src/app/services/mgmt.service';
|
||||
|
||||
enum OrgListSearchKey {
|
||||
NAME = 'NAME',
|
||||
@ -30,7 +32,7 @@ export class OrgTableComponent {
|
||||
@ViewChild('input') public filter!: Input;
|
||||
|
||||
public dataSource: MatTableDataSource<Org.AsObject> = new MatTableDataSource<Org.AsObject>([]);
|
||||
public displayedColumns: string[] = ['name', 'state', 'primaryDomain', 'creationDate', 'changeDate'];
|
||||
public displayedColumns: string[] = ['name', 'state', 'primaryDomain', 'creationDate', 'changeDate', 'actions'];
|
||||
private loadingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
public loading$: Observable<boolean> = this.loadingSubject.asObservable();
|
||||
public activeOrg!: Org.AsObject;
|
||||
@ -50,10 +52,13 @@ export class OrgTableComponent {
|
||||
offset: 0,
|
||||
queries: [],
|
||||
});
|
||||
public defaultOrgId: string = '';
|
||||
private requestOrgsObservable$ = this.requestOrgs$.pipe(takeUntil(this.destroy$));
|
||||
|
||||
constructor(
|
||||
private authService: GrpcAuthService,
|
||||
private mgmtService: ManagementService,
|
||||
private adminService: AdminService,
|
||||
private router: Router,
|
||||
private toast: ToastService,
|
||||
private _liveAnnouncer: LiveAnnouncer,
|
||||
@ -65,6 +70,10 @@ export class OrgTableComponent {
|
||||
this.requestOrgsObservable$.pipe(switchMap((req) => this.loadOrgs(req))).subscribe((orgs) => {
|
||||
this.dataSource = new MatTableDataSource<Org.AsObject>(orgs);
|
||||
});
|
||||
|
||||
this.mgmtService.getIAM().then((iam) => {
|
||||
this.defaultOrgId = iam.defaultOrgId;
|
||||
});
|
||||
}
|
||||
|
||||
public loadOrgs(request: Request): Observable<Org.AsObject[]> {
|
||||
@ -111,6 +120,18 @@ export class OrgTableComponent {
|
||||
}
|
||||
}
|
||||
|
||||
public setDefaultOrg(org: Org.AsObject) {
|
||||
this.adminService
|
||||
.setDefaultOrg(org.id)
|
||||
.then(() => {
|
||||
this.toast.showInfo('ORG.PAGES.DEFAULTORGSET', true);
|
||||
this.defaultOrgId = org.id;
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toast.showError(error);
|
||||
});
|
||||
}
|
||||
|
||||
public applySearchQuery(searchQueries: OrgQuery[]): void {
|
||||
this.searchQueries = searchQueries;
|
||||
this.requestOrgs$.next({
|
||||
|
@ -20,6 +20,7 @@ import { InputModule } from '../input/input.module';
|
||||
import { PaginatorModule } from '../paginator/paginator.module';
|
||||
import { RefreshTableModule } from '../refresh-table/refresh-table.module';
|
||||
import { OrgTableComponent } from './org-table.component';
|
||||
import { TableActionsModule } from '../table-actions/table-actions.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [OrgTableComponent],
|
||||
@ -33,6 +34,7 @@ import { OrgTableComponent } from './org-table.component';
|
||||
TimestampToDatePipeModule,
|
||||
LocalizedDatePipeModule,
|
||||
MatSortModule,
|
||||
TableActionsModule,
|
||||
MatIconModule,
|
||||
PaginatorModule,
|
||||
HasRoleModule,
|
||||
|
@ -222,6 +222,8 @@ import {
|
||||
GetCustomPasswordChangeMessageTextRequest,
|
||||
AddNotificationPolicyRequest,
|
||||
AddNotificationPolicyResponse,
|
||||
SetDefaultOrgRequest,
|
||||
SetDefaultOrgResponse,
|
||||
} from '../proto/generated/zitadel/admin_pb';
|
||||
import { SearchQuery } from '../proto/generated/zitadel/member_pb';
|
||||
import { ListQuery } from '../proto/generated/zitadel/object_pb';
|
||||
@ -233,6 +235,13 @@ import { GrpcService } from './grpc.service';
|
||||
export class AdminService {
|
||||
constructor(private readonly grpcService: GrpcService) {}
|
||||
|
||||
public setDefaultOrg(orgId: string): Promise<SetDefaultOrgResponse.AsObject> {
|
||||
const req = new SetDefaultOrgRequest();
|
||||
req.setOrgId(orgId);
|
||||
|
||||
return this.grpcService.admin.setDefaultOrg(req, null).then((resp) => resp.toObject());
|
||||
}
|
||||
|
||||
public listEvents(req: ListEventsRequest): Promise<ListEventsResponse> {
|
||||
return this.grpcService.admin.listEvents(req, null).then((resp) => resp);
|
||||
}
|
||||
|
@ -843,6 +843,9 @@
|
||||
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Geben Sie den Namen der neuen Organisation ein.",
|
||||
"ORGDETAILUSER_TITLE": "Organisationsbesitzer hinzufügen",
|
||||
"DELETE": "Organisation löschen",
|
||||
"DEFAULTLABEL": "Standard",
|
||||
"SETASDEFAULT": "Als Standardorganisation festlegen",
|
||||
"DEFAULTORGSET": "Standardorganisation erfolgreich geändert",
|
||||
"RENAME": {
|
||||
"ACTION": "Umbenennen",
|
||||
"TITLE": "Organisation umbenennen",
|
||||
|
@ -843,6 +843,9 @@
|
||||
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Enter the name of your new organization.",
|
||||
"ORGDETAILUSER_TITLE": "Configure Organization Owner",
|
||||
"DELETE": "Delete organization",
|
||||
"DEFAULTLABEL": "Default",
|
||||
"SETASDEFAULT": "Set as default organization",
|
||||
"DEFAULTORGSET": "Default organization changed successfully",
|
||||
"RENAME": {
|
||||
"ACTION": "Rename",
|
||||
"TITLE": "Rename Organization",
|
||||
|
@ -843,6 +843,9 @@
|
||||
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Saisissez le nom de votre nouvelle organisation.",
|
||||
"ORGDETAILUSER_TITLE": "Configurer le propriétaire de l'organisation",
|
||||
"DELETE": "Supprimer l'organisation",
|
||||
"DEFAULTLABEL": "Défaut",
|
||||
"SETASDEFAULT": "Définir comme organisation par défaut",
|
||||
"DEFAULTORGSET": "L'organisation par défaut a été modifiée avec succès",
|
||||
"RENAME": {
|
||||
"ACTION": "Renommer",
|
||||
"TITLE": "Renommer l'organisation",
|
||||
|
@ -844,6 +844,9 @@
|
||||
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "Inserisci il nome della tua nuova organizzazione.",
|
||||
"ORGDETAILUSER_TITLE": "Configurare il proprietario dell'organizzazione",
|
||||
"DELETE": "Elimina organizzazione",
|
||||
"DEFAULTLABEL": "Standard",
|
||||
"SETASDEFAULT": "Imposta come organizzazione predefinita",
|
||||
"DEFAULTORGSET": "Organizzazione predefinita cambiata con successo",
|
||||
"RENAME": {
|
||||
"ACTION": "Rinomina",
|
||||
"TITLE": "Rinomina organizzazione",
|
||||
|
@ -843,6 +843,9 @@
|
||||
"ORGDETAIL_TITLE_WITHOUT_DOMAIN": "输入新组织的名称。",
|
||||
"ORGDETAILUSER_TITLE": "配置组织所有者",
|
||||
"DELETE": "删除组织",
|
||||
"DEFAULTLABEL": "默认情况下",
|
||||
"SETASDEFAULT": "设置为默认组织",
|
||||
"DEFAULTORGSET": "默认的组织已经改变",
|
||||
"RENAME": {
|
||||
"ACTION": "改名",
|
||||
"TITLE": "重命名组织",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ensureOrgExists } from 'support/api/orgs';
|
||||
import { ensureOrgExists, ensureOrgIsDefault, isDefaultOrg } from 'support/api/orgs';
|
||||
import { apiAuth } from '../../support/api/apiauth';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
@ -33,6 +33,40 @@ describe('organizations', () => {
|
||||
});
|
||||
});
|
||||
|
||||
const orgOverviewPath = `/orgs`;
|
||||
const initialDefaultOrg = 'e2eorgolddefault';
|
||||
const orgNameForNewDefault = 'e2eorgnewdefault';
|
||||
|
||||
describe('set default org', () => {
|
||||
beforeEach(() => {
|
||||
apiAuth()
|
||||
.as('api')
|
||||
.then((api) => {
|
||||
ensureOrgExists(api, orgNameForNewDefault)
|
||||
.as('newDefaultOrgId')
|
||||
.then(() => {
|
||||
ensureOrgExists(api, initialDefaultOrg)
|
||||
.as('defaultOrg')
|
||||
.then((id) => {
|
||||
ensureOrgIsDefault(api, id)
|
||||
.as('orgWasDefault')
|
||||
.then(() => {
|
||||
cy.visit(`${orgOverviewPath}`).as('orgsite');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should rename the organization', function () {
|
||||
const rowSelector = `tr:contains(${orgNameForNewDefault})`;
|
||||
cy.get(rowSelector).find('[data-e2e="table-actions-button"]').click({ force: true });
|
||||
cy.get('[data-e2e="set-default-button"]', { timeout: 1000 }).should('be.visible').click();
|
||||
cy.shouldConfirmSuccess();
|
||||
isDefaultOrg(this.api, this.newDefaultOrgId);
|
||||
});
|
||||
});
|
||||
|
||||
it('should add an organization with the personal account as org owner');
|
||||
describe('changing the current organization', () => {
|
||||
it('should update displayed organization details');
|
||||
|
@ -2,6 +2,7 @@ import { ensureSomething } from './ensure';
|
||||
import { searchSomething } from './search';
|
||||
import { API } from './types';
|
||||
import { host } from '../login/users';
|
||||
import { requestHeaders } from './apiauth';
|
||||
|
||||
export function ensureOrgExists(api: API, name: string): Cypress.Chainable<number> {
|
||||
return ensureSomething(
|
||||
@ -23,6 +24,51 @@ export function ensureOrgExists(api: API, name: string): Cypress.Chainable<numbe
|
||||
);
|
||||
}
|
||||
|
||||
export function isDefaultOrg(api: API, orgId: number): Cypress.Chainable<boolean> {
|
||||
console.log('huhu', orgId);
|
||||
return cy
|
||||
.request({
|
||||
method: 'GET',
|
||||
url: encodeURI(`${api.mgmtBaseURL}/iam`),
|
||||
headers: requestHeaders(api, orgId),
|
||||
})
|
||||
.then((res) => {
|
||||
const { defaultOrgId } = res.body;
|
||||
expect(defaultOrgId).to.equal(orgId);
|
||||
return defaultOrgId === orgId;
|
||||
});
|
||||
}
|
||||
|
||||
export function ensureOrgIsDefault(api: API, orgId: number): Cypress.Chainable<boolean> {
|
||||
return cy
|
||||
.request({
|
||||
method: 'GET',
|
||||
url: encodeURI(`${api.mgmtBaseURL}/iam`),
|
||||
headers: requestHeaders(api, orgId),
|
||||
})
|
||||
.then((res) => {
|
||||
return res.body;
|
||||
})
|
||||
.then(({ defaultOrgId }) => {
|
||||
if (defaultOrgId === orgId) {
|
||||
return true;
|
||||
} else {
|
||||
return cy
|
||||
.request({
|
||||
method: 'PUT',
|
||||
url: `${api.adminBaseURL}/orgs/default/${orgId}`,
|
||||
headers: requestHeaders(api, orgId),
|
||||
failOnStatusCode: true,
|
||||
followRedirect: false,
|
||||
})
|
||||
.then((cRes) => {
|
||||
expect(cRes.status).to.equal(200);
|
||||
return !!cRes.body;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function getOrgUnderTest(api: API): Cypress.Chainable<number> {
|
||||
return searchSomething(api, `${api.mgmtBaseURL}/orgs/me`, 'GET', (res) => {
|
||||
return { entity: res.org, id: res.org.id, sequence: parseInt(<string>res.org.details.sequence) };
|
||||
|
Loading…
x
Reference in New Issue
Block a user