feat: add new marketplace-client component
MODIFY
src/app/app.module.ts
src/app/app.module.ts
@@ -3,7 +3,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { MarkdownModule } from 'ngx-markdown';
-import { FormsModule } from '@angular/forms';
+import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component';
@@ -32,6 +32,10 @@ import { MyAppsComponent } from './components/my-apps/my-apps.component';
import { ThemeService } from './services/theme/theme.service';
import { KeycloakAngularModule } from 'keycloak-angular';
import { KeycloakHelperService } from './services/keycloak-helper/keycloak-helper.service';
+import { MarketplaceClientAppComponent } from './components/marketplace-client-app/marketplace-client-app.component';
+import { MatSelectModule } from '@angular/material/select';
+import { MatTableModule } from '@angular/material/table';
+import { MatSortModule } from '@angular/material/sort';
import { MyAppsSummaryComponent } from './components/my-apps-summary/my-apps-summary.component';
import { MyAppsUseApiComponent } from './components/my-apps-use-api/my-apps-use-api.component';
import { MyAppsPoliciesComponent } from './components/my-apps-policies/my-apps-policies.component';
@@ -97,6 +101,7 @@ function initializeKeycloak(keycloakHelper: KeycloakHelperService) {
PlanCardListComponent,
AccountComponent,
MyAppsComponent,
+ MarketplaceClientAppComponent,
MyAppsSummaryComponent,
MyAppsUseApiComponent,
MyAppsPoliciesComponent,
@@ -119,6 +124,10 @@ function initializeKeycloak(keycloakHelper: KeycloakHelperService) {
deps: [HttpClient],
},
}),
+ MatSelectModule,
+ MatTableModule,
+ ReactiveFormsModule,
+ MatSortModule,
],
providers: [
{

ADD
src/app/components/marketplace-client-app/marketplace-client-app.component.html
+<h2>Create or Select an Application</h2>
+
+<div class="container">
+ <form>
+ <mat-form-field appearance="fill">
+ <mat-label>Application Name</mat-label>
+ <input
+ matInput
+ placeholder="My-Application"
+ [(ngModel)]="clientName"
+ name="input"
+ (keyup)="applyFilter($event)"
+ />
+ </mat-form-field>
+ </form>
+ <button
+ mat-raised-button
+ color="primary"
+ id="add-button"
+ (click)="addClient()"
+ >
+ Create
+ </button>
+</div>
+
+<table mat-table [dataSource]="dataSource" class="mat-elevation-z8">
+ <!-- Name Column -->
+ <ng-container matColumnDef="name">
+ <th mat-header-cell *matHeaderCellDef>Application Name</th>
+ <td mat-cell *matCellDef="let element">{{ element.name }}</td>
+ </ng-container>
+
+ <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
+ <tr
+ mat-row
+ *matRowDef="let row; columns: displayedColumns"
+ (click)="selectClient(row)"
+ [class.is-selected]="clickedRows.has(row)"
+ ></tr>
+</table>

ADD
src/app/components/marketplace-client-app/marketplace-client-app.component.scss
+.is-selected {
+ background: whitesmoke;
+ font-weight: bold;
+}
+
+.container {
+ display: grid;
+ justify-items: start;
+}
+
+table {
+ min-width: 50%;
+ margin-top: 20px;
+}
+
+
+//#add-button {
+// margin-left: 20px;
+//}

ADD
src/app/components/marketplace-client-app/marketplace-client-app.component.spec.ts
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { MarketplaceClientAppComponent } from './marketplace-client-app.component';
+
+describe('MarketplaceClientAppComponent', () => {
+ let component: MarketplaceClientAppComponent;
+ let fixture: ComponentFixture<MarketplaceClientAppComponent>;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ declarations: [MarketplaceClientAppComponent],
+ }).compileComponents();
+ });
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(MarketplaceClientAppComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});

