Skip to content

Commit 93aca1b

Browse files
committed
feat(ws): automate generation of types and HTTP client layer from Swagger definitions
Signed-off-by: Guilherme Caponetto <[email protected]>
1 parent bd66a26 commit 93aca1b

File tree

86 files changed

+2189
-1576
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

86 files changed

+2189
-1576
lines changed

workspaces/frontend/package-lock.json

Lines changed: 582 additions & 28 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

workspaces/frontend/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"build:bundle-analyze": "webpack-bundle-analyzer ./bundle.stats.json",
2020
"build:clean": "rimraf ./dist",
2121
"build:prod": "webpack --config ./config/webpack.prod.js",
22+
"generate:api": "swagger-typescript-api generate -p ../backend/openapi/swagger.json -o ./src/generated --extract-request-body --responses --clean-output --axios --unwrap-response-data --modular && npm run prettier",
2223
"start:dev": "cross-env STYLE_THEME=$npm_config_theme webpack serve --hot --color --config ./config/webpack.dev.js",
2324
"start:dev:mock": "cross-env MOCK_API_ENABLED=true STYLE_THEME=$npm_config_theme npm run start:dev",
2425
"test": "run-s prettier:check test:lint test:unit test:cypress-ci",
@@ -113,6 +114,7 @@
113114
"@patternfly/react-table": "^6.2.0",
114115
"@patternfly/react-tokens": "^6.2.0",
115116
"@types/js-yaml": "^4.0.9",
117+
"axios": "^1.10.0",
116118
"date-fns": "^4.1.0",
117119
"eslint-plugin-local-rules": "^3.0.2",
118120
"js-yaml": "^4.1.0",
@@ -138,6 +140,7 @@
138140
"eslint-plugin-no-relative-import-paths": "^1.5.2",
139141
"eslint-plugin-prettier": "^5.0.0",
140142
"eslint-plugin-react": "^7.37.2",
141-
"eslint-plugin-react-hooks": "^5.0.0"
143+
"eslint-plugin-react-hooks": "^5.0.0",
144+
"swagger-typescript-api": "^13.2.7"
142145
}
143146
}

workspaces/frontend/src/__mocks__/mockNamespaces.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import { NamespacesNamespace } from '~/generated/data-contracts';
12
import { buildMockNamespace } from '~/shared/mock/mockBuilder';
2-
import { Namespace } from '~/shared/api/backendApiTypes';
33

