Skip to content

Commit 2351c80

Browse files
authored
Merge pull request #431 from dreamfactorysoftware/develop
Release 7.2 preparation
2 parents e9b261d + 0d7311d commit 2351c80

14 files changed

+1607
-560
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const healthCheckEndpointsInfo: {
2+
[key: string]: { endpoint: string; title: string; description: string }[];
3+
} = {
4+
Database: [
5+
{
6+
endpoint: '/_schema',
7+
title: 'View Available Schemas',
8+
description:
9+
'This command fetches a list of schemas from your connected database',
10+
},
11+
{
12+
endpoint: '/_table',
13+
title: 'View Tables in Your Database',
14+
description: 'This command lists all tables in your database',
15+
},
16+
],
17+
File: [
18+
{
19+
endpoint: '/',
20+
title: 'View Available Folders',
21+
description:
22+
'This command fetches a list of folders from your connected file storage',
23+
},
24+
],
25+
};

src/app/adf-api-docs/df-api-docs/df-api-docs.component.html

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<div
22
class="api-doc-button-container"
3-
[class]="(isDarkMode | async) ? 'dark-theme' : ''">
3+
[class]="(isDarkMode | async) ? 'dark-theme' : ''"
4+
style="display: flex; align-items: center; gap: 16px">
45
<button class="cancel-btn" mat-raised-button (click)="goBackToList()">
56
{{ 'goBack' | transloco }}
67
</button>
@@ -15,6 +16,18 @@
1516
<mat-form-field appearance="outline" class="api-keys-select">
1617
<mat-label>{{ 'apiDocs.apiKeys.label' | transloco }}</mat-label>
1718
<mat-select>
19+
<mat-option [value]="null">
20+
<div class="api-key-option">
21+
<div class="key-info">
22+
<span class="key-name"
23+
>None (Session token based authentication)</span
24+
>
25+
<span class="key-preview"
26+
>Uses session token to build the request</span
27+
>
28+
</div>
29+
</div>
30+
</mat-option>
1831
<mat-option *ngFor="let key of apiKeys" [value]="key.apiKey">
1932
<div class="api-key-option">
2033
<div class="key-info">
@@ -80,6 +93,19 @@
8093
*ngIf="serviceName"
8194
[apiDocJson]="apiDocJson"
8295
[serviceName]="serviceName"></df-api-quickstart>
96+
<div
97+
*ngIf="apiDocJson?.info?.group === 'Database'"
98+
style="margin: 16px 0 8px 0">
99+
<mat-slide-toggle
100+
[(ngModel)]="expandSchema"
101+
(ngModelChange)="reloadApiDocs()">
102+
Populate table/field names in API docs
103+
</mat-slide-toggle>
104+
<div style="font-size: 12px; color: #888; margin-left: 40px">
105+
When enabled, the API documentation will include live table and field
106+
names from your database. (May be slow for large databases)
107+
</div>
108+
</div>
83109
</div>
84110

85111
<div #apiDocumentation class="swagger-ui"></div>

src/app/adf-api-docs/df-api-docs/df-api-docs.component.ts

Lines changed: 87 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,19 @@ import {
4545
distinctUntilChanged,
4646
catchError,
4747
} from 'rxjs/operators';
48-
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
48+
import {
49+
HttpClient,
50+
HttpErrorResponse,
51+
HttpBackend,
52+
HttpHeaders,
53+
} from '@angular/common/http';
4954
import { BASE_URL } from 'src/app/shared/constants/urls';
5055
import { Subscription, of, forkJoin } from 'rxjs';
5156
import { DfApiQuickstartComponent } from '../df-api-quickstart/df-api-quickstart.component';
5257
import { ApiDocJson } from 'src/app/shared/types/files';
58+
import { healthCheckEndpointsInfo } from '../constants/health-check-endpoints';
59+
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
60+
import { FormsModule } from '@angular/forms';
5361

