From dcde6d9adb80fa037364f9b5f98009ae601aebbd Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Mon, 2 Jun 2025 11:30:44 +0300
Subject: [PATCH 01/13] Add service health checker component
---
.../df-api-docs/df-api-docs.component.html | 22 +++++++
.../df-api-docs/df-api-docs.component.scss | 45 +++++++++++++
.../df-api-docs/df-api-docs.component.ts | 66 +++++++++++++++++--
src/assets/i18n/en.json | 6 ++
4 files changed, 132 insertions(+), 7 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index da274cff..a1a77205 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -32,4 +32,26 @@
+
+
+
{{ 'apiHealthBanner.loading' | transloco }}
+
+
+
{{ 'apiHealthBanner.healthy' | transloco }}
+
+
+
{{ 'apiHealthBanner.unhealthy' | transloco:{ healthError: healthError } }}
+
+
+
{{ 'apiHealthBanner.error' | transloco:{ healthError: healthError } }}
+
+
+
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
index 45708b99..548597e7 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
@@ -41,3 +41,48 @@
.swagger-ui {
margin-top: 16px;
}
+
+.api-health-banner {
+ padding: 8px 12px;
+ border-radius: 4px;
+ border-left-width: 4px;
+ border-left-style: solid;
+
+ p {
+ margin: 0;
+ font-size: 0.9em;
+ }
+
+ &.status-healthy {
+ border-left-color: #28a745; // Green
+ background-color: #e9f5ec; // Light green
+ color: #155724; // Darker green text
+ }
+
+ &.status-unhealthy,
+ &.status-error {
+ border-left-color: #dc3545; // Red
+ background-color: #f8d7da; // Light red
+ color: #721c24; // Darker red text
+ }
+
+ &.status-loading {
+ border-left-color: #007bff; // Blue
+ background-color: #e7f3ff; // Light blue
+ color: #004085; // Darker blue text
+ }
+}
+
+// Styles for elements within Swagger UI, piercing encapsulation
+:host ::ng-deep {
+ .swagger-ui {
+ // This targets the wrapper div for Swagger UI in your component's template
+ .information-container {
+ .main {
+ display: flex;
+ justify-content: space-between;
+ flex-direction: row-reverse;
+ }
+ }
+ }
+}
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 8d805e37..2638e994 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -25,7 +25,7 @@ import {
mapSnakeToCamel,
} from 'src/app/shared/utilities/case';
import { DfThemeService } from 'src/app/shared/services/df-theme.service';
-import { AsyncPipe, NgIf, NgFor, SlicePipe } from '@angular/common';
+import { AsyncPipe, NgIf, NgFor, SlicePipe, NgClass } from '@angular/common';
import { environment } from '../../../../environments/environment';
import { ApiKeysService } from '../services/api-keys.service';
import { ApiKeyInfo } from 'src/app/shared/types/api-keys';
@@ -34,10 +34,10 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faCopy } from '@fortawesome/free-solid-svg-icons';
import { DfCurrentServiceService } from 'src/app/shared/services/df-current-service.service';
-import { tap, switchMap, map, distinctUntilChanged } from 'rxjs/operators';
-import { HttpClient } from '@angular/common/http';
+import { tap, switchMap, map, distinctUntilChanged, catchError } from 'rxjs/operators';
+import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BASE_URL } from 'src/app/shared/constants/urls';
-import { Subscription } from 'rxjs';
+import { Subscription, of } from 'rxjs';
interface ServiceResponse {
resource: Array<{
@@ -63,6 +63,7 @@ interface ServiceResponse {
NgIf,
NgFor,
SlicePipe,
+ NgClass,
FontAwesomeModule,
],
})
@@ -70,11 +71,15 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
@ViewChild('apiDocumentation', { static: true }) apiDocElement:
| ElementRef
| undefined;
+ @ViewChild('healthBannerElement') healthBannerElementRef: ElementRef | undefined;
apiDocJson: object;
apiKeys: ApiKeyInfo[] = [];
faCopy = faCopy;
private subscriptions: Subscription[] = [];
+ healthStatus: 'loading' | 'healthy' | 'unhealthy' | 'error' = 'loading';
+ healthError: string | null = null;
+ serviceName: string | null = null;
constructor(
private activatedRoute: ActivatedRoute,
@@ -90,14 +95,14 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
isDarkMode = this.themeService.darkMode$;
ngOnInit(): void {
// Get the service name from the route
- const serviceName = this.activatedRoute.snapshot.params['name'];
+ this.serviceName = this.activatedRoute.snapshot.params['name'];
// First fetch the service ID by name
- if (serviceName) {
+ if (this.serviceName) {
this.subscriptions.push(
this.http
.get(
- `${BASE_URL}/system/service?filter=name=${serviceName}`
+ `${BASE_URL}/system/service?filter=name=${this.serviceName}`
)
.pipe(
map(response => response?.resource?.[0]?.id || -1),
@@ -109,6 +114,28 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
)
.subscribe()
);
+
+ // Perform health check
+ this.healthStatus = 'loading';
+ this.subscriptions.push(
+ this.http.get(`${BASE_URL}/${this.serviceName}/_schema`).pipe(
+ tap(() => {
+ this.healthStatus = 'healthy';
+ this.healthError = null;
+ }),
+ catchError((error: HttpErrorResponse) => {
+ if (error.status >= 200 && error.status < 300) {
+ // Even if there's an error in parsing, if it's 2xx, consider it healthy for schema check
+ this.healthStatus = 'healthy';
+ this.healthError = null;
+ } else {
+ this.healthStatus = 'unhealthy';
+ this.healthError = error.message || 'Unknown error';
+ }
+ return of(null); // Continue the stream
+ })
+ ).subscribe()
+ );
}
// Handle the API documentation
@@ -165,6 +192,31 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
return req;
},
showMutatedRequest: true,
+ onComplete: () => {
+ if (this.healthBannerElementRef && this.healthBannerElementRef.nativeElement && this.apiDocElement && this.apiDocElement.nativeElement) {
+ const swaggerContainer = this.apiDocElement.nativeElement;
+ const bannerNode = this.healthBannerElementRef.nativeElement;
+
+ // Try to find the information container within Swagger UI
+ const infoContainer = swaggerContainer.querySelector('.information-container .main');
+
+ if (infoContainer) {
+ // Prepend banner to the information container (which typically holds the title)
+ if (infoContainer.firstChild) {
+ infoContainer.insertBefore(bannerNode, infoContainer.firstChild);
+ } else {
+ infoContainer.appendChild(bannerNode);
+ }
+ } else {
+ // Fallback: Prepend to the main swagger container if .information-container is not found
+ if (swaggerContainer.firstChild) {
+ swaggerContainer.insertBefore(bannerNode, swaggerContainer.firstChild);
+ } else {
+ swaggerContainer.appendChild(bannerNode);
+ }
+ }
+ }
+ }
});
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index f8f4399c..244aaeda 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -117,6 +117,12 @@
"python3": "Python3",
"nodejs": "Node.js"
},
+ "apiHealthBanner": {
+ "loading": "Checking API status...",
+ "healthy": "API status: Healthy",
+ "unhealthy": "API status: Unhealthy. {{healthError}}",
+ "error": "Error checking API status. {{healthError}}"
+ },
"nav": {
"error": {
"header": "Error"
From 1f4588f1eeca8d9bc5c40d7f6052a3aa77017a41 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Mon, 2 Jun 2025 11:31:09 +0300
Subject: [PATCH 02/13] Used a prittier
---
.../df-api-docs/df-api-docs.component.html | 28 +++++---
.../df-api-docs/df-api-docs.component.ts | 65 ++++++++++++-------
2 files changed, 61 insertions(+), 32 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index a1a77205..1df757a1 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -32,14 +32,16 @@
-
+
{{ 'apiHealthBanner.loading' | transloco }}
@@ -47,10 +49,16 @@
{{ 'apiHealthBanner.healthy' | transloco }}
-
{{ 'apiHealthBanner.unhealthy' | transloco:{ healthError: healthError } }}
+
+ {{
+ 'apiHealthBanner.unhealthy' | transloco: { healthError: healthError }
+ }}
+
-
{{ 'apiHealthBanner.error' | transloco:{ healthError: healthError } }}
+
+ {{ 'apiHealthBanner.error' | transloco: { healthError: healthError } }}
+
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 2638e994..35325fc4 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -34,7 +34,13 @@ import { MatSnackBar } from '@angular/material/snack-bar';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faCopy } from '@fortawesome/free-solid-svg-icons';
import { DfCurrentServiceService } from 'src/app/shared/services/df-current-service.service';
-import { tap, switchMap, map, distinctUntilChanged, catchError } from 'rxjs/operators';
+import {
+ tap,
+ switchMap,
+ map,
+ distinctUntilChanged,
+ catchError,
+} from 'rxjs/operators';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BASE_URL } from 'src/app/shared/constants/urls';
import { Subscription, of } from 'rxjs';
@@ -71,7 +77,9 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
@ViewChild('apiDocumentation', { static: true }) apiDocElement:
| ElementRef
| undefined;
- @ViewChild('healthBannerElement') healthBannerElementRef: ElementRef | undefined;
+ @ViewChild('healthBannerElement') healthBannerElementRef:
+ | ElementRef
+ | undefined;
apiDocJson: object;
apiKeys: ApiKeyInfo[] = [];
@@ -118,23 +126,26 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
// Perform health check
this.healthStatus = 'loading';
this.subscriptions.push(
- this.http.get(`${BASE_URL}/${this.serviceName}/_schema`).pipe(
- tap(() => {
- this.healthStatus = 'healthy';
- this.healthError = null;
- }),
- catchError((error: HttpErrorResponse) => {
- if (error.status >= 200 && error.status < 300) {
- // Even if there's an error in parsing, if it's 2xx, consider it healthy for schema check
- this.healthStatus = 'healthy';
+ this.http
+ .get(`${BASE_URL}/${this.serviceName}/_schema`)
+ .pipe(
+ tap(() => {
+ this.healthStatus = 'healthy';
this.healthError = null;
- } else {
- this.healthStatus = 'unhealthy';
- this.healthError = error.message || 'Unknown error';
- }
- return of(null); // Continue the stream
- })
- ).subscribe()
+ }),
+ catchError((error: HttpErrorResponse) => {
+ if (error.status >= 200 && error.status < 300) {
+ // Even if there's an error in parsing, if it's 2xx, consider it healthy for schema check
+ this.healthStatus = 'healthy';
+ this.healthError = null;
+ } else {
+ this.healthStatus = 'unhealthy';
+ this.healthError = error.message || 'Unknown error';
+ }
+ return of(null); // Continue the stream
+ })
+ )
+ .subscribe()
);
}
@@ -193,12 +204,19 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
},
showMutatedRequest: true,
onComplete: () => {
- if (this.healthBannerElementRef && this.healthBannerElementRef.nativeElement && this.apiDocElement && this.apiDocElement.nativeElement) {
+ if (
+ this.healthBannerElementRef &&
+ this.healthBannerElementRef.nativeElement &&
+ this.apiDocElement &&
+ this.apiDocElement.nativeElement
+ ) {
const swaggerContainer = this.apiDocElement.nativeElement;
const bannerNode = this.healthBannerElementRef.nativeElement;
// Try to find the information container within Swagger UI
- const infoContainer = swaggerContainer.querySelector('.information-container .main');
+ const infoContainer = swaggerContainer.querySelector(
+ '.information-container .main'
+ );
if (infoContainer) {
// Prepend banner to the information container (which typically holds the title)
@@ -210,13 +228,16 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
} else {
// Fallback: Prepend to the main swagger container if .information-container is not found
if (swaggerContainer.firstChild) {
- swaggerContainer.insertBefore(bannerNode, swaggerContainer.firstChild);
+ swaggerContainer.insertBefore(
+ bannerNode,
+ swaggerContainer.firstChild
+ );
} else {
swaggerContainer.appendChild(bannerNode);
}
}
}
- }
+ },
});
}
From 8d5a2262717f64b600820015b5a8e31f3ece16ef Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Wed, 4 Jun 2025 16:37:14 +0300
Subject: [PATCH 03/13] Make a few improvements for health checker component
---
.../df-api-docs/df-api-docs.component.html | 16 ++--
.../df-api-docs/df-api-docs.component.scss | 72 ++++++++++++++---
.../df-api-docs/df-api-docs.component.ts | 81 ++++++++++++-------
src/assets/i18n/en.json | 6 +-
4 files changed, 128 insertions(+), 47 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index 1df757a1..6b3d7121 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -40,7 +40,7 @@
'status-loading': healthStatus === 'loading',
'status-healthy': healthStatus === 'healthy',
'status-unhealthy': healthStatus === 'unhealthy',
- 'status-error': healthStatus === 'error'
+ 'status-warning': healthStatus === 'warning'
}">
{{ 'apiHealthBanner.loading' | transloco }}
@@ -50,14 +50,18 @@
- {{
- 'apiHealthBanner.unhealthy' | transloco: { healthError: healthError }
- }}
+ {{ 'apiHealthBanner.unhealthyBase' | transloco }}
+
+
-
+
- {{ 'apiHealthBanner.error' | transloco: { healthError: healthError } }}
+ {{ 'apiHealthBanner.warningDefault' | transloco }}
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
index 548597e7..136f9269 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
@@ -43,6 +43,8 @@
}
.api-health-banner {
+ display: flex;
+ align-items: center;
padding: 8px 12px;
border-radius: 4px;
border-left-width: 4px;
@@ -54,22 +56,73 @@
}
&.status-healthy {
- border-left-color: #28a745; // Green
- background-color: #e9f5ec; // Light green
- color: #155724; // Darker green text
+ border-left-color: #28a745;
+ background-color: #e9f5ec;
+ color: #155724;
}
&.status-unhealthy,
&.status-error {
- border-left-color: #dc3545; // Red
- background-color: #f8d7da; // Light red
- color: #721c24; // Darker red text
+ border-left-color: #dc3545;
+ background-color: #f8d7da;
+ color: #721c24;
+ }
+
+ &.status-unhealthy {
+ & > div {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ width: 100%;
+
+ & > p {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+
+ .view-details-button {
+ margin-left: 12px;
+ flex-shrink: 0;
+ padding: 2px 8px;
+ line-height: normal;
+ font-size: 0.9em;
+ min-width: auto;
+ }
+ }
+
+ .unhealthy-error-details {
+ margin-top: 0;
+ padding: 8px 12px;
+ background-color: rgba(0, 0, 0, 0.03);
+ border: 1px solid rgba(0, 0, 0, 0.06);
+ border-radius: 4px;
+ width: 100%;
+ box-sizing: border-box;
+ max-height: 150px;
+ overflow-y: auto;
+
+ pre {
+ margin: 0;
+ white-space: pre-wrap;
+ word-break: break-word;
+ font-size: 0.85em;
+ color: inherit;
+ }
+ }
+ }
}
&.status-loading {
- border-left-color: #007bff; // Blue
- background-color: #e7f3ff; // Light blue
- color: #004085; // Darker blue text
+ border-left-color: #007bff;
+ background-color: #e7f3ff;
+ color: #004085;
+ }
+
+ &.status-warning {
+ border-left-color: #ffc107;
+ background-color: #fff3cd;
+ color: #856404;
}
}
@@ -82,6 +135,7 @@
display: flex;
justify-content: space-between;
flex-direction: row-reverse;
+ gap: 8px;
}
}
}
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 35325fc4..4112179f 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -53,6 +53,12 @@ interface ServiceResponse {
}>;
}
+interface ApiDocJson {
+ paths: {
+ [key: string]: any;
+ };
+}
+
@UntilDestroy({ checkProperties: true })
@Component({
selector: 'df-api-docs',
@@ -81,13 +87,14 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
| ElementRef
| undefined;
- apiDocJson: object;
+ apiDocJson: ApiDocJson;
apiKeys: ApiKeyInfo[] = [];
faCopy = faCopy;
private subscriptions: Subscription[] = [];
- healthStatus: 'loading' | 'healthy' | 'unhealthy' | 'error' = 'loading';
+ healthStatus: 'loading' | 'healthy' | 'unhealthy' | 'warning' = 'loading';
healthError: string | null = null;
serviceName: string | null = null;
+ showUnhealthyErrorDetails = false;
constructor(
private activatedRoute: ActivatedRoute,
@@ -122,31 +129,6 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
)
.subscribe()
);
-
- // Perform health check
- this.healthStatus = 'loading';
- this.subscriptions.push(
- this.http
- .get(`${BASE_URL}/${this.serviceName}/_schema`)
- .pipe(
- tap(() => {
- this.healthStatus = 'healthy';
- this.healthError = null;
- }),
- catchError((error: HttpErrorResponse) => {
- if (error.status >= 200 && error.status < 300) {
- // Even if there's an error in parsing, if it's 2xx, consider it healthy for schema check
- this.healthStatus = 'healthy';
- this.healthError = null;
- } else {
- this.healthStatus = 'unhealthy';
- this.healthError = error.message || 'Unknown error';
- }
- return of(null); // Continue the stream
- })
- )
- .subscribe()
- );
}
// Handle the API documentation
@@ -154,9 +136,7 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
this.activatedRoute.data.subscribe(({ data }) => {
if (data) {
if (
- data.paths['/']?.get &&
- data.paths['/']?.get.operationId &&
- data.paths['/']?.get.operationId === 'getSoapResources'
+ data.paths['/']?.get?.operationId === 'getSoapResources'
) {
this.apiDocJson = { ...data, paths: mapSnakeToCamel(data.paths) };
} else {
@@ -184,6 +164,9 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
ngAfterContentInit(): void {
const apiDocumentation = this.apiDocJson;
+
+ this.checkApiHealth();
+
SwaggerUI({
spec: apiDocumentation,
domNode: this.apiDocElement?.nativeElement,
@@ -246,6 +229,40 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
+ private checkApiHealth(): void {
+ if (this.serviceName && this.apiDocJson?.paths['/_schema']?.get) {
+ // Perform health check
+ this.performHealthCheck(this.serviceName, `${BASE_URL}/${this.serviceName}/_schema`);
+ } else {
+ this.setHealthState('warning');
+ }
+ }
+
+ private setHealthState(status: 'healthy' | 'unhealthy' | 'warning', error: string | null = null): void {
+ this.healthStatus = status;
+ this.healthError = error;
+ }
+
+ private performHealthCheck(serviceName: string | null, url: string): void {
+ this.healthStatus = 'loading';
+ this.healthError = null;
+
+ this.subscriptions.push(
+ this.http.get(url).pipe(
+ tap(() => this.setHealthState('healthy')),
+ catchError((error: HttpErrorResponse) => {
+ if (error.status >= 200 && error.status < 300) {
+ this.setHealthState('healthy');
+ return of(null);
+ }
+
+ this.setHealthState('unhealthy', error.message || 'Unknown error');
+ return of(null);
+ })
+ ).subscribe()
+ );
+ }
+
goBackToList(): void {
this.currentServiceService.clearCurrentServiceId();
this.router.navigate(['../'], { relativeTo: this.activatedRoute });
@@ -265,4 +282,8 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
duration: 3000,
});
}
+
+ toggleUnhealthyErrorDetails(): void {
+ this.showUnhealthyErrorDetails = !this.showUnhealthyErrorDetails;
+ }
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 244aaeda..41a28d60 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -120,8 +120,10 @@
"apiHealthBanner": {
"loading": "Checking API status...",
"healthy": "API status: Healthy",
- "unhealthy": "API status: Unhealthy. {{healthError}}",
- "error": "Error checking API status. {{healthError}}"
+ "unhealthyBase": "API status: Unhealthy.",
+ "viewDetails": "View Details",
+ "hideDetails": "Hide Details",
+ "warningDefault": "Warning: This type of API currently does not support automatic health checks."
},
"nav": {
"error": {
From 96040bd6e4de327fef4d5f00ec02e65576a2ef21 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Wed, 4 Jun 2025 16:38:08 +0300
Subject: [PATCH 04/13] used a prittier
---
.../df-api-docs/df-api-docs.component.html | 13 ++++++-
.../df-api-docs/df-api-docs.component.ts | 39 +++++++++++--------
2 files changed, 34 insertions(+), 18 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index 6b3d7121..6caa9b0d 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -51,8 +51,17 @@
{{ 'apiHealthBanner.unhealthyBase' | transloco }}
-
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 4112179f..9c7829b5 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -135,9 +135,7 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
this.subscriptions.push(
this.activatedRoute.data.subscribe(({ data }) => {
if (data) {
- if (
- data.paths['/']?.get?.operationId === 'getSoapResources'
- ) {
+ if (data.paths['/']?.get?.operationId === 'getSoapResources') {
this.apiDocJson = { ...data, paths: mapSnakeToCamel(data.paths) };
} else {
this.apiDocJson = { ...data, paths: mapCamelToSnake(data.paths) };
@@ -232,13 +230,19 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
private checkApiHealth(): void {
if (this.serviceName && this.apiDocJson?.paths['/_schema']?.get) {
// Perform health check
- this.performHealthCheck(this.serviceName, `${BASE_URL}/${this.serviceName}/_schema`);
+ this.performHealthCheck(
+ this.serviceName,
+ `${BASE_URL}/${this.serviceName}/_schema`
+ );
} else {
this.setHealthState('warning');
}
}
- private setHealthState(status: 'healthy' | 'unhealthy' | 'warning', error: string | null = null): void {
+ private setHealthState(
+ status: 'healthy' | 'unhealthy' | 'warning',
+ error: string | null = null
+ ): void {
this.healthStatus = status;
this.healthError = error;
}
@@ -248,18 +252,21 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
this.healthError = null;
this.subscriptions.push(
- this.http.get(url).pipe(
- tap(() => this.setHealthState('healthy')),
- catchError((error: HttpErrorResponse) => {
- if (error.status >= 200 && error.status < 300) {
- this.setHealthState('healthy');
- return of(null);
- }
+ this.http
+ .get(url)
+ .pipe(
+ tap(() => this.setHealthState('healthy')),
+ catchError((error: HttpErrorResponse) => {
+ if (error.status >= 200 && error.status < 300) {
+ this.setHealthState('healthy');
+ return of(null);
+ }
- this.setHealthState('unhealthy', error.message || 'Unknown error');
- return of(null);
- })
- ).subscribe()
+ this.setHealthState('unhealthy', error.message || 'Unknown error');
+ return of(null);
+ })
+ )
+ .subscribe()
);
}
From 1d57f99415689f96e77327c445d521a5a2aac067 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Thu, 5 Jun 2025 15:50:05 +0300
Subject: [PATCH 05/13] A few more improvements for health check banner
functionality
---
.../df-api-docs/df-api-docs.component.ts | 108 +++++++++++++++---
1 file changed, 92 insertions(+), 16 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 9c7829b5..f14ebef7 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -43,7 +43,7 @@ import {
} from 'rxjs/operators';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BASE_URL } from 'src/app/shared/constants/urls';
-import { Subscription, of } from 'rxjs';
+import { Subscription, of, forkJoin } from 'rxjs';
interface ServiceResponse {
resource: Array<{
@@ -54,9 +54,22 @@ interface ServiceResponse {
}
interface ApiDocJson {
+ info: {
+ description: string | undefined;
+ title: string;
+ version: string | undefined;
+ group: string;
+ };
paths: {
[key: string]: any;
};
+ [key: string]: any;
+}
+
+interface HealthCheckResult {
+ endpoint: string;
+ success?: boolean;
+ error?: string;
}
@UntilDestroy({ checkProperties: true })
@@ -95,6 +108,7 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
healthError: string | null = null;
serviceName: string | null = null;
showUnhealthyErrorDetails = false;
+ private static SUPPORTED_SERVICE_TYPE_GROUPS = ['Database', 'File'];
constructor(
private activatedRoute: ActivatedRoute,
@@ -162,7 +176,6 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
ngAfterContentInit(): void {
const apiDocumentation = this.apiDocJson;
-
this.checkApiHealth();
SwaggerUI({
@@ -228,17 +241,64 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
}
private checkApiHealth(): void {
- if (this.serviceName && this.apiDocJson?.paths['/_schema']?.get) {
+ let endpointsToValidate = this.getApiEnpointsToValidate();
+ if (this.serviceName && endpointsToValidate.length > 0) {
// Perform health check
- this.performHealthCheck(
- this.serviceName,
- `${BASE_URL}/${this.serviceName}/_schema`
- );
+ this.performHealthCheck(endpointsToValidate);
} else {
this.setHealthState('warning');
}
}
+ private getApiEnpointsToValidate(): string[] {
+ const endpointsToValidate: string[] = [];
+
+ if (!this.apiDocJson || !this.apiDocJson.info) {
+ return []; // Not enough info to determine endpoints
+ }
+
+ if (
+ DfApiDocsComponent.SUPPORTED_SERVICE_TYPE_GROUPS.includes(
+ this.apiDocJson.info.group
+ )
+ ) {
+ for (const path in this.apiDocJson.paths) {
+ if (
+ !Object.prototype.hasOwnProperty.call(this.apiDocJson.paths, path)
+ ) {
+ continue;
+ }
+ const methods = this.apiDocJson.paths[path];
+
+ for (const method in methods) {
+ if (!Object.prototype.hasOwnProperty.call(methods, method)) {
+ continue;
+ }
+ if (method.toLowerCase() === 'get') {
+ const operation = methods[method];
+ const parameters: Array<{
+ name: string;
+ in: string;
+ required?: boolean;
+ }> = operation.parameters || [];
+
+ const hasPathParameters = path.includes('{');
+
+ const hasRequiredQueryParameters = parameters.some(
+ param => param && param.in === 'query' && param.required === true
+ );
+
+ if (!hasPathParameters && !hasRequiredQueryParameters) {
+ endpointsToValidate.push(path);
+ }
+ }
+ }
+ }
+ }
+
+ return endpointsToValidate;
+ }
+
private setHealthState(
status: 'healthy' | 'unhealthy' | 'warning',
error: string | null = null
@@ -247,23 +307,39 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
this.healthError = error;
}
- private performHealthCheck(serviceName: string | null, url: string): void {
+ private performHealthCheck(endpoints: string[]): void {
this.healthStatus = 'loading';
this.healthError = null;
- this.subscriptions.push(
+ const healthCheckRequests = endpoints.map(endpoint =>
this.http
- .get(url)
+ .get(`${BASE_URL}/${this.serviceName}${endpoint}`, {
+ responseType: 'text',
+ })
.pipe(
- tap(() => this.setHealthState('healthy')),
+ map(() => ({ endpoint, success: true }) as HealthCheckResult),
catchError((error: HttpErrorResponse) => {
- if (error.status >= 200 && error.status < 300) {
+ return of({
+ endpoint,
+ error: error.message || error.error?.message || 'Unknown error',
+ } as HealthCheckResult);
+ })
+ )
+ );
+
+ this.subscriptions.push(
+ forkJoin(healthCheckRequests)
+ .pipe(
+ tap((results: HealthCheckResult[]) => {
+ const errors = results.filter(result => result.error);
+ if (errors.length > 0) {
+ this.setHealthState(
+ 'unhealthy',
+ errors.map(e => `${e.endpoint}: ${e.error}`).join(', \n\n')
+ );
+ } else {
this.setHealthState('healthy');
- return of(null);
}
-
- this.setHealthState('unhealthy', error.message || 'Unknown error');
- return of(null);
})
)
.subscribe()
From 796dd1d4ef999526bb079ed97de7dce4b90f919f Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Thu, 5 Jun 2025 16:32:36 +0300
Subject: [PATCH 06/13] Add system and user service groups into supported types
---
.../df-api-docs/df-api-docs.component.ts | 21 ++++++++++++++++---
1 file changed, 18 insertions(+), 3 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index f14ebef7..657800c2 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -108,7 +108,12 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
healthError: string | null = null;
serviceName: string | null = null;
showUnhealthyErrorDetails = false;
- private static SUPPORTED_SERVICE_TYPE_GROUPS = ['Database', 'File'];
+ private static SUPPORTED_SERVICE_TYPE_GROUPS = [
+ 'Database',
+ 'File',
+ 'User',
+ 'System',
+ ];
constructor(
private activatedRoute: ActivatedRoute,
@@ -274,6 +279,7 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
if (!Object.prototype.hasOwnProperty.call(methods, method)) {
continue;
}
+
if (method.toLowerCase() === 'get') {
const operation = methods[method];
const parameters: Array<{
@@ -283,12 +289,21 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
}> = operation.parameters || [];
const hasPathParameters = path.includes('{');
-
const hasRequiredQueryParameters = parameters.some(
param => param && param.in === 'query' && param.required === true
);
- if (!hasPathParameters && !hasRequiredQueryParameters) {
+ const isSystemServiceRelatedPath =
+ (path.includes('/admin') || path.includes('/service_report')) &&
+ this.apiDocJson.info.group === 'System';
+
+ const isNonRootAdmin = !this.userDataService.userData?.isRootAdmin;
+
+ if (
+ !hasPathParameters &&
+ !hasRequiredQueryParameters &&
+ !(isNonRootAdmin && isSystemServiceRelatedPath)
+ ) {
endpointsToValidate.push(path);
}
}
From e463b37d5478969a52e164fe33bb9ef9b3810ff4 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Thu, 5 Jun 2025 17:07:30 +0300
Subject: [PATCH 07/13] Revert "Add system and user service groups into
supported types"
This reverts commit 796dd1d4ef999526bb079ed97de7dce4b90f919f.
---
.../df-api-docs/df-api-docs.component.ts | 21 +++----------------
1 file changed, 3 insertions(+), 18 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 657800c2..f14ebef7 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -108,12 +108,7 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
healthError: string | null = null;
serviceName: string | null = null;
showUnhealthyErrorDetails = false;
- private static SUPPORTED_SERVICE_TYPE_GROUPS = [
- 'Database',
- 'File',
- 'User',
- 'System',
- ];
+ private static SUPPORTED_SERVICE_TYPE_GROUPS = ['Database', 'File'];
constructor(
private activatedRoute: ActivatedRoute,
@@ -279,7 +274,6 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
if (!Object.prototype.hasOwnProperty.call(methods, method)) {
continue;
}
-
if (method.toLowerCase() === 'get') {
const operation = methods[method];
const parameters: Array<{
@@ -289,21 +283,12 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
}> = operation.parameters || [];
const hasPathParameters = path.includes('{');
+
const hasRequiredQueryParameters = parameters.some(
param => param && param.in === 'query' && param.required === true
);
- const isSystemServiceRelatedPath =
- (path.includes('/admin') || path.includes('/service_report')) &&
- this.apiDocJson.info.group === 'System';
-
- const isNonRootAdmin = !this.userDataService.userData?.isRootAdmin;
-
- if (
- !hasPathParameters &&
- !hasRequiredQueryParameters &&
- !(isNonRootAdmin && isSystemServiceRelatedPath)
- ) {
+ if (!hasPathParameters && !hasRequiredQueryParameters) {
endpointsToValidate.push(path);
}
}
From 26c51f14751db5eeb7c39735ba3c403cfe7c9914 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Thu, 5 Jun 2025 17:49:58 +0300
Subject: [PATCH 08/13] Get rid of dynemic endpoint fetching functionality to
reduce requests
---
.../df-api-docs/df-api-docs.component.ts | 95 ++++---------------
1 file changed, 19 insertions(+), 76 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index f14ebef7..519a9ef3 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -108,7 +108,11 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
healthError: string | null = null;
serviceName: string | null = null;
showUnhealthyErrorDetails = false;
- private static SUPPORTED_SERVICE_TYPE_GROUPS = ['Database', 'File'];
+ // Mapping of service types to their corresponding endpoints, probably would be better to move to the back-end
+ healthCheckEndpointsMap: { [key: string]: string[] } = {
+ Database: ['/_schema', '/_table'],
+ File: ['/'],
+ };
constructor(
private activatedRoute: ActivatedRoute,
@@ -241,64 +245,16 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
}
private checkApiHealth(): void {
- let endpointsToValidate = this.getApiEnpointsToValidate();
- if (this.serviceName && endpointsToValidate.length > 0) {
+ let endpointsToValidate =
+ this.healthCheckEndpointsMap[this.apiDocJson.info.group];
+ if (this.serviceName && endpointsToValidate) {
// Perform health check
- this.performHealthCheck(endpointsToValidate);
+ this.performHealthCheck(endpointsToValidate[0]);
} else {
this.setHealthState('warning');
}
}
- private getApiEnpointsToValidate(): string[] {
- const endpointsToValidate: string[] = [];
-
- if (!this.apiDocJson || !this.apiDocJson.info) {
- return []; // Not enough info to determine endpoints
- }
-
- if (
- DfApiDocsComponent.SUPPORTED_SERVICE_TYPE_GROUPS.includes(
- this.apiDocJson.info.group
- )
- ) {
- for (const path in this.apiDocJson.paths) {
- if (
- !Object.prototype.hasOwnProperty.call(this.apiDocJson.paths, path)
- ) {
- continue;
- }
- const methods = this.apiDocJson.paths[path];
-
- for (const method in methods) {
- if (!Object.prototype.hasOwnProperty.call(methods, method)) {
- continue;
- }
- if (method.toLowerCase() === 'get') {
- const operation = methods[method];
- const parameters: Array<{
- name: string;
- in: string;
- required?: boolean;
- }> = operation.parameters || [];
-
- const hasPathParameters = path.includes('{');
-
- const hasRequiredQueryParameters = parameters.some(
- param => param && param.in === 'query' && param.required === true
- );
-
- if (!hasPathParameters && !hasRequiredQueryParameters) {
- endpointsToValidate.push(path);
- }
- }
- }
- }
- }
-
- return endpointsToValidate;
- }
-
private setHealthState(
status: 'healthy' | 'unhealthy' | 'warning',
error: string | null = null
@@ -307,39 +263,26 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
this.healthError = error;
}
- private performHealthCheck(endpoints: string[]): void {
+ private performHealthCheck(endpoint: string): void {
this.healthStatus = 'loading';
this.healthError = null;
- const healthCheckRequests = endpoints.map(endpoint =>
+ this.subscriptions.push(
this.http
.get(`${BASE_URL}/${this.serviceName}${endpoint}`, {
responseType: 'text',
})
.pipe(
- map(() => ({ endpoint, success: true }) as HealthCheckResult),
+ tap(() => this.setHealthState('healthy')),
catchError((error: HttpErrorResponse) => {
- return of({
- endpoint,
- error: error.message || error.error?.message || 'Unknown error',
- } as HealthCheckResult);
- })
- )
- );
+ this.setHealthState(
+ 'unhealthy',
+ `${endpoint}: ${
+ error.message || error.error.message || 'Unknown error'
+ }`
+ );
- this.subscriptions.push(
- forkJoin(healthCheckRequests)
- .pipe(
- tap((results: HealthCheckResult[]) => {
- const errors = results.filter(result => result.error);
- if (errors.length > 0) {
- this.setHealthState(
- 'unhealthy',
- errors.map(e => `${e.endpoint}: ${e.error}`).join(', \n\n')
- );
- } else {
- this.setHealthState('healthy');
- }
+ return of(null);
})
)
.subscribe()
From 4882f67ad4d76c8b045d41f008b9fc9010b4f728 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Fri, 6 Jun 2025 12:46:44 +0300
Subject: [PATCH 09/13] Add some basic and simple pre-generated curls for
supported for health check api types
---
.../df-api-docs/df-api-docs.component.html | 119 ++++++++++++------
.../df-api-docs/df-api-docs.component.scss | 24 +++-
.../df-api-docs/df-api-docs.component.ts | 94 ++++++++++----
src/assets/i18n/en.json | 4 +
4 files changed, 176 insertions(+), 65 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index 6caa9b0d..45d8ed42 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -32,47 +32,88 @@
-
-
-
{{ 'apiHealthBanner.loading' | transloco }}
-
-
-
{{ 'apiHealthBanner.healthy' | transloco }}
-
-
-
- {{ 'apiHealthBanner.unhealthyBase' | transloco }}
-
- {{
- (showUnhealthyErrorDetails
- ? 'apiHealthBanner.hideDetails'
- : 'apiHealthBanner.viewDetails'
- ) | transloco
- }}
-
-
-
-
{{ healthError }}
+
+
+
+
{{ 'apiHealthBanner.loading' | transloco }}
+
+
+
{{ 'apiHealthBanner.healthy' | transloco }}
+
+
+
+ {{ 'apiHealthBanner.unhealthyBase' | transloco }}
+
+ {{
+ (showUnhealthyErrorDetails
+ ? 'apiHealthBanner.hideDetails'
+ : 'apiHealthBanner.viewDetails'
+ ) | transloco
+ }}
+
+
+
+
+
+
+ {{ 'apiHealthBanner.warningDefault' | transloco }}
+
-
-
- {{ 'apiHealthBanner.warningDefault' | transloco }}
-
-
+
+
+
+
+
+ {{ 'apiBasicCurlCommands.title' | transloco }}
+
+
+
+
+ 0"
+ class="curl-commands-container">
+
+
+ {{ command.text }}
+
+
+
+
+
+
+
+
+
+ {{ 'apiBasicCurlCommands.noCommands' | transloco }}
+
+
+
+
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
index 136f9269..d2f0639d 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
@@ -134,9 +134,31 @@
.main {
display: flex;
justify-content: space-between;
- flex-direction: row-reverse;
+ flex-wrap: wrap;
gap: 8px;
}
}
}
}
+
+.curl-command-text {
+ white-space: pre;
+ font-family: monospace;
+ font-size: 0.9em;
+ margin: 0;
+ color: var(--df-script-editor-text-color);
+ overflow-x: auto;
+}
+
+.custom-swagger-content-wrapper {
+ width: 100%;
+}
+
+.curl-commands-container {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ .actions-container {
+ padding: 0px 8px;
+ }
+}
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 519a9ef3..b216f3cc 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -31,6 +31,10 @@ import { ApiKeysService } from '../services/api-keys.service';
import { ApiKeyInfo } from 'src/app/shared/types/api-keys';
import { Clipboard } from '@angular/cdk/clipboard';
import { MatSnackBar } from '@angular/material/snack-bar';
+import { MatListModule } from '@angular/material/list';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatCardModule } from '@angular/material/card';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faCopy } from '@fortawesome/free-solid-svg-icons';
import { DfCurrentServiceService } from 'src/app/shared/services/df-current-service.service';
@@ -90,12 +94,18 @@ interface HealthCheckResult {
SlicePipe,
NgClass,
FontAwesomeModule,
+ MatListModule,
+ MatTooltipModule,
+ MatExpansionModule,
+ MatCardModule,
],
})
export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
@ViewChild('apiDocumentation', { static: true }) apiDocElement:
| ElementRef
| undefined;
+ @ViewChild('swaggerInjectedContentContainer')
+ swaggerInjectedContentContainerRef: ElementRef | undefined;
@ViewChild('healthBannerElement') healthBannerElementRef:
| ElementRef
| undefined;
@@ -103,6 +113,7 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
apiDocJson: ApiDocJson;
apiKeys: ApiKeyInfo[] = [];
faCopy = faCopy;
+ curlCommands: { text: string }[] = []; // Will be populated dynamically
private subscriptions: Subscription[] = [];
healthStatus: 'loading' | 'healthy' | 'unhealthy' | 'warning' = 'loading';
healthError: string | null = null;
@@ -202,38 +213,27 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
},
showMutatedRequest: true,
onComplete: () => {
+ this.prepareCurlCommands();
+
if (
- this.healthBannerElementRef &&
- this.healthBannerElementRef.nativeElement &&
this.apiDocElement &&
- this.apiDocElement.nativeElement
+ this.apiDocElement.nativeElement &&
+ this.swaggerInjectedContentContainerRef &&
+ this.swaggerInjectedContentContainerRef.nativeElement
) {
const swaggerContainer = this.apiDocElement.nativeElement;
- const bannerNode = this.healthBannerElementRef.nativeElement;
+ const customContentNode =
+ this.swaggerInjectedContentContainerRef.nativeElement;
- // Try to find the information container within Swagger UI
const infoContainer = swaggerContainer.querySelector(
'.information-container .main'
);
- if (infoContainer) {
- // Prepend banner to the information container (which typically holds the title)
- if (infoContainer.firstChild) {
- infoContainer.insertBefore(bannerNode, infoContainer.firstChild);
- } else {
- infoContainer.appendChild(bannerNode);
- }
- } else {
- // Fallback: Prepend to the main swagger container if .information-container is not found
- if (swaggerContainer.firstChild) {
- swaggerContainer.insertBefore(
- bannerNode,
- swaggerContainer.firstChild
- );
- } else {
- swaggerContainer.appendChild(bannerNode);
- }
- }
+ this.injectCustomContent(
+ swaggerContainer,
+ infoContainer,
+ customContentNode
+ );
}
},
});
@@ -304,12 +304,56 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
copyApiKey(key: string) {
this.clipboard.copy(key);
- this.snackBar.open('API Key copied to clipboard', 'Close', {
- duration: 3000,
+ this.snackBar.open('API Key copied to clipboard!', 'Close', {
+ duration: 2000,
});
}
+ copyCurlCommand(commandText: string) {
+ this.clipboard.copy(commandText);
+ }
+
toggleUnhealthyErrorDetails(): void {
this.showUnhealthyErrorDetails = !this.showUnhealthyErrorDetails;
}
+
+ private prepareCurlCommands(): void {
+ this.curlCommands = [];
+ if (!this.serviceName || !this.apiDocJson?.info?.group) {
+ return;
+ }
+
+ const endpoints = this.healthCheckEndpointsMap[this.apiDocJson.info.group];
+ if (endpoints && endpoints.length > 0) {
+ endpoints.forEach(endpoint => {
+ const sessionToken = this.userDataService.token
+ ? this.userDataService.token
+ : 'YOUR_SESSION_TOKEN';
+
+ console.log(`${BASE_URL}/${this.serviceName}${endpoint}`);
+
+ const command = `curl -X 'GET' '${window.location.origin}${BASE_URL}/${this.serviceName}${endpoint}' -H 'accept: application/json' -H '${SESSION_TOKEN_HEADER}: ${sessionToken}'`;
+ this.curlCommands.push({ text: command });
+ });
+ }
+ }
+
+ private injectCustomContent(
+ swaggerContainer: HTMLElement,
+ infoContainer: HTMLElement | null,
+ customContentNode: HTMLElement
+ ): void {
+ if (infoContainer) {
+ infoContainer.appendChild(customContentNode);
+ } else {
+ if (swaggerContainer.firstChild) {
+ swaggerContainer.insertBefore(
+ customContentNode,
+ swaggerContainer.firstChild
+ );
+ } else {
+ swaggerContainer.appendChild(customContentNode);
+ }
+ }
+ }
}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 41a28d60..524ca228 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -125,6 +125,10 @@
"hideDetails": "Hide Details",
"warningDefault": "Warning: This type of API currently does not support automatic health checks."
},
+ "apiBasicCurlCommands": {
+ "title": "Test api yourself with this simple curl commads here:",
+ "copyTooltip": "Copy"
+ },
"nav": {
"error": {
"header": "Error"
From f6480a039a0ce9136a175fcd7f7d1a3321cca75a Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Fri, 6 Jun 2025 12:53:46 +0300
Subject: [PATCH 10/13] Do not display dropdown if no curl commands generated
---
src/app/adf-api-docs/df-api-docs/df-api-docs.component.html | 5 +----
src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts | 3 ---
2 files changed, 1 insertion(+), 7 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index 45d8ed42..a8711347 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -76,7 +76,7 @@
-
+ 0">
@@ -108,9 +108,6 @@
-
- {{ 'apiBasicCurlCommands.noCommands' | transloco }}
-
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index b216f3cc..70788022 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -329,9 +329,6 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
const sessionToken = this.userDataService.token
? this.userDataService.token
: 'YOUR_SESSION_TOKEN';
-
- console.log(`${BASE_URL}/${this.serviceName}${endpoint}`);
-
const command = `curl -X 'GET' '${window.location.origin}${BASE_URL}/${this.serviceName}${endpoint}' -H 'accept: application/json' -H '${SESSION_TOKEN_HEADER}: ${sessionToken}'`;
this.curlCommands.push({ text: command });
});
From 9049d0fca7a2d1675a274d4b4bdcff07eb6d6b30 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Mon, 9 Jun 2025 15:19:25 +0300
Subject: [PATCH 11/13] Make a few improvements for quickstart dropdown
component + move quickstart dropdown to its own component
---
.../df-api-docs/df-api-docs.component.html | 39 +-----
.../df-api-docs/df-api-docs.component.scss | 20 +--
.../df-api-docs/df-api-docs.component.ts | 77 +++++------
.../df-curl-command.component.html | 52 ++++++++
.../df-curl-command.component.scss | 26 ++++
.../df-curl-command.component.ts | 126 ++++++++++++++++++
src/app/shared/types/files.ts | 21 +++
src/assets/i18n/en.json | 6 +-
src/dark-style.scss | 3 +-
9 files changed, 268 insertions(+), 102 deletions(-)
create mode 100644 src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
create mode 100644 src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss
create mode 100644 src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index a8711347..396bd261 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -76,41 +76,10 @@
- 0">
-
-
-
- {{ 'apiBasicCurlCommands.title' | transloco }}
-
-
-
-
- 0"
- class="curl-commands-container">
-
-
- {{ command.text }}
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
index d2f0639d..4351fb83 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
@@ -141,24 +141,6 @@
}
}
-.curl-command-text {
- white-space: pre;
- font-family: monospace;
- font-size: 0.9em;
- margin: 0;
- color: var(--df-script-editor-text-color);
- overflow-x: auto;
-}
-
.custom-swagger-content-wrapper {
width: 100%;
-}
-
-.curl-commands-container {
- display: flex;
- flex-direction: column;
- gap: 8px;
- .actions-container {
- padding: 0px 8px;
- }
-}
+}
\ No newline at end of file
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index 70788022..aa447b87 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -48,6 +48,8 @@ import {
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BASE_URL } from 'src/app/shared/constants/urls';
import { Subscription, of, forkJoin } from 'rxjs';
+import { DfCurlCommandComponent } from '../df-curl-command/df-curl-command.component';
+import { ApiDocJson } from '../../shared/types/files';
interface ServiceResponse {
resource: Array<{
@@ -57,19 +59,6 @@ interface ServiceResponse {
}>;
}
-interface ApiDocJson {
- info: {
- description: string | undefined;
- title: string;
- version: string | undefined;
- group: string;
- };
- paths: {
- [key: string]: any;
- };
- [key: string]: any;
-}
-
interface HealthCheckResult {
endpoint: string;
success?: boolean;
@@ -98,6 +87,7 @@ interface HealthCheckResult {
MatTooltipModule,
MatExpansionModule,
MatCardModule,
+ DfCurlCommandComponent,
],
})
export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
@@ -113,16 +103,37 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
apiDocJson: ApiDocJson;
apiKeys: ApiKeyInfo[] = [];
faCopy = faCopy;
- curlCommands: { text: string }[] = []; // Will be populated dynamically
+
private subscriptions: Subscription[] = [];
healthStatus: 'loading' | 'healthy' | 'unhealthy' | 'warning' = 'loading';
healthError: string | null = null;
serviceName: string | null = null;
showUnhealthyErrorDetails = false;
// Mapping of service types to their corresponding endpoints, probably would be better to move to the back-end
- healthCheckEndpointsMap: { [key: string]: string[] } = {
- Database: ['/_schema', '/_table'],
- File: ['/'],
+ healthCheckEndpointsInfo: {
+ [key: string]: { endpoint: string; title: string; description: string }[];
+ } = {
+ Database: [
+ {
+ endpoint: '/_schema',
+ title: 'View Available Schemas',
+ description:
+ 'This command fetches a list of schemas from your connected database',
+ },
+ {
+ endpoint: '/_table',
+ title: 'View Tables in Your Database',
+ description: 'This command lists all tables in your database',
+ },
+ ],
+ File: [
+ {
+ endpoint: '/',
+ title: 'View Available Folders',
+ description:
+ 'This command fetches a list of folders from your connected file storage',
+ },
+ ],
};
constructor(
@@ -213,8 +224,6 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
},
showMutatedRequest: true,
onComplete: () => {
- this.prepareCurlCommands();
-
if (
this.apiDocElement &&
this.apiDocElement.nativeElement &&
@@ -245,11 +254,11 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
}
private checkApiHealth(): void {
- let endpointsToValidate =
- this.healthCheckEndpointsMap[this.apiDocJson.info.group];
- if (this.serviceName && endpointsToValidate) {
+ let endpointsInfoToValidate =
+ this.healthCheckEndpointsInfo[this.apiDocJson.info.group];
+ if (this.serviceName && endpointsInfoToValidate) {
// Perform health check
- this.performHealthCheck(endpointsToValidate[0]);
+ this.performHealthCheck(endpointsInfoToValidate[0].endpoint);
} else {
this.setHealthState('warning');
}
@@ -309,32 +318,10 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
});
}
- copyCurlCommand(commandText: string) {
- this.clipboard.copy(commandText);
- }
-
toggleUnhealthyErrorDetails(): void {
this.showUnhealthyErrorDetails = !this.showUnhealthyErrorDetails;
}
- private prepareCurlCommands(): void {
- this.curlCommands = [];
- if (!this.serviceName || !this.apiDocJson?.info?.group) {
- return;
- }
-
- const endpoints = this.healthCheckEndpointsMap[this.apiDocJson.info.group];
- if (endpoints && endpoints.length > 0) {
- endpoints.forEach(endpoint => {
- const sessionToken = this.userDataService.token
- ? this.userDataService.token
- : 'YOUR_SESSION_TOKEN';
- const command = `curl -X 'GET' '${window.location.origin}${BASE_URL}/${this.serviceName}${endpoint}' -H 'accept: application/json' -H '${SESSION_TOKEN_HEADER}: ${sessionToken}'`;
- this.curlCommands.push({ text: command });
- });
- }
- }
-
private injectCustomContent(
swaggerContainer: HTMLElement,
infoContainer: HTMLElement | null,
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
new file mode 100644
index 00000000..88b5969c
--- /dev/null
+++ b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
@@ -0,0 +1,52 @@
+ 0">
+
+
+
+ {{ 'apiBasicCurlCommands.title' | transloco }}
+
+
+
+
+
+ {{ 'apiBasicCurlCommands.quickStartDetails' | transloco }}
+
+
+
+ {{ i + 1 }}. {{ command.title }}
+
+
+ {{ command.description }}
+
+
+
+ {{ command.text }}
+
+
+
+
+
+
+
+
{{ command.note }}
+
+
+
+ {{
+ 'apiBasicCurlCommands.nextStepFooter.header' | transloco
+ }}
+ {{ 'apiBasicCurlCommands.nextStepFooter.body' | transloco }}
+
+
+
+
\ No newline at end of file
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss
new file mode 100644
index 00000000..b43548a0
--- /dev/null
+++ b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss
@@ -0,0 +1,26 @@
+.curl-command-text {
+ white-space: pre;
+ font-family: monospace;
+ font-size: 0.9em;
+ margin: 0;
+ color: var(--df-script-editor-text-color);
+ overflow-x: auto;
+}
+
+.curl-commands-container {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ .actions-container {
+ padding: 0px 8px;
+ }
+}
+
+.curl-command-title {
+ margin: 0;
+ font-weight: bold;
+}
+
+.curl-command-note {
+ color: gray !important;
+}
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
new file mode 100644
index 00000000..cd507b53
--- /dev/null
+++ b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
@@ -0,0 +1,126 @@
+import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { TranslocoModule } from '@ngneat/transloco';
+import { MatExpansionModule } from '@angular/material/expansion';
+import { MatCardModule } from '@angular/material/card';
+import { MatIconModule } from '@angular/material/icon';
+import { MatTooltipModule } from '@angular/material/tooltip';
+import { MatButtonModule } from '@angular/material/button';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { faCopy } from '@fortawesome/free-solid-svg-icons';
+import { Clipboard } from '@angular/cdk/clipboard';
+import { MatSnackBar } from '@angular/material/snack-bar';
+import { DfUserDataService } from 'src/app/shared/services/df-user-data.service';
+import { BASE_URL } from 'src/app/shared/constants/urls';
+import { SESSION_TOKEN_HEADER } from 'src/app/shared/constants/http-headers';
+import { ApiDocJson } from 'src/app/shared/types/files';
+import { MatDividerModule } from '@angular/material/divider';
+
+interface CurlCommand {
+ title: string;
+ description: string;
+ text: string;
+ note: string;
+}
+
+const healthCheckEndpointsInfo: {
+ [key: string]: { endpoint: string; title: string; description: string }[];
+} = {
+ Database: [
+ {
+ endpoint: '/_schema',
+ title: 'View Available Schemas',
+ description:
+ 'This command fetches a list of schemas from your connected database',
+ },
+ {
+ endpoint: '/_table',
+ title: 'View Tables in Your Database',
+ description: 'This command lists all tables in your database',
+ },
+ ],
+ File: [
+ {
+ endpoint: '/',
+ title: 'View Available Folders',
+ description:
+ 'This command fetches a list of folders from your connected file storage',
+ },
+ ],
+};
+
+@Component({
+ selector: 'df-curl-command',
+ templateUrl: './df-curl-command.component.html',
+ styleUrls: ['./df-curl-command.component.scss'],
+ standalone: true,
+ imports: [
+ CommonModule,
+ TranslocoModule,
+ MatExpansionModule,
+ MatCardModule,
+ MatIconModule,
+ MatTooltipModule,
+ FontAwesomeModule,
+ MatDividerModule,
+ MatButtonModule
+ ],
+})
+export class DfCurlCommandComponent implements OnChanges {
+ @Input() apiDocJson: ApiDocJson;
+ @Input() serviceName: string;
+
+ curlCommands: CurlCommand[] = [];
+ faCopy = faCopy;
+
+ constructor(
+ private clipboard: Clipboard,
+ private snackBar: MatSnackBar,
+ private userDataService: DfUserDataService
+ ) {}
+
+ ngOnChanges(changes: SimpleChanges): void {
+ if (
+ (changes['apiDocJson'] || changes['serviceName']) &&
+ this.apiDocJson &&
+ this.serviceName
+ ) {
+ this.prepareCurlCommands();
+ }
+ }
+
+ copyCurlCommand(commandText: string) {
+ this.clipboard.copy(commandText);
+ this.snackBar.open('CURL command copied to clipboard.', 'Close', {
+ duration: 2000,
+ });
+ }
+
+ private prepareCurlCommands(): void {
+ this.curlCommands = [];
+ if (!this.serviceName || !this.apiDocJson?.info?.group) {
+ return;
+ }
+
+ const endpointsInfo =
+ healthCheckEndpointsInfo[this.apiDocJson.info.group];
+ if (endpointsInfo?.length > 0) {
+ endpointsInfo.forEach(endpointInfo => {
+ const sessionToken =
+ this.userDataService.token || 'YOUR_SESSION_TOKEN';
+ const command = `curl -X 'GET' '${window.location.origin}${BASE_URL}/${this.serviceName}${endpointInfo.endpoint}' -H 'accept: application/json' -H '${SESSION_TOKEN_HEADER}: ${sessionToken}'`;
+
+ this.curlCommands.push({
+ title: endpointInfo.title,
+ description: endpointInfo.description,
+ text: command,
+ note: this.apiDocJson.paths[endpointInfo.endpoint]?.['get']?.summary,
+ });
+ });
+ }
+ }
+
+ trackByCommand(index: number, item: CurlCommand): string {
+ return item.text;
+ }
+}
\ No newline at end of file
diff --git a/src/app/shared/types/files.ts b/src/app/shared/types/files.ts
index a6e25dba..ba4c0541 100644
--- a/src/app/shared/types/files.ts
+++ b/src/app/shared/types/files.ts
@@ -36,3 +36,24 @@ export interface FileType {
}
type EntityType = 'file' | 'folder';
+
+export interface ApiDocJson {
+ info: {
+ description?: string;
+ title: string;
+ version?: string;
+ group: string;
+ };
+ paths: {
+ [endpoint: string]: {
+ [method: string]: {
+ operationId: string;
+ description: string;
+ summary: string;
+ tags: string[];
+ [key: string]: any;
+ };
+ };
+ };
+ [key: string]: any;
+}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 524ca228..2d837f75 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -126,8 +126,10 @@
"warningDefault": "Warning: This type of API currently does not support automatic health checks."
},
"apiBasicCurlCommands": {
- "title": "Test api yourself with this simple curl commads here:",
- "copyTooltip": "Copy"
+ "title": "Quickstart: Test Your API Connection:",
+ "quickStartDetails": "Start by testing your API with these sample curl commands. They return real data and confirm your connection is active.",
+ "copyTooltip": "Copy",
+ "nextStepFooter": {"header": "Next Step:", "body": "Scroll below to explore more endpoints that allow you to read, write, and filter your data via generated REST"}
},
"nav": {
"error": {
diff --git a/src/dark-style.scss b/src/dark-style.scss
index f361b0a5..aa651f21 100644
--- a/src/dark-style.scss
+++ b/src/dark-style.scss
@@ -95,7 +95,8 @@ $df-purple-palette: mat.define-palette(theme.$df-purple-palette);
input,
textarea,
button,
- span {
+ span,
+ .themed-text {
color: white !important;
}
.mat-mdc-form-field-required-marker {
From 99f96f0fce41e147c1df6a40b09c4758c3b4fbc8 Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Mon, 9 Jun 2025 15:20:04 +0300
Subject: [PATCH 12/13] Used a prittier
---
.../df-api-docs/df-api-docs.component.scss | 2 +-
.../df-curl-command/df-curl-command.component.html | 6 ++----
.../df-curl-command/df-curl-command.component.ts | 10 ++++------
3 files changed, 7 insertions(+), 11 deletions(-)
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
index 4351fb83..f2cbf028 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.scss
@@ -143,4 +143,4 @@
.custom-swagger-content-wrapper {
width: 100%;
-}
\ No newline at end of file
+}
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
index 88b5969c..6d8682b0 100644
--- a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
+++ b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
@@ -31,9 +31,7 @@
+ matTooltip="{{ 'apiBasicCurlCommands.copyTooltip' | transloco }}">
@@ -49,4 +47,4 @@
-
\ No newline at end of file
+
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
index cd507b53..ba3bf4ce 100644
--- a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
+++ b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
@@ -63,7 +63,7 @@ const healthCheckEndpointsInfo: {
MatTooltipModule,
FontAwesomeModule,
MatDividerModule,
- MatButtonModule
+ MatButtonModule,
],
})
export class DfCurlCommandComponent implements OnChanges {
@@ -102,12 +102,10 @@ export class DfCurlCommandComponent implements OnChanges {
return;
}
- const endpointsInfo =
- healthCheckEndpointsInfo[this.apiDocJson.info.group];
+ const endpointsInfo = healthCheckEndpointsInfo[this.apiDocJson.info.group];
if (endpointsInfo?.length > 0) {
endpointsInfo.forEach(endpointInfo => {
- const sessionToken =
- this.userDataService.token || 'YOUR_SESSION_TOKEN';
+ const sessionToken = this.userDataService.token || 'YOUR_SESSION_TOKEN';
const command = `curl -X 'GET' '${window.location.origin}${BASE_URL}/${this.serviceName}${endpointInfo.endpoint}' -H 'accept: application/json' -H '${SESSION_TOKEN_HEADER}: ${sessionToken}'`;
this.curlCommands.push({
@@ -123,4 +121,4 @@ export class DfCurlCommandComponent implements OnChanges {
trackByCommand(index: number, item: CurlCommand): string {
return item.text;
}
-}
\ No newline at end of file
+}
From cdddb7d7430f2bc83d2ba145ca31a06a6952769d Mon Sep 17 00:00:00 2001
From: VitaliyHrabovych
Date: Mon, 9 Jun 2025 16:11:41 +0300
Subject: [PATCH 13/13] Renamed a component
---
.../df-api-docs/df-api-docs.component.html | 4 +-
.../df-api-docs/df-api-docs.component.ts | 6 +-
.../df-api-quickstart.component.html | 55 ++++++++++++++++
.../df-api-quickstart.component.scss | 62 +++++++++++++++++++
.../df-api-quickstart.component.ts} | 31 +++++-----
.../df-curl-command.component.html | 50 ---------------
.../df-curl-command.component.scss | 26 --------
src/assets/i18n/en.json | 2 +-
8 files changed, 140 insertions(+), 96 deletions(-)
create mode 100644 src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.html
create mode 100644 src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.scss
rename src/app/adf-api-docs/{df-curl-command/df-curl-command.component.ts => df-api-quickstart/df-api-quickstart.component.ts} (79%)
delete mode 100644 src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
delete mode 100644 src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
index 396bd261..9003c18f 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.html
@@ -76,10 +76,10 @@
-
+ [serviceName]="serviceName">
diff --git a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
index aa447b87..26a293fa 100644
--- a/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
+++ b/src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts
@@ -48,8 +48,8 @@ import {
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { BASE_URL } from 'src/app/shared/constants/urls';
import { Subscription, of, forkJoin } from 'rxjs';
-import { DfCurlCommandComponent } from '../df-curl-command/df-curl-command.component';
-import { ApiDocJson } from '../../shared/types/files';
+import { DfApiQuickstartComponent } from '../df-api-quickstart/df-api-quickstart.component';
+import { ApiDocJson } from 'src/app/shared/types/files';
interface ServiceResponse {
resource: Array<{
@@ -87,7 +87,7 @@ interface HealthCheckResult {
MatTooltipModule,
MatExpansionModule,
MatCardModule,
- DfCurlCommandComponent,
+ DfApiQuickstartComponent,
],
})
export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
diff --git a/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.html b/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.html
new file mode 100644
index 00000000..0d73cff7
--- /dev/null
+++ b/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.html
@@ -0,0 +1,55 @@
+
+
+
+
+ {{ 'apiBasicCurlCommands.title' | transloco }}
+
+
+
+ 0">
+
+ {{ 'apiBasicCurlCommands.quickStartDetails' | transloco }}
+
+
+
+ {{ i + 1 }}. {{ command.title }}
+
+
+ {{ command.description }}
+
+
+
+ {{ command.textForDisplay }}
+
+
+
+
+
+
+
+
{{ command.note }}
+
+
+
+
+ {{
+ 'apiBasicCurlCommands.nextStepFooter.header' | transloco
+ }}
+ {{ 'apiBasicCurlCommands.nextStepFooter.body' | transloco }}
+
+
+
+
diff --git a/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.scss b/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.scss
new file mode 100644
index 00000000..d6e9a152
--- /dev/null
+++ b/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.scss
@@ -0,0 +1,62 @@
+mat-expansion-panel-header {
+ padding: 0 12px;
+}
+.curl-command-text {
+ white-space: pre;
+ font-family: monospace;
+ font-size: 0.9em;
+ margin: 0;
+ color: var(--df-script-editor-text-color);
+ overflow-x: auto;
+}
+
+.curl-commands-container {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ .actions-container {
+ padding: 0px 8px;
+ }
+}
+
+.curl-command-title {
+ margin: 0;
+ font-weight: bold;
+}
+
+.curl-command-note {
+ color: gray !important;
+}
+
+.no-commands-container {
+ ul {
+ padding-left: 20px;
+ li {
+ margin-bottom: 10px;
+ }
+ }
+
+ span[class^='method-'] {
+ font-weight: bold;
+ font-family: monospace;
+ padding: 2px 6px;
+ border-radius: 4px;
+ color: white;
+ }
+
+ .method-get {
+ background-color: #61affe; // blue
+ }
+ .method-post {
+ background-color: #49cc90; // green
+ }
+ .method-put {
+ background-color: #fca130; // orange
+ }
+ .method-patch {
+ background-color: #fca130; // orange
+ }
+ .method-delete {
+ background-color: #f93e3e; // red
+ }
+}
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts b/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.ts
similarity index 79%
rename from src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
rename to src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.ts
index ba3bf4ce..6a97d20e 100644
--- a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.ts
+++ b/src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.ts
@@ -9,17 +9,18 @@ import { MatButtonModule } from '@angular/material/button';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faCopy } from '@fortawesome/free-solid-svg-icons';
import { Clipboard } from '@angular/cdk/clipboard';
-import { MatSnackBar } from '@angular/material/snack-bar';
import { DfUserDataService } from 'src/app/shared/services/df-user-data.service';
import { BASE_URL } from 'src/app/shared/constants/urls';
import { SESSION_TOKEN_HEADER } from 'src/app/shared/constants/http-headers';
import { ApiDocJson } from 'src/app/shared/types/files';
import { MatDividerModule } from '@angular/material/divider';
+import { MatSnackBar } from '@angular/material/snack-bar';
interface CurlCommand {
title: string;
description: string;
- text: string;
+ textForDisplay: string;
+ textForCopy: string;
note: string;
}
@@ -50,9 +51,9 @@ const healthCheckEndpointsInfo: {
};
@Component({
- selector: 'df-curl-command',
- templateUrl: './df-curl-command.component.html',
- styleUrls: ['./df-curl-command.component.scss'],
+ selector: 'df-api-quickstart',
+ templateUrl: './df-api-quickstart.component.html',
+ styleUrls: ['./df-api-quickstart.component.scss'],
standalone: true,
imports: [
CommonModule,
@@ -66,7 +67,7 @@ const healthCheckEndpointsInfo: {
MatButtonModule,
],
})
-export class DfCurlCommandComponent implements OnChanges {
+export class DfApiQuickstartComponent implements OnChanges {
@Input() apiDocJson: ApiDocJson;
@Input() serviceName: string;
@@ -75,8 +76,8 @@ export class DfCurlCommandComponent implements OnChanges {
constructor(
private clipboard: Clipboard,
- private snackBar: MatSnackBar,
- private userDataService: DfUserDataService
+ private userDataService: DfUserDataService,
+ private snackBar: MatSnackBar
) {}
ngOnChanges(changes: SimpleChanges): void {
@@ -91,9 +92,6 @@ export class DfCurlCommandComponent implements OnChanges {
copyCurlCommand(commandText: string) {
this.clipboard.copy(commandText);
- this.snackBar.open('CURL command copied to clipboard.', 'Close', {
- duration: 2000,
- });
}
private prepareCurlCommands(): void {
@@ -106,12 +104,17 @@ export class DfCurlCommandComponent implements OnChanges {
if (endpointsInfo?.length > 0) {
endpointsInfo.forEach(endpointInfo => {
const sessionToken = this.userDataService.token || 'YOUR_SESSION_TOKEN';
- const command = `curl -X 'GET' '${window.location.origin}${BASE_URL}/${this.serviceName}${endpointInfo.endpoint}' -H 'accept: application/json' -H '${SESSION_TOKEN_HEADER}: ${sessionToken}'`;
+ const baseUrl = `${window.location.origin}${BASE_URL}/${this.serviceName}${endpointInfo.endpoint}`;
+ const headers = `-H 'accept: application/json' -H '${SESSION_TOKEN_HEADER}: ${sessionToken}'`;
+
+ const commandForDisplay = `curl -X 'GET' '${baseUrl}' \\\n ${headers}`;
+ const commandForCopy = `curl -X 'GET' '${baseUrl}' ${headers}`;
this.curlCommands.push({
title: endpointInfo.title,
description: endpointInfo.description,
- text: command,
+ textForDisplay: commandForDisplay,
+ textForCopy: commandForCopy,
note: this.apiDocJson.paths[endpointInfo.endpoint]?.['get']?.summary,
});
});
@@ -119,6 +122,6 @@ export class DfCurlCommandComponent implements OnChanges {
}
trackByCommand(index: number, item: CurlCommand): string {
- return item.text;
+ return item.textForCopy;
}
}
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
deleted file mode 100644
index 6d8682b0..00000000
--- a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.html
+++ /dev/null
@@ -1,50 +0,0 @@
- 0">
-
-
-
- {{ 'apiBasicCurlCommands.title' | transloco }}
-
-
-
-
-
- {{ 'apiBasicCurlCommands.quickStartDetails' | transloco }}
-
-
-
- {{ i + 1 }}. {{ command.title }}
-
-
- {{ command.description }}
-
-
-
- {{ command.text }}
-
-
-
-
-
-
-
-
{{ command.note }}
-
-
-
- {{
- 'apiBasicCurlCommands.nextStepFooter.header' | transloco
- }}
- {{ 'apiBasicCurlCommands.nextStepFooter.body' | transloco }}
-
-
-
-
diff --git a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss b/src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss
deleted file mode 100644
index b43548a0..00000000
--- a/src/app/adf-api-docs/df-curl-command/df-curl-command.component.scss
+++ /dev/null
@@ -1,26 +0,0 @@
-.curl-command-text {
- white-space: pre;
- font-family: monospace;
- font-size: 0.9em;
- margin: 0;
- color: var(--df-script-editor-text-color);
- overflow-x: auto;
-}
-
-.curl-commands-container {
- display: flex;
- flex-direction: column;
- gap: 8px;
- .actions-container {
- padding: 0px 8px;
- }
-}
-
-.curl-command-title {
- margin: 0;
- font-weight: bold;
-}
-
-.curl-command-note {
- color: gray !important;
-}
diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json
index 2d837f75..8b00adbf 100644
--- a/src/assets/i18n/en.json
+++ b/src/assets/i18n/en.json
@@ -129,7 +129,7 @@
"title": "Quickstart: Test Your API Connection:",
"quickStartDetails": "Start by testing your API with these sample curl commands. They return real data and confirm your connection is active.",
"copyTooltip": "Copy",
- "nextStepFooter": {"header": "Next Step:", "body": "Scroll below to explore more endpoints that allow you to read, write, and filter your data via generated REST"}
+ "nextStepFooter": {"header": "Next Step:", "body": "Scroll below to explore generated endpoints that allow you to read, write, and filter your data via REST"}
},
"nav": {
"error": {