diff --git a/gravitee-apim-console-webui/src/assets/integrations-banner.png b/gravitee-apim-console-webui/src/assets/integrations-banner.png new file mode 100644 index 00000000000..0b9c8902144 Binary files /dev/null and b/gravitee-apim-console-webui/src/assets/integrations-banner.png differ diff --git a/gravitee-apim-console-webui/src/components/gio-side-nav/gio-side-nav.component.ts b/gravitee-apim-console-webui/src/components/gio-side-nav/gio-side-nav.component.ts index b2fe9abc1cf..04b7124fb95 100644 --- a/gravitee-apim-console-webui/src/components/gio-side-nav/gio-side-nav.component.ts +++ b/gravitee-apim-console-webui/src/components/gio-side-nav/gio-side-nav.component.ts @@ -125,6 +125,12 @@ export class GioSideNavComponent implements OnInit, OnDestroy { displayName: 'APIs', category: 'Apis', }, + { + icon: 'gio:box', + routerLink: './integrations', + displayName: 'Integrations', + category: 'Integrations', + }, { icon: 'gio:multi-window', routerLink: './applications', diff --git a/gravitee-apim-console-webui/src/entities/integrations/integration.fixture.ts b/gravitee-apim-console-webui/src/entities/integrations/integration.fixture.ts new file mode 100644 index 00000000000..057cc19e9cb --- /dev/null +++ b/gravitee-apim-console-webui/src/entities/integrations/integration.fixture.ts @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Integration } from '../../management/integrations/integrations.model'; + +export function fakeIntegration(attribute?: Partial) { + const base: Integration = { + id: 'test_id', + name: 'test_name', + description: 'test_description', + provider: 'test_provider', + owner: 'test_owner', + status: 'test_status', + agent: 'test_agent', + }; + + return { + ...base, + ...attribute, + }; +} diff --git a/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.html b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.html new file mode 100644 index 00000000000..43c84f0d887 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.html @@ -0,0 +1,99 @@ + + +
+ + + + +
+
+

Create Integration

+
+ +
+ +
+
+ +
+
+ + +
+
General Information
+

Enter the general information for this new integration.

