Skip to content

Commit

Permalink
feat: apps portal sdks (#808)
Browse files Browse the repository at this point in the history
Signed-off-by: nirgur <nirgur@users.noreply.github.com>
Co-authored-by: Asaf Shen <asaf@descope.com>
  • Loading branch information
nirgur and asafshen authored Oct 1, 2024
1 parent 88cc3c7 commit 30b11b0
Show file tree
Hide file tree
Showing 43 changed files with 878 additions and 226 deletions.
13 changes: 13 additions & 0 deletions packages/sdks/angular-sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,19 @@ The widget lets you:
Example:
[My User Profile](./projects/demo-app/src/app/my-user-profile/my-user-profile.component.html)

#### ApplicationsPortal

The `ApplicationsPortal` lets you embed an applications portal component in your app and allows the logged-in user to open applications they are assigned to.

###### Usage

```angular2html
<applications-portal widgetId="applications-portal-widget" />
```

Example:
[My User Profile](./projects/demo-app/src/app/my-applications-portal/my-applications-portal.component.html)

## Code Example

You can find an example angular app in the [examples folder](./projects/demo-app).
Expand Down
1 change: 1 addition & 0 deletions packages/sdks/angular-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@descope/role-management-widget": "workspace:*",
"@descope/user-management-widget": "workspace:*",
"@descope/user-profile-widget": "workspace:*",
"@descope/applications-portal-widget": "workspace:*",
"@descope/web-component": "workspace:*",
"@descope/web-js-sdk": "workspace:*",
"@descope/core-js-sdk": "workspace:*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"@descope/role-management-widget",
"@descope/access-key-management-widget",
"@descope/audit-management-widget",
"@descope/user-profile-widget"
"@descope/user-profile-widget",
"@descope/applications-portal-widget"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ApplicationsPortalComponent } from './applications-portal.component';
import createSdk from '@descope/web-js-sdk';
import { DescopeAuthConfig } from '../../types/types';
import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core';
import mocked = jest.mocked;

jest.mock('@descope/web-js-sdk');
//Mock DescopeApplicationsPortalWidget
jest.mock('@descope/applications-portal-widget', () => {
return jest.fn(() => {
// Create a mock DOM element
return document.createElement('descope-applications-portal-widget');
});
});

describe('DescopeApplicationsPortalComponent', () => {
let component: ApplicationsPortalComponent;
let fixture: ComponentFixture<ApplicationsPortalComponent>;
let mockedCreateSdk: jest.Mock;
const onSessionTokenChangeSpy = jest.fn();
const onAuditChangeSpy = jest.fn();
const afterRequestHooksSpy = jest.fn();
const mockConfig: DescopeAuthConfig = {
projectId: 'someProject'
};

beforeEach(() => {
mockedCreateSdk = mocked(createSdk);

mockedCreateSdk.mockReturnValue({
onSessionTokenChange: onSessionTokenChangeSpy,
onAuditChange: onAuditChangeSpy,
httpClient: {
hooks: {
afterRequest: afterRequestHooksSpy
}
}
});

TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
DescopeAuthConfig,
{ provide: DescopeAuthConfig, useValue: mockConfig }
]
});

fixture = TestBed.createComponent(ApplicationsPortalComponent);
component = fixture.componentInstance;
component.projectId = '123';
component.widgetId = 'widget-1';
component.logout = new EventEmitter<CustomEvent>();
component.logger = {
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
debug: jest.fn()
};
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
const html: HTMLElement = fixture.nativeElement;
const webComponentHtml = html.querySelector(
'descope-applications-portal-widget'
);
expect(webComponentHtml).toBeDefined();
});

it('should correctly setup attributes based on inputs', () => {
const html: HTMLElement = fixture.nativeElement;
const webComponentHtml = html.querySelector(
'descope-applications-portal-widget'
)!;
expect(webComponentHtml.getAttribute('project-id')).toStrictEqual('123');
expect(webComponentHtml.getAttribute('widget-id')).toStrictEqual(
'widget-1'
);
expect(webComponentHtml.getAttribute('logger')).toBeDefined();
});

