diff --git a/apps/datahub/src/app/app.module.ts b/apps/datahub/src/app/app.module.ts index 0c69562559..ec34f60ce3 100644 --- a/apps/datahub/src/app/app.module.ts +++ b/apps/datahub/src/app/app.module.ts @@ -41,6 +41,7 @@ import { UiLayoutModule, } from '@geonetwork-ui/ui/layout' import { UiSearchModule } from '@geonetwork-ui/ui/search' +import { IgnApiDlComponent } from '@geonetwork-ui/feature/record' import { getGlobalConfig, getOptionalSearchConfig, @@ -95,6 +96,7 @@ import { UiWidgetsModule } from '@geonetwork-ui/ui/widgets' import { RecordUserFeedbacksComponent } from './record/record-user-feedbacks/record-user-feedbacks.component' import { LetDirective } from '@ngrx/component' import { OrganizationPageComponent } from './organization/organization-page/organization-page.component' +import { MatButtonToggleModule } from '@angular/material/button-toggle' export const metaReducers: MetaReducer[] = !environment.production ? [] : [] @@ -168,6 +170,7 @@ export const metaReducers: MetaReducer[] = !environment.production ? [] : [] BlockListComponent, PreviousNextButtonsComponent, LetDirective, + MatButtonToggleModule, ], providers: [ importProvidersFrom(FeatureAuthModule), diff --git a/apps/datahub/src/app/record/record-apis/record-apis.component.html b/apps/datahub/src/app/record/record-apis/record-apis.component.html index a4bf38449f..30cdc4d61d 100644 --- a/apps/datahub/src/app/record/record-apis/record-apis.component.html +++ b/apps/datahub/src/app/record/record-apis/record-apis.component.html @@ -36,9 +36,20 @@
-

+

record.metadata.api.form.title

+

+ record.metadata.api.form.title.gpf +

+ +
diff --git a/apps/datahub/src/app/record/record-apis/record-apis.component.spec.ts b/apps/datahub/src/app/record/record-apis/record-apis.component.spec.ts index 699b7e7b08..994c4ceb0e 100644 --- a/apps/datahub/src/app/record/record-apis/record-apis.component.spec.ts +++ b/apps/datahub/src/app/record/record-apis/record-apis.component.spec.ts @@ -50,7 +50,7 @@ describe('RecordApisComponent', () => { expect(component.selectedApiLink).toEqual(serviceDistributionMock) }) it('should update maxHeight for transition', () => { - expect(component.maxHeight).toEqual('500px') + expect(component.maxHeight).toEqual('700px') }) it('should update opacity for transition', () => { expect(component.opacity).toEqual(1) diff --git a/apps/datahub/src/app/record/record-apis/record-apis.component.ts b/apps/datahub/src/app/record/record-apis/record-apis.component.ts index c89732aff9..72c1970d3e 100644 --- a/apps/datahub/src/app/record/record-apis/record-apis.component.ts +++ b/apps/datahub/src/app/record/record-apis/record-apis.component.ts @@ -20,6 +20,7 @@ export class RecordApisComponent implements OnInit { maxHeight = '0px' opacity = 0 + displayApiIgnForm: boolean selectedApiLink: DatasetServiceDistribution apiLinks$ = this.facade.apiLinks$ @@ -51,6 +52,8 @@ export class RecordApisComponent implements OnInit { } openRecordApiForm(link: DatasetServiceDistribution) { + this.displayApiIgnForm = + link.accessServiceProtocol === 'GPFDL' ? true : false this.selectedApiLink = link this.setStyle(link) } @@ -61,7 +64,7 @@ export class RecordApisComponent implements OnInit { } setStyle(link: DatasetServiceDistribution) { - this.maxHeight = link === undefined ? '0px' : '500px' + this.maxHeight = link === undefined ? '0px' : '700px' this.opacity = link === undefined ? 0 : 1 } diff --git a/libs/api/metadata-converter/src/lib/common/distribution.mapper.ts b/libs/api/metadata-converter/src/lib/common/distribution.mapper.ts index e5d66e3b66..172fc2190c 100644 --- a/libs/api/metadata-converter/src/lib/common/distribution.mapper.ts +++ b/libs/api/metadata-converter/src/lib/common/distribution.mapper.ts @@ -7,6 +7,7 @@ export function matchProtocol(protocol: string): ServiceProtocol { if (/wps/i.test(protocol)) return 'wps' if (/ogc\W*api\W*features/i.test(protocol)) return 'ogcFeatures' if (/esri/i.test(protocol)) return 'esriRest' + if (/DOWNLOAD-1/i.test(protocol)) return 'GPFDL' return 'other' } diff --git a/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts b/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts index cdf02b3482..656ee891c3 100644 --- a/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts +++ b/libs/api/metadata-converter/src/lib/gn4/gn4.field.mapper.ts @@ -393,7 +393,8 @@ export class Gn4FieldMapper { /^OGC:WMS/.test(protocol) || /^OGC:WFS/.test(protocol) || /^OGC:WMTS/.test(protocol) || - /ogc\W*api\W*features/i.test(protocol) + /ogc\W*api\W*features/i.test(protocol) || + /^WWW:DOWNLOAD-/.test(protocol) ) { return 'service' } diff --git a/libs/common/domain/src/lib/model/record/metadata.model.ts b/libs/common/domain/src/lib/model/record/metadata.model.ts index 0589e96b7c..7952d917a0 100644 --- a/libs/common/domain/src/lib/model/record/metadata.model.ts +++ b/libs/common/domain/src/lib/model/record/metadata.model.ts @@ -113,6 +113,7 @@ export type ServiceProtocol = | 'wmts' | 'esriRest' | 'ogcFeatures' + | 'GPFDL' | 'other' export type OnlineResourceType = 'service' | 'download' | 'link' | 'endpoint' diff --git a/libs/feature/record/src/index.ts b/libs/feature/record/src/index.ts index 5cd2bfe857..e9f1785563 100644 --- a/libs/feature/record/src/index.ts +++ b/libs/feature/record/src/index.ts @@ -7,3 +7,4 @@ export * from './lib/data-view-share/data-view-share.component' export * from './lib/data-view-web-component/data-view-web-component.component' export * from './lib/external-viewer-button/external-viewer-button.component' export * from './lib/map-view/map-view.component' +export * from './lib/ign-api-dl/ign-api-dl.component' diff --git a/libs/feature/record/src/lib/feature-record.module.ts b/libs/feature/record/src/lib/feature-record.module.ts index e0488ff889..be19442097 100644 --- a/libs/feature/record/src/lib/feature-record.module.ts +++ b/libs/feature/record/src/lib/feature-record.module.ts @@ -15,6 +15,8 @@ import { METADATA_VIEW_FEATURE_STATE_KEY, reducer, } from './state/mdview.reducer' +import { IgnApiDlComponent } from './ign-api-dl/ign-api-dl.component' +import { IgnApiProduitComponent } from './ign-api-produit/ign-api-produit.component' import { MatTabsModule } from '@angular/material/tabs' import { MatIconModule } from '@angular/material/icon' import { PopupAlertComponent, UiWidgetsModule } from '@geonetwork-ui/ui/widgets' @@ -35,6 +37,8 @@ import { DataViewShareComponent } from './data-view-share/data-view-share.compon DataViewPermalinkComponent, DataViewWebComponentComponent, DataViewShareComponent, + IgnApiDlComponent, + IgnApiProduitComponent, ], imports: [ CommonModule, @@ -61,6 +65,7 @@ import { DataViewShareComponent } from './data-view-share/data-view-share.compon DataViewPermalinkComponent, DataViewWebComponentComponent, DataViewShareComponent, + IgnApiDlComponent, ], }) export class FeatureRecordModule {} diff --git a/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.css b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.css new file mode 100644 index 0000000000..0aa4ba7d0b --- /dev/null +++ b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.css @@ -0,0 +1,22 @@ +:host ::ng-deep input { + color: black; + opacity: 1; +} + +:host ::ng-deep gn-ui-copy-text-button input[type='text'] { + color: black; + background-color: white; +} + +:host ::ng-deep gn-ui-copy-text-button button, +host ::ng-deep gn-ui-copy-text-button button:hover { + background-color: var(--color-secondary) !important; +} + +:host ::ng-deep gn-ui-copy-text-button button mat-icon { + color: white !important; + opacity: 1 !important; +} +:host ::ng-deep gn-ui-copy-text-button button:hover mat-icon { + color: lightgrey !important; +} diff --git a/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.html b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.html new file mode 100644 index 0000000000..3dc41b5f21 --- /dev/null +++ b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.html @@ -0,0 +1,108 @@ +
+
+
+
+ record.metadata.api.form.create +
+ +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+
+ + +
+ +
+
+
+
+ {{ page$.value }}/{{ pageMax$ | async }} +
+
+
+ +
+
+ + +
+
+
+
+
diff --git a/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.spec.ts b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.spec.ts new file mode 100644 index 0000000000..78d9cb64a6 --- /dev/null +++ b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.spec.ts @@ -0,0 +1,150 @@ +import { TestBed, ComponentFixture } from '@angular/core/testing' + +import { DatasetServiceDistribution } from '@geonetwork-ui/common/domain/model/record' +import { firstValueFrom } from 'rxjs' +import { Choice, UiInputsModule } from '@geonetwork-ui/ui/inputs' +import { TranslateModule } from '@ngx-translate/core' +import { IgnApiDlComponent } from './ign-api-dl.component' +import { HttpClientTestingModule } from '@angular/common/http/testing' +import exp from 'constants' + +const mockDatasetServiceDistribution: DatasetServiceDistribution = { + url: new URL('https://api.example.com/data'), + type: 'service', + accessServiceProtocol: 'GPFDL', +} + +describe('IgnApiDlComponent', () => { + let component: IgnApiDlComponent + let fixture: ComponentFixture + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [IgnApiDlComponent], + imports: [ + UiInputsModule, + HttpClientTestingModule, + TranslateModule.forRoot(), + ], + }).compileComponents() + + fixture = TestBed.createComponent(IgnApiDlComponent) + component = fixture.componentInstance + component.apiLink = mockDatasetServiceDistribution + }) + + it('should create', () => { + expect(component).toBeTruthy() + }) + describe('When panel is opened', () => { + it('should set the links and initial values correctly', async () => { + expect(component.apiBaseUrl).toBe('https://api.example.com/data') + expect(component.format$.getValue()).toBe('') + expect(component.zone$.getValue()).toBe('') + expect(component.crs$.getValue()).toBe('') + const url = await firstValueFrom(component.apiQueryUrl$) + expect(url).toBe('https://api.example.com/data?pageSize=200&page=0') + //expect(component.apiLink.accessServiceProtocol).toBe('GPFDL') + }) + }) + describe('When URL params are changed', () => { + describe('When Format changed', () => { + const bucketFormat: Choice[] = [ + { value: 'SHP', label: 'SHP' }, + { value: 'json', label: 'json' }, + { value: 'SQL', label: 'SQL' }, + ] + const mockFormat = 'SHP' + const mockBadFormat = 'notAFormat' + it('should be a correct format', async () => { + jest.spyOn(component, 'resetPage') + component.bucketPromisesFormat = bucketFormat + component.setFormat(mockFormat) + expect(component.format$.getValue()).toBe(mockFormat) + expect(component.resetPage).toHaveBeenCalled() + }) + + it('should not be a correct format', async () => { + component.bucketPromisesFormat = bucketFormat + component.setFormat(mockBadFormat) + expect(component.format$.getValue()).toBe('') + }) + }) + describe('When CRS changed', () => { + const bucketCRS: Choice[] = [ + { value: 'CRS12', label: 'CRS1' }, + { value: 'CRS2', label: 'CRS2' }, + { value: 'CRS3', label: 'CRS3' }, + ] + const mockCRS = 'CRS12' + const mockBadCRS = 'notACRS' + it('should be a correct CRS', async () => { + jest.spyOn(component, 'resetPage') + component.bucketPromisesCrs = bucketCRS + component.setCrs(mockCRS) + expect(component.crs$.getValue()).toBe(mockCRS) + expect(component.resetPage).toHaveBeenCalled() + }) + + it('should not be a correct CRS', async () => { + component.bucketPromisesCrs = bucketCRS + component.setCrs(mockBadCRS) + expect(component.crs$.getValue()).toBe('') + }) + }) + describe('When Zone changed', () => { + const bucketZone: Choice[] = [ + { value: 'D01', label: 'D01' }, + { value: 'D02', label: 'D02' }, + { value: 'D03', label: 'D03' }, + ] + const mockZone = 'D03' + const mockBadZone = 'notAZone' + it('should be a correct Zone', async () => { + jest.spyOn(component, 'resetPage') + component.bucketPromisesZone = bucketZone + component.setZone(mockZone) + expect(component.zone$.getValue()).toBe(mockZone) + expect(component.resetPage).toHaveBeenCalled() + }) + + it('should not be a correct Zone', async () => { + component.bucketPromisesZone = bucketZone + component.setZone(mockBadZone) + expect(component.zone$.getValue()).toBe('') + }) + }) + + describe('When EditionDate changed', () => { + const mockEditionDate = '2022-04-30' + const mockBadEditionDate = '88-88-88' + it('hould be a correct edition date', () => { + jest.spyOn(component, 'resetPage') + component.setEditionDate(mockEditionDate) + expect(component.editionDate$.getValue()).toBe(mockEditionDate) + expect(component.resetPage).toHaveBeenCalled() + }) + it('should not be a correct edition date', () => { + component.setEditionDate(mockBadEditionDate) + expect(component.editionDate$.getValue()).toBe('') + }) + }) + + describe('When Url is reset', () => { + it('Should reset zone, format, crs, page and size value', () => { + component.resetUrl() + expect(component.zone$.getValue()).toBe('null') + expect(component.format$.getValue()).toBe('null') + expect(component.crs$.getValue()).toBe('null') + expect(component.page$.getValue()).toBe('0') + }) + }) + + describe('When page is reset', () => { + it('Should reset page value', () => { + component.resetPage() + expect(component.page$.getValue()).toBe('0') + }) + }) + }) +}) diff --git a/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.ts b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.ts new file mode 100644 index 0000000000..a741abd5ec --- /dev/null +++ b/libs/feature/record/src/lib/ign-api-dl/ign-api-dl.component.ts @@ -0,0 +1,249 @@ +import { + ChangeDetectionStrategy, + Component, + Input, + OnInit, +} from '@angular/core' +import { DatasetServiceDistribution } from '@geonetwork-ui/common/domain/model/record' +import { BehaviorSubject, Observable, combineLatest, map, mergeMap } from 'rxjs' +import { HttpClient } from '@angular/common/http' +import { Choice } from '@geonetwork-ui/ui/inputs' +import axios from 'axios' + +export interface Label { + label: string +} + +export interface FormatProduit { + title: string + update: string + format: Array + zone: Array +} + +export interface FormatSortieProduit { + label: string + value: string | number +} +export interface ListUrl { + url: string +} + +export interface listChoice { + zone: Choice[] + format: Choice[] + editionDate: Choice[] + crs: Choice[] +} + +export interface TermBucket { + term: string + label: string | number +} + +export interface Field { + entry: Array + link: any +} + +@Component({ + selector: 'gn-ui-ign-api-dl', + templateUrl: './ign-api-dl.component.html', + styleUrls: ['./ign-api-dl.component.css'], + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class IgnApiDlComponent implements OnInit { + isOpen = false + collapsed = false + initialLimit = 50 + apiBaseUrl: string + editionDate$ = new BehaviorSubject('') + zone$ = new BehaviorSubject('') + format$ = new BehaviorSubject('') + crs$ = new BehaviorSubject('') + page$ = new BehaviorSubject(1) + // a passer en config + url = + 'https://data.geopf.fr/telechargement/capabilities?outputFormat=application/json' + choices: any + bucketPromisesZone: Choice[] + bucketPromisesFormat: Choice[] + bucketPromisesCrs: Choice[] + + constructor(protected http: HttpClient) {} + + @Input() set apiLink(value: DatasetServiceDistribution) { + this.apiBaseUrl = value ? value.url.href : undefined + } + + ngOnInit(): void { + this.bucketPromisesZone = [{ value: '', label: 'ZONE' }] + this.bucketPromisesFormat = [{ value: '', label: 'FORMAT' }] + this.bucketPromisesCrs = [{ value: '', label: 'CRS' }] + this.getFields() + } + + apiQueryUrl$ = combineLatest([ + this.zone$, + this.format$, + this.editionDate$, + this.crs$, + this.page$, + ]).pipe( + map(([zone, format, editionDate, crs, page]) => { + let outputUrl + if (this.apiBaseUrl) { + const url = new URL(this.apiBaseUrl) // initialisation de l'url avec l'url de base + const params = { + zone: zone, + format: format, + editionDate: editionDate, + crs: crs, + page: page, + } // initialisation des paramètres de filtres + for (const [key, value] of Object.entries(params)) { + if (value && value !== 'null') { + url.searchParams.set(key, String(value)) + } else { + url.searchParams.delete(key) + } + } + outputUrl = url.toString() + } else { + console.error('erreur apibaseUrl null') + } + return outputUrl + }) + // startWith(() => this.apiBaseUrl) + ) + + listFilteredProduct$ = this.apiQueryUrl$.pipe( + mergeMap((url) => { + console.log(url) + + return this.getFilteredProduct$(url).pipe( + map((response) => response['entry']) + // startWith([]) + ) + }) + ) + + pageMax$ = this.apiQueryUrl$.pipe( + mergeMap((url) => { + return this.getFilteredProduct$(url).pipe( + map((response) => + Math.ceil(response['totalentries'] / Number(this.initialLimit)) + ) + ) + }) + ) + + getFilteredProduct$(url): Observable { + return this.http.get(url) + } + + getLinkFormat(produit): string { + return produit['format'][0]['label'] + } + + setEditionDate(value: string) { + if (value.match(/[0-9]{4}-[0-1][0-9]-[0-3][0-9]/)) { + this.editionDate$.next(value) + this.resetPage() + } + } + + setZone(value: string) { + if (this.bucketPromisesZone.map((choice) => choice.value).includes(value)) { + this.zone$.next(value) + this.resetPage() + } + } + + setCrs(value: string) { + if (this.bucketPromisesCrs.map((choice) => choice.value).includes(value)) { + this.crs$.next(value) + this.resetPage() + } + } + + setFormat(value: string) { + if ( + this.bucketPromisesFormat.map((choice) => choice.value).includes(value) + ) { + this.format$.next(value) + this.resetPage() + } + } + + resetUrl() { + // this.offset$.next(DEFAULT_PARAMS.OFFSET) + this.zone$.next('null') + this.format$.next('null') + this.crs$.next('null') + this.page$.next(1) + } + moreResult(): void { + this.page$.next(this.page$.value + 1) + } + + lessResult(): void { + this.page$.next(this.page$.value - 1) + } + + resetPage(): void { + this.page$.next(1) + } + + async getCapabilities() { + let page = 0 + let choicesTest = undefined + let pageCount = 1 + + while (choicesTest === undefined && pageCount > page) { + const response = await axios.get( + this.url.concat(`&limit=200&page=${page}`) + ) + + choicesTest = response.data.entry.filter( + (element) => element['id'] == this.apiBaseUrl + )[0] + page += 1 + pageCount = response.data.pagecount + } + + return choicesTest + } + async getFields() { + this.choices = await this.getCapabilities() + + const tempZone = this.choices.zone.map((bucket) => ({ + value: bucket.label, + label: bucket.term, + })) + tempZone.sort((a, b) => (a.label > b.label ? 1 : -1)) + tempZone.unshift({ value: 'null', label: 'ZONE' }) + + this.bucketPromisesZone = tempZone + + const tempFormat = this.choices.format.map((bucket) => ({ + value: bucket.label, + label: bucket.term, + })) + tempFormat.sort((a, b) => (a.label > b.label ? 1 : -1)) + tempFormat.unshift({ value: 'null', label: 'FORMAT' }) + + this.bucketPromisesFormat = tempFormat + + const tempCrs = this.choices.category.map((bucket) => ({ + value: bucket.label, + label: bucket.label, + })) + tempCrs.sort((a, b) => (a.label > b.label ? 1 : -1)) + tempCrs.unshift({ value: 'null', label: 'CRS' }) + + this.bucketPromisesCrs = tempCrs + + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + } +} diff --git a/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.css b/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.css new file mode 100644 index 0000000000..e69de29bb2 diff --git a/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.html b/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.html new file mode 100644 index 0000000000..c413764ffa --- /dev/null +++ b/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.html @@ -0,0 +1,37 @@ + + + diff --git a/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.ts b/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.ts new file mode 100644 index 0000000000..e3ecbfc07f --- /dev/null +++ b/libs/feature/record/src/lib/ign-api-produit/ign-api-produit.component.ts @@ -0,0 +1,45 @@ +import { HttpClient, HttpErrorResponse } from '@angular/common/http' +import { + ChangeDetectionStrategy, + Component, + EventEmitter, + Input, + OnInit, + Output, +} from '@angular/core' +import { Observable, catchError, map, mergeMap, tap, throwError } from 'rxjs' + +@Component({ + selector: 'gn-ui-ign-api-produit', + templateUrl: './ign-api-produit.component.html', + styleUrls: ['./ign-api-produit.component.css'], +}) +export class IgnApiProduitComponent implements OnInit { + @Input() link + @Input() color: string + @Input() format: string + @Input() isFromWfs: boolean + + constructor(protected http: HttpClient) {} + liste$: Observable + + ngOnInit(): void { + this.liste$ = this.http + .get(this.link['id']) + .pipe(map((response) => response['entry'])) + } + + downloadListe(): void { + this.http + .get(this.link['id']) + .pipe( + map((response) => response['entry']), + mergeMap((response) => response) + ) + .subscribe((reponse) => this.download(reponse['id'])) + } + + download(url): void { + this.http.get(url).subscribe() + } +} diff --git a/libs/feature/record/src/lib/state/mdview.facade.ts b/libs/feature/record/src/lib/state/mdview.facade.ts index 02c869e5d5..faed694ccf 100644 --- a/libs/feature/record/src/lib/state/mdview.facade.ts +++ b/libs/feature/record/src/lib/state/mdview.facade.ts @@ -15,6 +15,7 @@ import { LinkClassifierService, LinkUsage } from '@geonetwork-ui/util/shared' import { DatavizConfigurationModel } from '@geonetwork-ui/common/domain/model/dataviz/dataviz-configuration.model' import { CatalogRecord, + DatasetServiceDistribution, UserFeedback, } from '@geonetwork-ui/common/domain/model/record' import { AvatarServiceInterface } from '@geonetwork-ui/api/repository' @@ -72,7 +73,14 @@ export class MdViewFacade { apiLinks$ = this.allLinks$.pipe( map((links) => - links.filter((link) => this.linkClassifier.hasUsage(link, LinkUsage.API)) + links + .filter((link) => this.linkClassifier.hasUsage(link, LinkUsage.API)) + .sort((dd1, dd2) => { + return (dd2 as DatasetServiceDistribution).accessServiceProtocol === + 'GPFDL' + ? 1 + : -1 + }) ) ) diff --git a/libs/ui/elements/src/lib/api-card/api-card.component.html b/libs/ui/elements/src/lib/api-card/api-card.component.html index c381e4fbf5..bc72720b1d 100644 --- a/libs/ui/elements/src/lib/api-card/api-card.component.html +++ b/libs/ui/elements/src/lib/api-card/api-card.component.html @@ -9,12 +9,23 @@
{{ link.accessServiceProtocol }} + + record.metadata.api.gpfdl