4-
export const mockNamespaces: Namespace[] = [
4+
export const mockNamespaces: NamespacesNamespace[] = [
55
buildMockNamespace({ name: 'default' }),
66
buildMockNamespace({ name: 'kubeflow' }),
77
buildMockNamespace({ name: 'custom-namespace' }),
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1-
import { ResponseBody } from '~/shared/api/types';
1+
interface Envelope<T> {
2+
data: T;
3+
}
24

3-
export const mockBFFResponse = <T>(data: T): ResponseBody<T> => ({
5+
export const mockBFFResponse = <T>(data: T): Envelope<T> => ({
46
data,
57
});

workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspace.mock.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,34 @@
1-
import { WorkspaceState } from '~/shared/api/backendApiTypes';
2-
import type { Workspace, WorkspaceKindInfo } from '~/shared/api/backendApiTypes';
1+
import type { WorkspacesWorkspace, WorkspacesWorkspaceKindInfo } from '~/generated/data-contracts';
2+
import { WorkspacesWorkspaceState } from '~/generated/data-contracts';
33

44
const generateMockWorkspace = (
55
name: string,
66
namespace: string,
7-
state: WorkspaceState,
7+
state: WorkspacesWorkspaceState,
88
paused: boolean,
99
imageConfigId: string,
1010
imageConfigDisplayName: string,
1111
podConfigId: string,
1212
podConfigDisplayName: string,
1313
pvcName: string,
14-
): Workspace => {
14+
): WorkspacesWorkspace => {
1515
const pausedTime = new Date(2025, 0, 1).getTime();
1616
const lastActivityTime = new Date(2025, 0, 2).getTime();
1717
const lastUpdateTime = new Date(2025, 0, 3).getTime();
1818

1919
return {
2020
name,
2121
namespace,
22-
workspaceKind: { name: 'jupyterlab' } as WorkspaceKindInfo,
22+
workspaceKind: { name: 'jupyterlab' } as WorkspacesWorkspaceKindInfo,
2323
deferUpdates: paused,
2424
paused,
2525
pausedTime,
2626
pendingRestart: Math.random() < 0.5, //to generate randomly True/False value
2727
state,
2828
stateMessage:
29-
state === WorkspaceState.WorkspaceStateRunning
29+
state === WorkspacesWorkspaceState.WorkspaceStateRunning
3030
? 'Workspace is running smoothly.'
31-
: state === WorkspaceState.WorkspaceStatePaused
31+
: state === WorkspacesWorkspaceState.WorkspaceStatePaused
3232
? 'Workspace is paused.'
3333
: 'Workspace is operational.',
3434
podTemplate: {
@@ -104,11 +104,11 @@ const generateMockWorkspaces = (numWorkspaces: number, byNamespace = false) => {
104104
for (let i = 1; i <= numWorkspaces; i++) {
105105
const state =
106106
i % 3 === 0
107-
? WorkspaceState.WorkspaceStateError
107+
? WorkspacesWorkspaceState.WorkspaceStateError
108108
: i % 2 === 0
109-
? WorkspaceState.WorkspaceStatePaused
110-
: WorkspaceState.WorkspaceStateRunning;
111-
const paused = state === WorkspaceState.WorkspaceStatePaused;
109+
? WorkspacesWorkspaceState.WorkspaceStatePaused
110+
: WorkspacesWorkspaceState.WorkspaceStateRunning;
111+
const paused = state === WorkspacesWorkspaceState.WorkspaceStatePaused;
112112
const name = `workspace-${i}`;
113113
const namespace = namespaces[i % namespaces.length];
114114
const pvcName = `data-pvc-${i}`;

workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaceKinds.mock.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
1-
import type { WorkspaceKind } from '~/shared/api/backendApiTypes';
1+
import {
2+
WorkspacekindsRedirectMessageLevel,
3+
type WorkspacekindsWorkspaceKind,
4+
} from '~/generated/data-contracts';
25

36
// Factory function to create a valid WorkspaceKind
4-
function createMockWorkspaceKind(overrides: Partial<WorkspaceKind> = {}): WorkspaceKind {
7+
function createMockWorkspaceKind(
8+
overrides: Partial<WorkspacekindsWorkspaceKind> = {},
9+
): WorkspacekindsWorkspaceKind {
510
return {
611
name: 'jupyter-lab',
712
displayName: 'JupyterLab Notebook',
@@ -27,14 +32,15 @@ function createMockWorkspaceKind(overrides: Partial<WorkspaceKind> = {}): Worksp
2732
values: [
2833
{
2934
id: 'jupyterlab_scipy_180',
35+
description: 'JupyterLab with SciPy 1.8.0',
3036
displayName: 'jupyter-scipy:v1.8.0',
31-
labels: { pythonVersion: '3.11' },
37+
labels: [{ key: 'pythonVersion', value: '3.11' }],
3238
hidden: true,
3339
redirect: {
3440
to: 'jupyterlab_scipy_190',
3541
message: {
3642
text: 'This update will change...',
37-
level: 'Info',
43+
level: WorkspacekindsRedirectMessageLevel.RedirectMessageLevelInfo,
3844
},
3945
},
4046
},
@@ -45,9 +51,13 @@ function createMockWorkspaceKind(overrides: Partial<WorkspaceKind> = {}): Worksp
4551
values: [
4652
{
4753
id: 'tiny_cpu',
54+
hidden: false,
4855
displayName: 'Tiny CPU',
4956
description: 'Pod with 0.1 CPU, 128 Mb RAM',
50-
labels: { cpu: '100m', memory: '128Mi' },
57+
labels: [
58+
{ key: 'cpu', value: '100m' },
59+
{ key: 'memory', value: '128Mi' },
60+
],
5161
},
5262
],
5363
},

workspaces/frontend/src/__tests__/cypress/cypress/tests/mocked/workspaces/Workspaces.cy.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import type { Workspace } from '~/shared/api/backendApiTypes';
1+
import { mockNamespaces } from '~/__mocks__/mockNamespaces';
2+
import { mockBFFResponse } from '~/__mocks__/utils';
23
import { home } from '~/__tests__/cypress/cypress/pages/home';
34
import {
45
mockWorkspaces,
56
mockWorkspacesByNS,
67
} from '~/__tests__/cypress/cypress/tests/mocked/workspace.mock';
7-
import { mockNamespaces } from '~/__mocks__/mockNamespaces';
8-
import { mockBFFResponse } from '~/__mocks__/utils';
8+
import type { WorkspacesWorkspace } from '~/generated/data-contracts';
99

1010
// Helper function to validate the content of a single workspace row in the table
11-
const validateWorkspaceRow = (workspace: Workspace, index: number) => {
11+
const validateWorkspaceRow = (workspace: WorkspacesWorkspace, index: number) => {
1212
// Validate the workspace name
1313
cy.findByTestId(`workspace-row-${index}`)
1414
.find('[data-testid="workspace-name"]')

workspaces/frontend/src/app/actions/WorkspaceKindsActions.tsx

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { WorkspaceKind, WorkspaceOptionRedirect } from '~/shared/api/backendApiTypes';
1+
import {
2+
WorkspacekindsOptionRedirect,
3+
WorkspacekindsWorkspaceKind,
4+
} from '~/generated/data-contracts';
25

36
type KindLogoDict = Record<string, string>;
47

@@ -7,7 +10,9 @@ type KindLogoDict = Record<string, string>;
710
* @param {WorkspaceKind[]} workspaceKinds - The list of workspace kinds.
811
* @returns {KindLogoDict} A dictionary with kind names as keys and logo URLs as values.
912
*/
10-
export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): KindLogoDict {
13+
export function buildKindLogoDictionary(
14+
workspaceKinds: WorkspacekindsWorkspaceKind[] | [],
15+
): KindLogoDict {
1116
const kindLogoDict: KindLogoDict = {};
1217

1318
for (const workspaceKind of workspaceKinds) {
@@ -20,15 +25,15 @@ export function buildKindLogoDictionary(workspaceKinds: WorkspaceKind[] | []): K
2025
return kindLogoDict;
2126
}
2227

23-
type WorkspaceRedirectStatus = Record<string, WorkspaceOptionRedirect | undefined>;
28+
type WorkspaceRedirectStatus = Record<string, WorkspacekindsOptionRedirect | undefined>;
2429

2530
/**
2631
* Builds a dictionary of workspace kinds to redirect statuses.
2732
* @param {WorkspaceKind[]} workspaceKinds - The list of workspace kinds.
2833
* @returns {WorkspaceRedirectStatus} A dictionary with kind names as keys and redirect status objects as values.
2934
*/
3035
export function buildWorkspaceRedirectStatus(
31-
workspaceKinds: WorkspaceKind[] | [],
36+
workspaceKinds: WorkspacekindsWorkspaceKind[] | [],
3237
): WorkspaceRedirectStatus {
3338
const workspaceRedirectStatus: WorkspaceRedirectStatus = {};
3439
for (const workspaceKind of workspaceKinds) {

workspaces/frontend/src/app/components/ValidationErrorAlert.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import React from 'react';
22
import { Alert } from '@patternfly/react-core/dist/esm/components/Alert';
33
import { List, ListItem } from '@patternfly/react-core/dist/esm/components/List';
4-
import { ValidationError } from '~/shared/api/backendApiTypes';
5-
import { ErrorEnvelopeException } from '~/shared/api/apiUtils';
4+
import { ApiValidationError } from '~/generated/data-contracts';
65

76
interface ValidationErrorAlertProps {
87
title: string;
9-
errors: (ValidationError | ErrorEnvelopeException)[];
8+
errors: ApiValidationError[];
109
}
1110

1211
export const ValidationErrorAlert: React.FC<ValidationErrorAlertProps> = ({ title, errors }) => {
@@ -18,7 +17,9 @@ export const ValidationErrorAlert: React.FC<ValidationErrorAlertProps> = ({ titl
1817
<Alert variant="danger" title={title} isInline>
1918
<List>
2019
{errors.map((error, index) => (
21-
<ListItem key={index}>{error.message}</ListItem>
20+
<ListItem key={index}>
21+
{error.message}: &apos;{error.field}&apos;
22+
</ListItem>
2223
))}
2324
</List>
2425
</Alert>

workspaces/frontend/src/app/components/WorkspaceTable.tsx

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ import { ExclamationTriangleIcon } from '@patternfly/react-icons/dist/esm/icons/
3030
import { TimesCircleIcon } from '@patternfly/react-icons/dist/esm/icons/times-circle-icon';
3131
import { QuestionCircleIcon } from '@patternfly/react-icons/dist/esm/icons/question-circle-icon';
3232
import { formatDistanceToNow } from 'date-fns/formatDistanceToNow';
33-
import { Workspace, WorkspaceState } from '~/shared/api/backendApiTypes';
3433
import {
3534
DataFieldKey,
3635
defineDataFields,
@@ -53,6 +52,7 @@ import {
5352
formatWorkspaceIdleState,
5453
} from '~/shared/utilities/WorkspaceUtils';
5554
import { ExpandedWorkspaceRow } from '~/app/pages/Workspaces/ExpandedWorkspaceRow';
55+
import { WorkspacesWorkspace, WorkspacesWorkspaceState } from '~/generated/data-contracts';
5656

5757
const {
5858
fields: wsTableColumns,
@@ -78,12 +78,12 @@ type WorkspaceTableSortableColumnKeys = SortableDataFieldKey<typeof wsTableColum
7878
export type WorkspaceTableFilteredColumn = FilteredColumn<WorkspaceTableFilterableColumnKeys>;
7979

8080
interface WorkspaceTableProps {
81-
workspaces: Workspace[];
81+
workspaces: WorkspacesWorkspace[];
8282
canCreateWorkspaces?: boolean;
8383
canExpandRows?: boolean;
8484
initialFilters?: WorkspaceTableFilteredColumn[];
8585
hiddenColumns?: WorkspaceTableColumnKeys[];
86-
rowActions?: (workspace: Workspace) => IActions;
86+
rowActions?: (workspace: WorkspacesWorkspace) => IActions;
8787
}
8888

8989
export interface WorkspaceTableRef {
@@ -162,7 +162,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
162162
navigate('workspaceCreate');
163163
}, [navigate]);
164164

165-
const setWorkspaceExpanded = (workspace: Workspace, isExpanding = true) =>
165+
const setWorkspaceExpanded = (workspace: WorkspacesWorkspace, isExpanding = true) =>
166166
setExpandedWorkspacesNames((prevExpanded) => {
167167
const newExpandedWorkspacesNames = prevExpanded.filter(
168168
(wsName) => wsName !== workspace.name,
@@ -172,7 +172,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
172172
: newExpandedWorkspacesNames;
173173
});
174174

175-
const isWorkspaceExpanded = (workspace: Workspace) =>
175+
const isWorkspaceExpanded = (workspace: WorkspacesWorkspace) =>
176176
expandedWorkspacesNames.includes(workspace.name);
177177

178178
const filteredWorkspaces = useMemo(() => {
@@ -214,7 +214,7 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
214214
// Column sorting
215215

216216
const getSortableRowValues = (
217-
workspace: Workspace,
217+
workspace: WorkspacesWorkspace,
218218
): Record<WorkspaceTableSortableColumnKeys, string | number> => ({
219219
name: workspace.name,
220220
kind: workspace.workspaceKind.name,
@@ -299,19 +299,19 @@ const WorkspaceTable = React.forwardRef<WorkspaceTableRef, WorkspaceTableProps>(
299299
}
300300
};
301301

302-
const extractStateColor = (state: WorkspaceState) => {
302+
const extractStateColor = (state: WorkspacesWorkspaceState) => {
303303
switch (state) {
304-
case WorkspaceState.WorkspaceStateRunning:
304+
case WorkspacesWorkspaceState.WorkspaceStateRunning:
305305
return 'green';
306-
case WorkspaceState.WorkspaceStatePending:
306+
case WorkspacesWorkspaceState.WorkspaceStatePending:
307307
return 'orange';
308-
case WorkspaceState.WorkspaceStateTerminating:
308+
case WorkspacesWorkspaceState.WorkspaceStateTerminating:
309309
return 'yellow';
310-
case WorkspaceState.WorkspaceStateError:
310+
case WorkspacesWorkspaceState.WorkspaceStateError:
311311
return 'red';
312-
case WorkspaceState.WorkspaceStatePaused:
312+
case WorkspacesWorkspaceState.WorkspaceStatePaused:
313313
return 'purple';
314-
case WorkspaceState.WorkspaceStateUnknown:
314+
case WorkspacesWorkspaceState.WorkspaceStateUnknown:
315315
default:
316316
return 'grey';
317317
}

0 commit comments

Comments
 (0)