it('should emit logout when web component emits logout', () => {
const html: HTMLElement = fixture.nativeElement;
const webComponentHtml = html.querySelector(
'descope-applications-portal-widget'
)!;

const event = {
detail: 'logout'
};
component.logout.subscribe((e) => {
expect(afterRequestHooksSpy).toHaveBeenCalled();
expect(e.detail).toHaveBeenCalledWith(event.detail);
});
webComponentHtml.dispatchEvent(new CustomEvent('logout', event));
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {
Component,
ElementRef,
EventEmitter,
Input,
OnChanges,
OnInit,
Output
} from '@angular/core';
import DescopeApplicationsPortalWidget from '@descope/applications-portal-widget';
import { ILogger } from '@descope/web-component';
import { DescopeAuthConfig } from '../../types/types';

@Component({
selector: 'applications-portal',
standalone: true,
template: ''
})
export class ApplicationsPortalComponent implements OnInit, OnChanges {
projectId: string;
baseUrl?: string;
baseStaticUrl?: string;
@Input() widgetId: string;

@Input() theme: 'light' | 'dark' | 'os';
@Input() debug: boolean;
@Input() logger: ILogger;

@Output() logout: EventEmitter<CustomEvent> = new EventEmitter<CustomEvent>();

private readonly webComponent = new DescopeApplicationsPortalWidget();

constructor(
private elementRef: ElementRef,
descopeConfig: DescopeAuthConfig
) {
this.projectId = descopeConfig.projectId;
this.baseUrl = descopeConfig.baseUrl;
this.baseStaticUrl = descopeConfig.baseStaticUrl;
}

ngOnInit() {
this.setupWebComponent();
this.elementRef.nativeElement.appendChild(this.webComponent);
}

ngOnChanges(): void {
this.setupWebComponent();
}

private setupWebComponent() {
this.webComponent.setAttribute('project-id', this.projectId);
this.webComponent.setAttribute('widget-id', this.widgetId);
if (this.baseUrl) {
this.webComponent.setAttribute('base-url', this.baseUrl);
}
if (this.baseStaticUrl) {
this.webComponent.setAttribute('base-static-url', this.baseStaticUrl);
}
if (this.theme) {
this.webComponent.setAttribute('theme', this.theme);
}
if (this.debug) {
this.webComponent.setAttribute('debug', this.debug.toString());
}

if (this.logger) {
(this.webComponent as any).logger = this.logger;
}

if (this.logout) {
this.webComponent.addEventListener('logout', (e: Event) => {
this.logout?.emit(e as CustomEvent);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { RoleManagementComponent } from './components/role-management/role-manag
import { AccessKeyManagementComponent } from './components/access-key-management/access-key-management.component';
import { AuditManagementComponent } from './components/audit-management/audit-management.component';
import { UserProfileComponent } from './components/user-profile/user-profile.component';
import { ApplicationsPortalComponent } from './components/applications-portal/applications-portal.component';
import { DescopeAuthConfig } from './types/types';

@NgModule({
Expand All @@ -27,7 +28,8 @@ import { DescopeAuthConfig } from './types/types';
RoleManagementComponent,
AccessKeyManagementComponent,
AuditManagementComponent,
UserProfileComponent
UserProfileComponent,
ApplicationsPortalComponent
],
exports: [
DescopeComponent,
Expand All @@ -38,7 +40,8 @@ import { DescopeAuthConfig } from './types/types';
RoleManagementComponent,
AccessKeyManagementComponent,
AuditManagementComponent,
UserProfileComponent
UserProfileComponent,
ApplicationsPortalComponent
]
})
export class DescopeAuthModule {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ export * from './lib/components/role-management/role-management.component';
export * from './lib/components/access-key-management/access-key-management.component';
export * from './lib/components/audit-management/audit-management.component';
export * from './lib/components/user-profile/user-profile.component';
export * from './lib/components/applications-portal/applications-portal.component';
export * from './lib/types/types';
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ManageRolesComponent } from './manage-roles/manage-roles.component';
import { ManageAccessKeysComponent } from './manage-access-keys/manage-access-keys.component';
import { ManageAuditComponent } from './manage-audit/manage-audit.component';
import { MyUserProfileComponent } from './my-user-profile/my-user-profile.component';
import { MyApplicationsPortalComponent } from './my-applications-portal/my-applications-portal.component';

const routes: Routes = [
{
Expand All @@ -23,6 +24,7 @@ const routes: Routes = [
{ path: 'manage-access-keys', component: ManageAccessKeysComponent },
{ path: 'manage-audit', component: ManageAuditComponent },
{ path: 'my-user-profile', component: MyUserProfileComponent },
{ path: 'my-applications-portal', component: MyApplicationsPortalComponent },
{ path: '**', component: HomeComponent }
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { ManageRolesComponent } from './manage-roles/manage-roles.component';
import { ManageAccessKeysComponent } from './manage-access-keys/manage-access-keys.component';
import { ManageAuditComponent } from './manage-audit/manage-audit.component';
import { MyUserProfileComponent } from './my-user-profile/my-user-profile.component';
import { MyApplicationsPortalComponent } from './my-applications-portal/my-applications-portal.component';
import {
HttpClientModule,
provideHttpClient,
Expand All @@ -36,7 +37,8 @@ export function initializeApp(authService: DescopeAuthService) {
ManageRolesComponent,
ManageAccessKeysComponent,
ManageAuditComponent,
MyUserProfileComponent
MyUserProfileComponent,
MyApplicationsPortalComponent
],
imports: [
BrowserModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@ <h3>Hello {{ userName }}</h3>
<button class="action-button" (click)="myUserProfile()">
My User Profile
</button>
<button class="action-button" (click)="myApplicationsPortal()">
My Applications Portal
</button>
<button class="logout-button" (click)="logout()">LOGOUT</button>
</ng-container>
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,10 @@ export class HomeComponent implements OnInit {
.navigate(['/my-user-profile'])
.catch((err) => console.error(err));
}

myApplicationsPortal() {
this.router
.navigate(['/my-applications-portal'])
.catch((err) => console.error(err));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<applications-portal
[theme]="theme"
[debug]="debugMode"
widgetId="applications-portal-widget"
/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MyApplicationsPortalComponent } from './my-applications-portal.component';
import createSdk from '@descope/web-js-sdk';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { DescopeAuthConfig } from '../../../../angular-sdk/src/lib/types/types';
import mocked = jest.mocked;

jest.mock('@descope/web-js-sdk');

describe('MyApplicationsPortalComponent', () => {
let component: MyApplicationsPortalComponent;
let fixture: ComponentFixture<MyApplicationsPortalComponent>;

let mockedCreateSdk: jest.Mock;

beforeEach(() => {
mockedCreateSdk = mocked(createSdk);
mockedCreateSdk.mockReturnValue({});

TestBed.configureTestingModule({
schemas: [NO_ERRORS_SCHEMA],
declarations: [MyApplicationsPortalComponent],
providers: [
DescopeAuthConfig,
{ provide: DescopeAuthConfig, useValue: { projectId: 'test' } }
]
});
fixture = TestBed.createComponent(MyApplicationsPortalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Component } from '@angular/core';
import { environment } from '../../environments/environment';
import { Router } from '@angular/router';

@Component({
selector: 'app-my-applications-portal',
templateUrl: './my-applications-portal.component.html',
styleUrls: ['./my-applications-portal.scss']
})
export class MyApplicationsPortalComponent {
theme = (environment.descopeTheme as 'light' | 'dark' | 'os') ?? 'os';
debugMode = environment.descopeDebugMode ?? false;

constructor(private router: Router) {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
:host {
height: 100vh;
position: relative;
}
main {
border-radius: 10px;
margin: auto;
border: 1px solid lightgray;
padding: 20px;
max-width: 700px;
box-shadow:
13px 13px 20px #cbced1,
-13px -13px 20px #fff;
background: #ecf0f3;
position: relative;
top: 50%;
transform: translateY(-50%);
}
Loading

0 comments on commit 30b11b0

Please sign in to comment.