5462
interface ServiceResponse {
5563
resource: Array<{
@@ -77,6 +85,7 @@ interface HealthCheckResult {
7785
MatSelectModule,
7886
MatIconModule,
7987
TranslocoModule,
88+
FormsModule,
8089
AsyncPipe,
8190
NgIf,
8291
NgFor,
@@ -88,6 +97,8 @@ interface HealthCheckResult {
8897
MatExpansionModule,
8998
MatCardModule,
9099
DfApiQuickstartComponent,
100+
MatSlideToggleModule,
101+
FormsModule,
91102
],
92103
})
93104
export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
@@ -103,38 +114,15 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
103114
apiDocJson: ApiDocJson;
104115
apiKeys: ApiKeyInfo[] = [];
105116
faCopy = faCopy;
117+
expandSchema = false;
106118

107119
private subscriptions: Subscription[] = [];
108120
healthStatus: 'loading' | 'healthy' | 'unhealthy' | 'warning' = 'loading';
109121
healthError: string | null = null;
110122
serviceName: string | null = null;
111123
showUnhealthyErrorDetails = false;
112-
// Mapping of service types to their corresponding endpoints, probably would be better to move to the back-end
113-
healthCheckEndpointsInfo: {
114-
[key: string]: { endpoint: string; title: string; description: string }[];
115-
} = {
116-
Database: [
117-
{
118-
endpoint: '/_schema',
119-
title: 'View Available Schemas',
120-
description:
121-
'This command fetches a list of schemas from your connected database',
122-
},
123-
{
124-
endpoint: '/_table',
125-
title: 'View Tables in Your Database',
126-
description: 'This command lists all tables in your database',
127-
},
128-
],
129-
File: [
130-
{
131-
endpoint: '/',
132-
title: 'View Available Folders',
133-
description:
134-
'This command fetches a list of folders from your connected file storage',
135-
},
136-
],
137-
};
124+
125+
private rawHttp: HttpClient;
138126

139127
constructor(
140128
private activatedRoute: ActivatedRoute,
@@ -145,14 +133,17 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
145133
private clipboard: Clipboard,
146134
private snackBar: MatSnackBar,
147135
private currentServiceService: DfCurrentServiceService,
148-
private http: HttpClient
149-
) {}
136+
private http: HttpClient,
137+
private httpBackend: HttpBackend
138+
) {
139+
this.rawHttp = new HttpClient(httpBackend);
140+
}
150141
isDarkMode = this.themeService.darkMode$;
151142
ngOnInit(): void {
152143
// Get the service name from the route
153144
this.serviceName = this.activatedRoute.snapshot.params['name'];
154145

155-
// First fetch the service ID by name
146+
// First fetch the service ID by name (use normal http)
156147
if (this.serviceName) {
157148
this.subscriptions.push(
158149
this.http
@@ -201,51 +192,8 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
201192
}
202193

203194
ngAfterContentInit(): void {
204-
const apiDocumentation = this.apiDocJson;
205195
this.checkApiHealth();
206-
207-
SwaggerUI({
208-
spec: apiDocumentation,
209-
domNode: this.apiDocElement?.nativeElement,
210-
requestInterceptor: (req: SwaggerUI.Request) => {
211-
req['headers'][SESSION_TOKEN_HEADER] = this.userDataService.token;
212-
req['headers'][API_KEY_HEADER] = environment.dfApiDocsApiKey;
213-
// Parse the request URL
214-
const url = new URL(req['url']);
215-
const params = new URLSearchParams(url.search);
216-
// Decode all parameters
217-
params.forEach((value, key) => {
218-
params.set(key, decodeURIComponent(value));
219-
});
220-
// Update the URL with decoded parameters
221-
url.search = params.toString();
222-
req['url'] = url.toString();
223-
return req;
224-
},
225-
showMutatedRequest: true,
226-
onComplete: () => {
227-
if (
228-
this.apiDocElement &&
229-
this.apiDocElement.nativeElement &&
230-
this.swaggerInjectedContentContainerRef &&
231-
this.swaggerInjectedContentContainerRef.nativeElement
232-
) {
233-
const swaggerContainer = this.apiDocElement.nativeElement;
234-
const customContentNode =
235-
this.swaggerInjectedContentContainerRef.nativeElement;
236-
237-
const infoContainer = swaggerContainer.querySelector(
238-
'.information-container .main'
239-
);
240-
241-
this.injectCustomContent(
242-
swaggerContainer,
243-
infoContainer,
244-
customContentNode
245-
);
246-
}
247-
},
248-
});
196+
this.generateSwaggerWithApiKey(this.apiDocJson);
249197
}
250198

251199
ngOnDestroy(): void {
@@ -255,7 +203,7 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
255203

256204
private checkApiHealth(): void {
257205
let endpointsInfoToValidate =
258-
this.healthCheckEndpointsInfo[this.apiDocJson.info.group];
206+
healthCheckEndpointsInfo[this.apiDocJson.info.group];
259207
if (this.serviceName && endpointsInfoToValidate) {
260208
// Perform health check
261209
this.performHealthCheck(endpointsInfoToValidate[0].endpoint);
@@ -322,6 +270,70 @@ export class DfApiDocsComponent implements OnInit, AfterContentInit, OnDestroy {
322270
this.showUnhealthyErrorDetails = !this.showUnhealthyErrorDetails;
323271
}
324272

273+
private generateSwaggerWithApiKey(apiDocumentation: ApiDocJson): void {
274+
SwaggerUI({
275+
spec: apiDocumentation,
276+
domNode: this.apiDocElement?.nativeElement,
277+
requestInterceptor: (req: SwaggerUI.Request) => {
278+
req['headers'][SESSION_TOKEN_HEADER] = this.userDataService.token;
279+
req['headers'][API_KEY_HEADER] = environment.dfApiDocsApiKey;
280+
// Parse the request URL
281+
const url = new URL(req['url']);
282+
const params = new URLSearchParams(url.search);
283+
// Decode all parameters
284+
params.forEach((value, key) => {
285+
params.set(key, decodeURIComponent(value));
286+
});
287+
// Update the URL with decoded parameters
288+
url.search = params.toString();
289+
req['url'] = url.toString();
290+
return req;
291+
},
292+
showMutatedRequest: true,
293+
onComplete: () => {
294+
if (
295+
this.apiDocElement &&
296+
this.apiDocElement.nativeElement &&
297+
this.swaggerInjectedContentContainerRef &&
298+
this.swaggerInjectedContentContainerRef.nativeElement
299+
) {
300+
const swaggerContainer = this.apiDocElement.nativeElement;
301+
const customContentNode =
302+
this.swaggerInjectedContentContainerRef.nativeElement;
303+
304+
const infoContainer = swaggerContainer.querySelector(
305+
'.information-container .main'
306+
);
307+
308+
this.injectCustomContent(
309+
swaggerContainer,
310+
infoContainer,
311+
customContentNode
312+
);
313+
}
314+
},
315+
});
316+
}
317+
318+
reloadApiDocs() {
319+
if (!this.serviceName) return;
320+
const params = this.expandSchema ? '?expand_schema=true' : '';
321+
const headers = new HttpHeaders({
322+
'X-DreamFactory-API-Key': environment.dfApiDocsApiKey,
323+
'X-DreamFactory-Session-Token': this.userDataService.token || '',
324+
});
325+
this.rawHttp
326+
.get<any>(`${BASE_URL}/api_docs/${this.serviceName}${params}`, {
327+
headers,
328+
})
329+
.subscribe(data => {
330+
if (data) {
331+
this.apiDocJson = data;
332+
}
333+
this.ngAfterContentInit();
334+
});
335+
}
336+
325337
private injectCustomContent(
326338
swaggerContainer: HTMLElement,
327339
infoContainer: HTMLElement | null,

src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ <h3 class="curl-command-title themed-text">
5252
</p>
5353
</ng-template>
5454
</mat-expansion-panel>
55+
56+
<!-- API Testing Section -->
57+
<df-api-tester [apiDocJson]="apiDocJson" [serviceName]="serviceName">
58+
</df-api-tester>
5559
</mat-accordion>

src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,7 @@ mat-expansion-panel-header {
6060
background-color: #f93e3e; // red
6161
}
6262
}
63+
64+
.themed-text {
65+
color: var(--df-primary-text-color);
66+
}

src/app/adf-api-docs/df-api-quickstart/df-api-quickstart.component.ts

Lines changed: 6 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { SESSION_TOKEN_HEADER } from 'src/app/shared/constants/http-headers';
1515
import { ApiDocJson } from 'src/app/shared/types/files';
1616
import { MatDividerModule } from '@angular/material/divider';
1717
import { MatSnackBar } from '@angular/material/snack-bar';
18+
import { DfApiTesterComponent } from 'src/app/shared/components/df-api-tester/df-api-tester.component';
19+
import { healthCheckEndpointsInfo } from '../constants/health-check-endpoints';
1820

1921
interface CurlCommand {
2022
title: string;
@@ -24,32 +26,6 @@ interface CurlCommand {
2426
note: string;
2527
}
2628

27-
const healthCheckEndpointsInfo: {
28-
[key: string]: { endpoint: string; title: string; description: string }[];
29-
} = {
30-
Database: [
31-
{
32-
endpoint: '/_schema',
33-
title: 'View Available Schemas',
34-
description:
35-
'This command fetches a list of schemas from your connected database',
36-
},
37-
{
38-
endpoint: '/_table',
39-
title: 'View Tables in Your Database',
40-
description: 'This command lists all tables in your database',
41-
},
42-
],
43-
File: [
44-
{
45-
endpoint: '/',
46-
title: 'View Available Folders',
47-
description:
48-
'This command fetches a list of folders from your connected file storage',
49-
},
50-
],
51-
};
52-
5329
@Component({
5430
selector: 'df-api-quickstart',
5531
templateUrl: './df-api-quickstart.component.html',
@@ -65,6 +41,7 @@ const healthCheckEndpointsInfo: {
6541
FontAwesomeModule,
6642
MatDividerModule,
6743
MatButtonModule,
44+
DfApiTesterComponent,
6845
],
6946
})
7047
export class DfApiQuickstartComponent implements OnChanges {
@@ -115,7 +92,9 @@ export class DfApiQuickstartComponent implements OnChanges {
11592
description: endpointInfo.description,
11693
textForDisplay: commandForDisplay,
11794
textForCopy: commandForCopy,
118-
note: this.apiDocJson.paths[endpointInfo.endpoint]?.['get']?.summary,
95+
note:
96+
this.apiDocJson.paths[endpointInfo.endpoint]?.['get']?.summary ||
97+
'',
11998
});
12099
});
121100
}

0 commit comments

Comments
 (0)