+
+
+ + + Name + Name is required + +
+
+ + + Description + {{ input.value.length }}/250 + +
+
+
+
+ +
+ + + +
+
+
+
+
diff --git a/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.scss b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.scss new file mode 100644 index 00000000000..4df1dd926dc --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.scss @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use 'sass:map'; +@use '@angular/material' as mat; +@use '@gravitee/ui-particles-angular' as gio; +@use '../../../scss/gio-layout' as gio-layout; + +$typography: map.get(gio.$mat-theme, typography); + +:host { + @include gio-layout.gio-responsive-content-container; +} + +.page-header { + margin-bottom: 24px; + + &__description { + color: mat.get-color-from-palette(gio.$mat-space-palette, 'lighter40'); + @include mat.typography-level($typography, body-2); + } +} + +.card-header { + display: flex; + justify-content: space-between; + padding-bottom: 24px; + + &__title { + display: flex; + flex-direction: column; + justify-content: center; + + h3 { + margin: 0; + } + } +} + +.form { + &__body { + display: flex; + justify-content: center; + padding: 24px 0; + + .mat-mdc-card { + width: 600px; + border: 1px solid var(--Dove-Darker10, #e7e8ef); + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2); + } + } + + &__actions { + display: flex; + justify-content: space-between; + padding-top: 24px; + } + + .form-field { + width: 100%; + } +} + +textarea { + resize: none; +} + +.info { + color: mat.get-color-from-palette(gio.$mat-space-palette, 'lighter40'); +} diff --git a/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.spec.ts b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.spec.ts new file mode 100644 index 00000000000..f7b43e8aec3 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.spec.ts @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { HttpTestingController } from '@angular/common/http/testing'; +import { InteractivityChecker } from '@angular/cdk/a11y'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; + +import { CreateIntegrationComponent } from './create-integration.component'; +import { CreateIntegrationHarness } from './create-integration.harness'; + +import { IntegrationsModule } from '../integrations.module'; +import { CONSTANTS_TESTING, GioTestingModule } from '../../../shared/testing'; + +describe('CreateIntegrationComponent', () => { + let fixture: ComponentFixture; + let componentHarness: CreateIntegrationHarness; + let httpTestingController: HttpTestingController; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [CreateIntegrationComponent], + imports: [GioTestingModule, IntegrationsModule, BrowserAnimationsModule, NoopAnimationsModule], + }) + .overrideProvider(InteractivityChecker, { + useValue: { + isFocusable: () => true, // This traps focus checks and so avoid warnings when dealing with + isTabbable: () => true, // This traps focus checks and so avoid warnings when dealing with + }, + }) + .compileComponents(); + }); + + beforeEach(async () => { + fixture = TestBed.createComponent(CreateIntegrationComponent); + httpTestingController = TestBed.inject(HttpTestingController); + componentHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, CreateIntegrationHarness); + fixture.detectChanges(); + }); + + afterEach(() => { + httpTestingController.verify(); + }); + + describe('form', () => { + it('should not submit empty form', async () => { + await componentHarness.clickOnSubmit(); + httpTestingController.expectNone(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations`); + }); + + it('should create integration with valid form', async () => { + await componentHarness.setName('TEST123'); + await componentHarness.clickOnSubmit(); + expectIntegrationPostRequest(); + }); + }); + + function expectIntegrationPostRequest(): void { + const req = httpTestingController.expectOne(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations`); + req.flush([]); + expect(req.request.method).toEqual('POST'); + } +}); diff --git a/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.ts b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.ts new file mode 100644 index 00000000000..588277a9c9b --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.component.ts @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, DestroyRef, inject } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormBuilder, Validators } from '@angular/forms'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { catchError, tap } from 'rxjs/operators'; +import { EMPTY } from 'rxjs'; + +import { CreateIntegrationPayload } from '../integrations.model'; +import { IntegrationsService } from '../../../services-ngx/integrations.service'; +import { SnackBarService } from '../../../services-ngx/snack-bar.service'; + +@Component({ + selector: 'app-create-integration', + templateUrl: './create-integration.component.html', + styleUrls: ['./create-integration.component.scss'], +}) +export class CreateIntegrationComponent { + public isLoading = false; + private destroyRef = inject(DestroyRef); + + public informationForm = this.formBuilder.group({ + name: ['', Validators.minLength(1)], + description: [''], + }); + + constructor( + private integrationsService: IntegrationsService, + private formBuilder: FormBuilder, + private readonly router: Router, + private activatedRoute: ActivatedRoute, + private snackBarService: SnackBarService, + ) {} + + public onSubmit(): void { + const payload: CreateIntegrationPayload = { + name: this.informationForm.controls.name.getRawValue(), + description: this.informationForm.controls.description.getRawValue(), + provider: 'AWS', + }; + + this.isLoading = true; + this.integrationsService + .createIntegration(payload) + .pipe( + tap(() => { + this.isLoading = false; + this.snackBarService.success(`Integration ${payload.name} created successfully`); + }), + catchError((_) => { + this.isLoading = false; + this.snackBarService.error('An error occurred. Integration not created'); + return EMPTY; + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe(() => { + this.isLoading = false; + this.router.navigate(['..'], { relativeTo: this.activatedRoute }); + }); + } +} diff --git a/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.harness.ts b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.harness.ts new file mode 100644 index 00000000000..2e2403f6538 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/create-integration/create-integration.harness.ts @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentHarness } from '@angular/cdk/testing'; +import { MatInputHarness } from '@angular/material/input/testing'; +import { MatButtonHarness } from '@angular/material/button/testing'; + +export class CreateIntegrationHarness extends ComponentHarness { + public static readonly hostSelector = 'app-create-integration'; + + private nameInputLocator = this.locatorFor(MatInputHarness.with({ selector: '[data-testid=create-integration-name-input]' })); + private submitButtonLocator = this.locatorFor(MatButtonHarness.with({ selector: '[data-testid=create-integration-submit-button]' })); + + public async setName(name: string) { + return this.nameInputLocator().then((input: MatInputHarness) => input.setValue(name)); + } + + public async clickOnSubmit() { + return this.submitButtonLocator().then(async (button: MatButtonHarness) => button.click()); + } +} diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations-routing.module.ts b/gravitee-apim-console-webui/src/management/integrations/integrations-routing.module.ts new file mode 100644 index 00000000000..c44bde98f0a --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations-routing.module.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; + +import { IntegrationsComponent } from './integrations.component'; +import { CreateIntegrationComponent } from './create-integration/create-integration.component'; + +const routes: Routes = [ + { + path: '', + component: IntegrationsComponent, + }, + { + path: 'create-integration', + component: CreateIntegrationComponent, + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class IntegrationsRoutingModule {} diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations.component.html b/gravitee-apim-console-webui/src/management/integrations/integrations.component.html new file mode 100644 index 00000000000..bc9630d5c09 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations.component.html @@ -0,0 +1,113 @@ + + +
+ + + +
+
+

Integrations

+
+
+ +
+
+ + +
+
+ +
+
+

No integrations yet

+

Create an integration to start importing APIs and event streams from a 3rd-party provider.

+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Table with Integrations +
Name{{ integration.name }}Owner{{ integration.owner || 'Not Implemented' }}Provider{{ integration.provider }}Agent{{ integration.agent || 'Not Implemented' }}Action + +
+ {{ 'Loading...' }} +
+
+
+
+
diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations.component.scss b/gravitee-apim-console-webui/src/management/integrations/integrations.component.scss new file mode 100644 index 00000000000..63b05a40de2 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations.component.scss @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use 'sass:map'; +@use '@angular/material' as mat; +@use '@gravitee/ui-particles-angular' as gio; +@use '../../scss/gio-layout' as gio-layout; + +$typography: map.get(gio.$mat-theme, typography); + +:host { + @include gio-layout.gio-responsive-content-container; +} + +.page-header { + margin-bottom: 24px; + + &__description { + color: mat.get-color-from-palette(gio.$mat-space-palette, 'lighter40'); + @include mat.typography-level($typography, body-2); + } +} + +.card-header { + display: flex; + justify-content: space-between; + padding-bottom: 24px; + + &__title { + display: flex; + flex-direction: column; + justify-content: center; + + h3 { + margin: 0; + } + } +} + +.no-integrations { + &__img { + display: flex; + justify-content: center; + padding: 36px 0 24px 0; + + .banner { + width: 327px; + } + } + + &__message { + display: flex; + flex-direction: column; + padding: 12px 0 22px 0; + + .header { + @include mat.typography-level($typography, 'headline-6'); + align-self: center; + } + + .description { + color: mat.get-color-from-palette(gio.$mat-space-palette, 'lighter40'); + @include mat.typography-level($typography, body-2); + width: 343px; + align-self: center; + text-align: center; + } + } +} diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations.component.spec.ts b/gravitee-apim-console-webui/src/management/integrations/integrations.component.spec.ts new file mode 100644 index 00000000000..df604cb33cd --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations.component.spec.ts @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { InteractivityChecker } from '@angular/cdk/a11y'; +import { HttpTestingController, TestRequest } from '@angular/common/http/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { BrowserAnimationsModule, NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TestElement } from '@angular/cdk/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { IntegrationsComponent } from './integrations.component'; +import { IntegrationsHarness } from './integrations.harness'; +import { Integration, IntegrationResponse } from './integrations.model'; +import { IntegrationsModule } from './integrations.module'; + +import { CONSTANTS_TESTING, GioTestingModule } from '../../shared/testing'; +import { fakeIntegration } from '../../entities/integrations/integration.fixture'; + +describe('IntegrationsComponent', () => { + let fixture: ComponentFixture; + let componentHarness: IntegrationsHarness; + let httpTestingController: HttpTestingController; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [IntegrationsComponent], + imports: [IntegrationsModule, GioTestingModule, BrowserAnimationsModule, NoopAnimationsModule, RouterTestingModule], + }) + .overrideProvider(InteractivityChecker, { + useValue: { + isFocusable: () => true, // This traps focus checks and so avoid warnings when dealing with + isTabbable: () => true, // This traps focus checks and so avoid warnings when dealing with + }, + }) + .compileComponents(); + }); + + beforeEach(async () => { + fixture = TestBed.createComponent(IntegrationsComponent); + httpTestingController = TestBed.inject(HttpTestingController); + componentHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, IntegrationsHarness); + fixture.componentInstance.filters = { + pagination: { index: 1, size: 10 }, + searchTerm: '', + }; + fixture.detectChanges(); + }); + + afterEach(() => { + httpTestingController.verify(); + }); + + describe('table', () => { + it('should display correct number of rows', async () => { + const fakeIntegrations: Integration[] = [fakeIntegration(), fakeIntegration(), fakeIntegration()]; + const fakeIntegrationResponse: IntegrationResponse = { + data: fakeIntegrations, + pagination: {}, + }; + + expectIntegrationGetRequest(fakeIntegrationResponse); + + const rows = await componentHarness.rowsNumber(); + expect(rows).toEqual(fakeIntegrations.length); + }); + }); + + describe('banner', () => { + it('should be visible when no integrations', async () => { + const fakeIntegrationResponse: IntegrationResponse = { + data: [], + pagination: {}, + }; + + expectIntegrationGetRequest(fakeIntegrationResponse); + + const banner: TestElement = await componentHarness.getBanner(); + expect(banner).toBeTruthy(); + }); + + it('should be hidden when integration are present', async () => { + const fakeIntegrationResponse: IntegrationResponse = { + data: [fakeIntegration()], + pagination: {}, + }; + + expectIntegrationGetRequest(fakeIntegrationResponse); + + const banner: TestElement = await componentHarness.getBanner(); + expect(banner).toBeFalsy(); + }); + }); + + function expectIntegrationGetRequest(fakeIntegrations: IntegrationResponse): void { + const req: TestRequest = httpTestingController.expectOne(`${CONSTANTS_TESTING.env.v2BaseURL}/integrations/?page=1&size=10`); + req.flush(fakeIntegrations); + expect(req.request.method).toEqual('GET'); + } +}); diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations.component.ts b/gravitee-apim-console-webui/src/management/integrations/integrations.component.ts new file mode 100644 index 00000000000..4d4a768896c --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations.component.ts @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, DestroyRef, inject, OnInit } from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { catchError, distinctUntilChanged, switchMap } from 'rxjs/operators'; +import { BehaviorSubject, EMPTY } from 'rxjs'; +import { isEqual } from 'lodash'; + +import { Integration, IntegrationResponse } from './integrations.model'; + +import { IntegrationsService } from '../../services-ngx/integrations.service'; +import { SnackBarService } from '../../services-ngx/snack-bar.service'; +import { GioTableWrapperFilters } from '../../shared/components/gio-table-wrapper/gio-table-wrapper.component'; + +@Component({ + selector: 'app-integrations', + templateUrl: './integrations.component.html', + styleUrls: ['./integrations.component.scss'], +}) +export class IntegrationsComponent implements OnInit { + private destroyRef: DestroyRef = inject(DestroyRef); + public isLoading: boolean = false; + public integrations: Integration[] = []; + public displayedColumns: string[] = ['name', 'owner', 'provider', 'agent', 'action']; + + public filters: GioTableWrapperFilters = { + pagination: { index: 1, size: 10 }, + searchTerm: '', + }; + public nbTotalInstances = this.integrations.length; + + private filters$ = new BehaviorSubject(this.filters); + + constructor( + private integrationsService: IntegrationsService, + private snackBarService: SnackBarService, + ) {} + + ngOnInit(): void { + // toDo: Pagination is waiting for backed + this.filters$ + .pipe( + distinctUntilChanged(isEqual), + switchMap((filters: GioTableWrapperFilters) => { + this.isLoading = true; + return this.integrationsService.getIntegrations(filters.pagination.index - 1, filters.pagination.size); + }), + catchError((_) => { + this.isLoading = false; + this.snackBarService.error('An error occurred'); + return EMPTY; + }), + takeUntilDestroyed(this.destroyRef), + ) + .subscribe((response: IntegrationResponse) => { + this.nbTotalInstances = response.pagination.totalCount; + this.integrations = response.data; + this.isLoading = false; + }); + } + + onFiltersChanged(filters: GioTableWrapperFilters) { + this.filters = { ...this.filters, ...filters }; + this.filters$.next(this.filters); + } +} diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations.harness.ts b/gravitee-apim-console-webui/src/management/integrations/integrations.harness.ts new file mode 100644 index 00000000000..02c71c3a321 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations.harness.ts @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentHarness, TestElement } from '@angular/cdk/testing'; +import { MatRowHarness, MatTableHarness } from '@angular/material/table/testing'; + +export class IntegrationsHarness extends ComponentHarness { + public static readonly hostSelector = 'app-integrations'; + + private getTable = this.locatorForOptional(MatTableHarness); + private getBannerLocator = this.locatorForOptional('.no-integrations'); + + public rowsNumber = async (): Promise => { + return this.getTable() + .then((table: MatTableHarness) => table.getRows()) + .then((rows: MatRowHarness[]) => rows.length); + }; + + public getBanner = async (): Promise => { + return await this.getBannerLocator(); + }; +} diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations.model.ts b/gravitee-apim-console-webui/src/management/integrations/integrations.model.ts new file mode 100644 index 00000000000..794a8496960 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations.model.ts @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Pagination } from '../../entities/management-api-v2'; + +export interface IntegrationResponse { + data: Integration[]; + pagination: Pagination; +} + +export interface Integration { + id: string; + name: string; + provider: string; + description: string; + owner?: string; + status?: string; + agent?: string; +} + +export interface CreateIntegrationPayload { + name: string; + description: string; + provider: string; +} diff --git a/gravitee-apim-console-webui/src/management/integrations/integrations.module.ts b/gravitee-apim-console-webui/src/management/integrations/integrations.module.ts new file mode 100644 index 00000000000..3d445e965d5 --- /dev/null +++ b/gravitee-apim-console-webui/src/management/integrations/integrations.module.ts @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { MatCard, MatCardContent, MatCardHeader } from '@angular/material/card'; +import { MatIcon } from '@angular/material/icon'; +import { MatButton, MatIconButton } from '@angular/material/button'; +import { MatError, MatFormField, MatHint, MatLabel } from '@angular/material/form-field'; +import { MatInput } from '@angular/material/input'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatTooltip } from '@angular/material/tooltip'; +import { MatCell, MatCellDef, MatColumnDef, MatHeaderCell, MatTableModule } from '@angular/material/table'; + +import { IntegrationsComponent } from './integrations.component'; +import { CreateIntegrationComponent } from './create-integration/create-integration.component'; +import { IntegrationsRoutingModule } from './integrations-routing.module'; + +import { GioTableWrapperModule } from '../../shared/components/gio-table-wrapper/gio-table-wrapper.module'; + +@NgModule({ + declarations: [IntegrationsComponent, CreateIntegrationComponent], + imports: [ + CommonModule, + ReactiveFormsModule, + MatCard, + MatCardHeader, + MatCardContent, + MatIcon, + MatButton, + MatError, + MatFormField, + MatHint, + MatInput, + MatLabel, + MatCell, + MatCellDef, + MatColumnDef, + MatHeaderCell, + MatIconButton, + MatTooltip, + MatTableModule, + GioTableWrapperModule, + IntegrationsRoutingModule, + ], +}) +export class IntegrationsModule {} diff --git a/gravitee-apim-console-webui/src/management/management-routing.module.ts b/gravitee-apim-console-webui/src/management/management-routing.module.ts index ee91f44873b..a2b65ce2781 100644 --- a/gravitee-apim-console-webui/src/management/management-routing.module.ts +++ b/gravitee-apim-console-webui/src/management/management-routing.module.ts @@ -47,6 +47,10 @@ const managementRoutes: Routes = [ path: 'apis', loadChildren: () => import('./api/apis.module').then((m) => m.ApisModule), }, + { + path: 'integrations', + loadChildren: () => import('./integrations/integrations.module').then((m) => m.IntegrationsModule), + }, { path: 'settings', loadChildren: () => import('./settings/settings.module').then((m) => m.SettingsModule), diff --git a/gravitee-apim-console-webui/src/services-ngx/integrations.service.spec.ts b/gravitee-apim-console-webui/src/services-ngx/integrations.service.spec.ts new file mode 100644 index 00000000000..3c4482fd576 --- /dev/null +++ b/gravitee-apim-console-webui/src/services-ngx/integrations.service.spec.ts @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TestBed } from '@angular/core/testing'; +import { HttpTestingController } from '@angular/common/http/testing'; + +import { IntegrationsService } from './integrations.service'; + +import { CONSTANTS_TESTING, GioTestingModule } from '../shared/testing'; +import { CreateIntegrationPayload, Integration } from '../management/integrations/integrations.model'; + +describe('IntegrationsService', () => { + const url = `${CONSTANTS_TESTING.env.v2BaseURL}/integrations`; + let service: IntegrationsService; + let httpTestingController: HttpTestingController; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [GioTestingModule], + }); + service = TestBed.inject(IntegrationsService); + + httpTestingController = TestBed.inject(HttpTestingController); + service = TestBed.inject(IntegrationsService); + }); + + afterEach(() => { + httpTestingController.verify(); + }); + + describe('get integrations', () => { + it('should call API', () => { + const fakeData: Integration[] = null; + + service.getIntegrations().subscribe((res) => { + expect(res).toMatchObject(fakeData); + }); + + httpTestingController.expectOne({ method: 'GET', url: url + '/?page=1&size=10' }).flush(fakeData); + }); + }); + + describe('create', () => { + it('should call API', (done) => { + const fakeData: CreateIntegrationPayload = { + name: 'test_name', + description: 'test_description', + provider: 'test_provider', + }; + + service.createIntegration(fakeData).subscribe(() => { + done(); + }); + + const req = httpTestingController.expectOne({ method: 'POST', url: url }); + req.flush(null); + expect(req.request.body).toEqual(fakeData); + }); + }); +}); diff --git a/gravitee-apim-console-webui/src/services-ngx/integrations.service.ts b/gravitee-apim-console-webui/src/services-ngx/integrations.service.ts new file mode 100644 index 00000000000..b7f80288e69 --- /dev/null +++ b/gravitee-apim-console-webui/src/services-ngx/integrations.service.ts @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Inject, Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { CreateIntegrationPayload, Integration, IntegrationResponse } from '../management/integrations/integrations.model'; +import { Constants } from '../entities/Constants'; + +@Injectable({ + providedIn: 'root', +}) +export class IntegrationsService { + private url: string = `${this.constants.env.v2BaseURL}/integrations`; + + constructor( + private readonly httpClient: HttpClient, + @Inject(Constants) private readonly constants: Constants, + ) {} + + getIntegrations(page?: number, size?: number): Observable { + // toDo: Pagination to check after backend ready: + return this.httpClient.get(`${this.url}/?page=${page || 1}&size=${size || 10}`); + } + + createIntegration(payload: CreateIntegrationPayload): Observable { + return this.httpClient.post(this.url, payload); + } +}