ADD
src/app/components/marketplace-client-app/marketplace-client-app.component.ts
+import { Component, OnInit } from '@angular/core';
+import { BackendService, Client } from '../../services/backend/backend.service';
+import { MatTableDataSource } from '@angular/material/table';
+import { switchMap } from 'rxjs/operators';
+
+@Component({
+ selector: 'app-marketplace-client-app',
+ templateUrl: './marketplace-client-app.component.html',
+ styleUrls: ['./marketplace-client-app.component.scss'],
+})
+export class MarketplaceClientAppComponent implements OnInit {
+ displayedColumns: string[] = ['name'];
+ dataSource = new MatTableDataSource<Client>([]);
+ clickedRows = new Set<Client>();
+ clientName = '';
+
+ constructor(private backend: BackendService) {}
+
+ ngOnInit(): void {
+ this.loadClients();
+ }
+
+ public selectClient(client: Client): void {
+ // always clear, because at the moment we only allow one client to be selected
+ this.clickedRows.clear();
+ this.clickedRows.add(client);
+ }
+
+ /**
+ * Add a new client
+ */
+ public addClient(): void {
+ this.backend
+ .getClientOrgs()
+ .pipe(
+ switchMap((orgs) => {
+ return this.backend.createClient(orgs[0].id, this.clientName);
+ })
+ )
+ .subscribe(
+ () => this.loadClients(),
+ (error) => console.error(error)
+ );
+ }
+
+ /**
+ * Refresh list of clients
+ * @private
+ */
+ private loadClients() {
+ this.backend.getClients().subscribe(
+ (clients: Client[]) => {
+ this.dataSource = new MatTableDataSource(clients);
+ },
+ (error) => {
+ console.error(error);
+ }
+ );
+ }
+
+ public applyFilter(event: Event) {
+ // https://material.angular.io/components/table/examples
+ const filterValue = (event.target as HTMLInputElement).value;
+ this.dataSource.filter = filterValue.trim().toLowerCase();
+ }
+}

MODIFY
src/app/services/backend/backend.service.ts
src/app/services/backend/backend.service.ts
@@ -3,6 +3,19 @@ import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ConfigService } from '../config/config.service';
+import { KeycloakService } from 'keycloak-angular';
+
+/**
+ * Client
+ */
+export interface Client {
+ organizationId: string;
+ organizationName: string;
+ id: string;
+ name: string;
+ description: string;
+ numContracts: number;
+}
export interface ApiSummaryBean {
organizationId?: string;
@@ -54,6 +67,15 @@ export interface OrganizationBean {
modifiedOn?: string;
}
+export interface OrganizationSummaryBean {
+ id: string;
+ name: string;
+ description: string;
+ numClients: number;
+ numApis: number;
+ numMembers: number;
+}
+
export interface SearchResultsBeanApiSummaryBean {
beans: ApiSummaryBean[];
@@ -89,7 +111,11 @@ export interface PagingBean {
export class BackendService {
private readonly endpoint: string;
- constructor(private http: HttpClient, private configService: ConfigService) {
+ constructor(
+ private http: HttpClient,
+ private configService: ConfigService,
+ private keycloak: KeycloakService
+ ) {
this.endpoint = configService.getEndpoint();
}
@@ -142,4 +168,25 @@ export class BackendService {
})
);
}
+
+ public getClients() {
+ const username = this.keycloak.getKeycloakInstance().profile?.username;
+ const url = this.endpoint + `/users/${username}/editable-clients`;
+ return this.http.get(url) as Observable<Array<Client>>;
+ }
+
+ public getClientOrgs() {
+ const username = this.keycloak.getKeycloakInstance().profile?.username;
+ const url = this.endpoint + `/users/${username}/clientorgs`;
+ return this.http.get(url) as Observable<Array<OrganizationSummaryBean>>;
+ }
+
+ public createClient(orgId: string, clientName: string) {
+ const url = this.endpoint + `/organizations/${orgId}/clients`;
+ return this.http.post(url, {
+ name: clientName,
+ initialVersion: '1.0',
+ description: '',
+ }) as Observable<OrganizationBean>;
+ }
}

feat: add new marketplace-client component