diff --git a/clients/ui/frontend/package-lock.json b/clients/ui/frontend/package-lock.json index 57bb275f..6f7bfc21 100644 --- a/clients/ui/frontend/package-lock.json +++ b/clients/ui/frontend/package-lock.json @@ -13,6 +13,7 @@ "@patternfly/react-icons": "6.0.0-alpha.37", "@patternfly/react-styles": "6.0.0-alpha.35", "@patternfly/react-table": "6.0.0-alpha.101", + "@patternfly/react-templates": "6.0.0-alpha.50", "classnames": "^2.2.6", "dompurify": "^3.1.6", "lodash-es": "^4.17.15", @@ -3647,6 +3648,23 @@ "react-dom": "^17 || ^18" } }, + "node_modules/@patternfly/react-templates": { + "version": "6.0.0-alpha.50", + "resolved": "https://registry.npmjs.org/@patternfly/react-templates/-/react-templates-6.0.0-alpha.50.tgz", + "integrity": "sha512-YmP9iYcejDrnGPadi5Y/qZWG4xmANZe3fB8HMhSWI+CewOAWCifkWC/gv0oaB3eDXCopAnsf0Y6oXR7CNdWgyQ==", + "license": "MIT", + "dependencies": { + "@patternfly/react-core": "^6.0.0-alpha.100", + "@patternfly/react-icons": "^6.0.0-alpha.35", + "@patternfly/react-styles": "^6.0.0-alpha.34", + "@patternfly/react-tokens": "^6.0.0-alpha.34", + "tslib": "^2.6.3" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, "node_modules/@patternfly/react-tokens": { "version": "6.0.0-prerelease.4", "resolved": "https://registry.npmjs.org/@patternfly/react-tokens/-/react-tokens-6.0.0-prerelease.4.tgz", diff --git a/clients/ui/frontend/package.json b/clients/ui/frontend/package.json index 99b0c619..56b47de5 100644 --- a/clients/ui/frontend/package.json +++ b/clients/ui/frontend/package.json @@ -94,6 +94,7 @@ "@patternfly/react-icons": "6.0.0-alpha.37", "@patternfly/react-styles": "6.0.0-alpha.35", "@patternfly/react-table": "6.0.0-alpha.101", + "@patternfly/react-templates": "6.0.0-alpha.50", "lodash-es": "^4.17.15", "npm-run-all": "^4.1.5", "react": "^18", diff --git a/clients/ui/frontend/src/__mocks__/mockBFFResponse.ts b/clients/ui/frontend/src/__mocks__/mockBFFResponse.ts index 8b2f910b..f1599462 100644 --- a/clients/ui/frontend/src/__mocks__/mockBFFResponse.ts +++ b/clients/ui/frontend/src/__mocks__/mockBFFResponse.ts @@ -1,5 +1,5 @@ -import { ModelRegistryResponse } from '~/app/types'; +import { ModelRegistryBody } from '~/app/types'; -export const mockBFFResponse = (data: T): ModelRegistryResponse => ({ +export const mockBFFResponse = (data: T): ModelRegistryBody => ({ data, }); diff --git a/clients/ui/frontend/src/__mocks__/utils.ts b/clients/ui/frontend/src/__mocks__/utils.ts index 1500da7d..43d1768e 100644 --- a/clients/ui/frontend/src/__mocks__/utils.ts +++ b/clients/ui/frontend/src/__mocks__/utils.ts @@ -1,6 +1,6 @@ import { ModelRegistryMetadataType, - ModelRegistryResponse, + ModelRegistryBody, ModelRegistryStringCustomProperties, } from '~/app/types'; @@ -16,6 +16,6 @@ export const createModelRegistryLabelsObject = ( return acc; }, {} as ModelRegistryStringCustomProperties); -export const mockBFFResponse = (data: T): ModelRegistryResponse => ({ +export const mockBFFResponse = (data: T): ModelRegistryBody => ({ data, }); diff --git a/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/api.ts b/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/api.ts index 1b07dfb8..ceaef6fd 100644 --- a/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/api.ts +++ b/clients/ui/frontend/src/__tests__/cypress/cypress/support/commands/api.ts @@ -3,7 +3,7 @@ import type { ModelArtifact, ModelArtifactList, ModelRegistry, - ModelRegistryResponse, + ModelRegistryBody, ModelVersion, ModelVersionList, RegisteredModel, @@ -35,7 +35,7 @@ declare global { interceptApi: (( type: 'GET /api/:apiVersion/model_registry/:modelRegistryName/registered_models', options: { path: { modelRegistryName: string; apiVersion: string } }, - response: ApiResponse>, + response: ApiResponse>, ) => Cypress.Chainable) & (( type: 'POST /api/:apiVersion/model_registry/:modelRegistryName/registered_models', @@ -47,7 +47,7 @@ declare global { options: { path: { modelRegistryName: string; apiVersion: string; registeredModelId: number }; }, - response: ApiResponse>, + response: ApiResponse>, ) => Cypress.Chainable) & (( type: 'POST /api/:apiVersion/model_registry/:modelRegistryName/registered_models/:registeredModelId/versions', @@ -61,28 +61,28 @@ declare global { options: { path: { modelRegistryName: string; apiVersion: string; registeredModelId: number }; }, - response: ApiResponse>, + response: ApiResponse>, ) => Cypress.Chainable) & (( type: 'PATCH /api/:apiVersion/model_registry/:modelRegistryName/registered_models/:registeredModelId', options: { path: { modelRegistryName: string; apiVersion: string; registeredModelId: number }; }, - response: ApiResponse>, + response: ApiResponse>, ) => Cypress.Chainable) & (( type: 'GET /api/:apiVersion/model_registry/:modelRegistryName/model_versions/:modelVersionId', options: { path: { modelRegistryName: string; apiVersion: string; modelVersionId: number }; }, - response: ApiResponse>, + response: ApiResponse>, ) => Cypress.Chainable) & (( type: 'GET /api/:apiVersion/model_registry/:modelRegistryName/model_versions/:modelVersionId/artifacts', options: { path: { modelRegistryName: string; apiVersion: string; modelVersionId: number }; }, - response: ApiResponse>, + response: ApiResponse>, ) => Cypress.Chainable) & (( type: 'POST /api/:apiVersion/model_registry/:modelRegistryName/model_versions/:modelVersionId/artifacts', @@ -101,7 +101,7 @@ declare global { (( type: 'GET /api/:apiVersion/model_registry', options: { path: { apiVersion: string } }, - response: ApiResponse>, + response: ApiResponse>, ) => Cypress.Chainable); } } diff --git a/clients/ui/frontend/src/app/api/__tests__/service.spec.ts b/clients/ui/frontend/src/app/api/__tests__/service.spec.ts index 7da6df75..85be9763 100644 --- a/clients/ui/frontend/src/app/api/__tests__/service.spec.ts +++ b/clients/ui/frontend/src/app/api/__tests__/service.spec.ts @@ -28,6 +28,7 @@ jest.mock('~/app/api/apiUtils', () => ({ restCREATE: jest.fn(() => mockRestPromise), restGET: jest.fn(() => mockRestPromise), restPATCH: jest.fn(() => mockRestPromise), + assembleModelRegistryBody: jest.fn(() => ({})), isModelRegistryResponse: jest.fn(() => true), })); @@ -61,7 +62,7 @@ describe('createRegisteredModel', () => { expect(restCREATEMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/registered_models`, - mockData, + {}, {}, APIOptionsMock, ); @@ -89,7 +90,7 @@ describe('createModelVersion', () => { expect(restCREATEMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/model_versions`, - mockData, + {}, {}, APIOptionsMock, ); @@ -117,7 +118,7 @@ describe('createModelVersionForRegisteredModel', () => { expect(restCREATEMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/registered_models/1/versions`, - mockData, + {}, {}, APIOptionsMock, ); @@ -150,7 +151,7 @@ describe('createModelArtifact', () => { expect(restCREATEMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/model_artifacts`, - mockData, + {}, {}, APIOptionsMock, ); @@ -183,7 +184,7 @@ describe('createModelArtifactForModelVersion', () => { expect(restCREATEMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/model_versions/2/artifacts`, - mockData, + {}, {}, APIOptionsMock, ); @@ -347,7 +348,7 @@ describe('patchRegisteredModel', () => { expect(restPATCHMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/registered_models/1`, - mockData, + {}, APIOptionsMock, ); expect(handleRestFailuresMock).toHaveBeenCalledTimes(1); @@ -366,7 +367,7 @@ describe('patchModelVersion', () => { expect(restPATCHMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/model_versions/1`, - mockData, + {}, APIOptionsMock, ); expect(handleRestFailuresMock).toHaveBeenCalledTimes(1); @@ -385,7 +386,7 @@ describe('patchModelArtifact', () => { expect(restPATCHMock).toHaveBeenCalledWith( `/api/${BFF_API_VERSION}/model_registry/model-registry-1/`, `/model_artifacts/1`, - mockData, + {}, APIOptionsMock, ); expect(handleRestFailuresMock).toHaveBeenCalledTimes(1); diff --git a/clients/ui/frontend/src/app/api/apiUtils.ts b/clients/ui/frontend/src/app/api/apiUtils.ts index 93e16681..d14733c7 100644 --- a/clients/ui/frontend/src/app/api/apiUtils.ts +++ b/clients/ui/frontend/src/app/api/apiUtils.ts @@ -1,6 +1,6 @@ import { APIOptions } from '~/app/api/types'; import { EitherOrNone } from '~/typeHelpers'; -import { ModelRegistryResponse } from '~/app/types'; +import { ModelRegistryBody } from '~/app/types'; export const mergeRequestInit = ( opts: APIOptions = {}, @@ -163,14 +163,16 @@ export const restDELETE = ( parseJSON: options?.parseJSON, }); -export const isModelRegistryResponse = ( - response: unknown, -): response is ModelRegistryResponse => { +export const isModelRegistryResponse = (response: unknown): response is ModelRegistryBody => { if (typeof response === 'object' && response !== null) { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - const modelRegistryResponse = response as { data?: T }; + const modelRegistryBody = response as { data?: T }; // TODO: Check if data is conforming any type so we have a proper check - return modelRegistryResponse.data !== undefined; + return modelRegistryBody.data !== undefined; } return false; }; + +export const assembleModelRegistryBody = (data: T): ModelRegistryBody => ({ + data, +}); diff --git a/clients/ui/frontend/src/app/api/service.ts b/clients/ui/frontend/src/app/api/service.ts index b894c6df..3579877d 100644 --- a/clients/ui/frontend/src/app/api/service.ts +++ b/clients/ui/frontend/src/app/api/service.ts @@ -9,26 +9,34 @@ import { RegisteredModelList, RegisteredModel, } from '~/app/types'; -import { isModelRegistryResponse, restCREATE, restGET, restPATCH } from '~/app/api/apiUtils'; +import { + assembleModelRegistryBody, + isModelRegistryResponse, + restCREATE, + restGET, + restPATCH, +} from '~/app/api/apiUtils'; import { APIOptions } from '~/app/api/types'; import { handleRestFailures } from '~/app/api/errorUtils'; export const createRegisteredModel = (hostPath: string) => (opts: APIOptions, data: CreateRegisteredModelData): Promise => - handleRestFailures(restCREATE(hostPath, `/registered_models`, data, {}, opts)).then( - (response) => { - if (isModelRegistryResponse(response)) { - return response.data; - } - throw new Error('Invalid response format'); - }, - ); + handleRestFailures( + restCREATE(hostPath, `/registered_models`, assembleModelRegistryBody(data), {}, opts), + ).then((response) => { + if (isModelRegistryResponse(response)) { + return response.data; + } + throw new Error('Invalid response format'); + }); export const createModelVersion = (hostPath: string) => (opts: APIOptions, data: CreateModelVersionData): Promise => - handleRestFailures(restCREATE(hostPath, `/model_versions`, data, {}, opts)).then((response) => { + handleRestFailures( + restCREATE(hostPath, `/model_versions`, assembleModelRegistryBody(data), {}, opts), + ).then((response) => { if (isModelRegistryResponse(response)) { return response.data; } @@ -43,7 +51,13 @@ export const createModelVersionForRegisteredModel = data: CreateModelVersionData, ): Promise => handleRestFailures( - restCREATE(hostPath, `/registered_models/${registeredModelId}/versions`, data, {}, opts), + restCREATE( + hostPath, + `/registered_models/${registeredModelId}/versions`, + assembleModelRegistryBody(data), + {}, + opts, + ), ).then((response) => { if (isModelRegistryResponse(response)) { return response.data; @@ -54,14 +68,14 @@ export const createModelVersionForRegisteredModel = export const createModelArtifact = (hostPath: string) => (opts: APIOptions, data: CreateModelArtifactData): Promise => - handleRestFailures(restCREATE(hostPath, `/model_artifacts`, data, {}, opts)).then( - (response) => { - if (isModelRegistryResponse(response)) { - return response.data; - } - throw new Error('Invalid response format'); - }, - ); + handleRestFailures( + restCREATE(hostPath, `/model_artifacts`, assembleModelRegistryBody(data), {}, opts), + ).then((response) => { + if (isModelRegistryResponse(response)) { + return response.data; + } + throw new Error('Invalid response format'); + }); export const createModelArtifactForModelVersion = (hostPath: string) => @@ -71,7 +85,13 @@ export const createModelArtifactForModelVersion = data: CreateModelArtifactData, ): Promise => handleRestFailures( - restCREATE(hostPath, `/model_versions/${modelVersionId}/artifacts`, data, {}, opts), + restCREATE( + hostPath, + `/model_versions/${modelVersionId}/artifacts`, + assembleModelRegistryBody(data), + {}, + opts, + ), ).then((response) => { if (isModelRegistryResponse(response)) { return response.data; @@ -177,7 +197,12 @@ export const patchRegisteredModel = registeredModelId: string, ): Promise => handleRestFailures( - restPATCH(hostPath, `/registered_models/${registeredModelId}`, data, opts), + restPATCH( + hostPath, + `/registered_models/${registeredModelId}`, + assembleModelRegistryBody(data), + opts, + ), ).then((response) => { if (isModelRegistryResponse(response)) { return response.data; @@ -188,14 +213,19 @@ export const patchRegisteredModel = export const patchModelVersion = (hostPath: string) => (opts: APIOptions, data: Partial, modelversionId: string): Promise => - handleRestFailures(restPATCH(hostPath, `/model_versions/${modelversionId}`, data, opts)).then( - (response) => { - if (isModelRegistryResponse(response)) { - return response.data; - } - throw new Error('Invalid response format'); - }, - ); + handleRestFailures( + restPATCH( + hostPath, + `/model_versions/${modelversionId}`, + assembleModelRegistryBody(data), + opts, + ), + ).then((response) => { + if (isModelRegistryResponse(response)) { + return response.data; + } + throw new Error('Invalid response format'); + }); export const patchModelArtifact = (hostPath: string) => @@ -204,11 +234,16 @@ export const patchModelArtifact = data: Partial, modelartifactId: string, ): Promise => - handleRestFailures(restPATCH(hostPath, `/model_artifacts/${modelartifactId}`, data, opts)).then( - (response) => { - if (isModelRegistryResponse(response)) { - return response.data; - } - throw new Error('Invalid response format'); - }, - ); + handleRestFailures( + restPATCH( + hostPath, + `/model_artifacts/${modelartifactId}`, + assembleModelRegistryBody(data), + opts, + ), + ).then((response) => { + if (isModelRegistryResponse(response)) { + return response.data; + } + throw new Error('Invalid response format'); + }); diff --git a/clients/ui/frontend/src/app/components/MarkdownView.scss b/clients/ui/frontend/src/app/components/MarkdownView.scss index aa367b23..7192c5c2 100644 --- a/clients/ui/frontend/src/app/components/MarkdownView.scss +++ b/clients/ui/frontend/src/app/components/MarkdownView.scss @@ -2,10 +2,10 @@ word-break: break-word; &--with-padding { - padding-bottom: var(--pf-v5-global--spacer--md); + padding-bottom: var(--pf-v6-global--spacer--md); p { - margin-bottom: var(--pf-v5-global--spacer--sm); + margin-bottom: var(--pf-v6-global--spacer--sm); } } @@ -15,35 +15,35 @@ h4, h5, h6 { - font-family: var(--pf-v5-global--FontFamily--heading--sans-serif); - font-weight: var(--pf-v5-global--FontWeight--normal); - margin-top: var(--pf-v5-global--spacer--md); - margin-bottom: var(--pf-v5-global--spacer--sm); + font-family: var(--pf-v6-global--FontFamily--heading--sans-serif); + font-weight: var(--pf-v6-global--FontWeight--normal); + margin-top: var(--pf-v6-global--spacer--md); + margin-bottom: var(--pf-v6-global--spacer--sm); } h1 { - font-size: var(--pf-v5-global--FontSize--2xl); + font-size: var(--pf-v6-global--FontSize--2xl); } h2 { - font-size: var(--pf-v5-global--FontSize--xl); + font-size: var(--pf-v6-global--FontSize--xl); } h3 { - font-size: var(--pf-v5-global--FontSize--lg); + font-size: var(--pf-v6-global--FontSize--lg); } h4, h5, h6 { - font-size: var(--pf-v5-global--FontSize--md); - margin-top: var(--pf-v5-global--spacer--sm); + font-size: var(--pf-v6-global--FontSize--md); + margin-top: var(--pf-v6-global--spacer--sm); } ul, ol { margin-top: 0; - margin-bottom: var(--pf-v5-global--spacer--sm); + margin-bottom: var(--pf-v6-global--spacer--sm); } ul { @@ -51,33 +51,33 @@ } li { - margin-left: var(--pf-v5-global--spacer--lg); + margin-left: var(--pf-v6-global--spacer--lg); } code, pre { font-family: Menlo, Monaco, Consolas, 'Courier New', monospace; - background-color: var(--pf-v5-global--BackgroundColor--200); - border-radius: var(--pf-v5-global--BorderRadius--sm); + background-color: var(--pf-v6-global--BackgroundColor--200); + border-radius: var(--pf-v6-global--BorderRadius--sm); } code { padding: 2px 4px; font-size: 85%; - color: var(--pf-v5-global--danger-color--100); + color: var(--pf-v6-global--danger-color--100); } pre { display: block; - padding: var(--pf-v5-global--spacer--sm); - margin-bottom: var(--pf-v5-global--spacer--sm); - font-size: var(--pf-v5-global--FontSize--sm); - color: var(--pf-v5-global--Color--300); + padding: var(--pf-v6-global--spacer--sm); + margin-bottom: var(--pf-v6-global--spacer--sm); + font-size: var(--pf-v6-global--FontSize--sm); + color: var(--pf-v6-global--Color--300); word-break: break-all; word-wrap: break-word; - background-color: var(--pf-v5-global--BackgroundColor--200); - border: var(--pf-v5-global--BorderWidth--sm) solid var(--pf-v5-global--Color--light-300); - border-radius: var(--pf-v5-global--BorderRadius--sm); + background-color: var(--pf-v6-global--BackgroundColor--200); + border: var(--pf-v6-global--BorderWidth--sm) solid var(--pf-v6-global--Color--light-300); + border-radius: var(--pf-v6-global--BorderRadius--sm); code { padding: 0; font-size: inherit; diff --git a/clients/ui/frontend/src/app/components/SimpleSelect.scss b/clients/ui/frontend/src/app/components/SimpleSelect.scss index 30dd3051..31c2011c 100644 --- a/clients/ui/frontend/src/app/components/SimpleSelect.scss +++ b/clients/ui/frontend/src/app/components/SimpleSelect.scss @@ -4,6 +4,6 @@ // remove this file when https://github.com/patternfly/patternfly/issues/6062 is solved .truncate-no-min-width { - --pf-v5-c-truncate--MinWidth: 0; - --pf-v5-c-truncate__start--MinWidth: 0; + --pf-v6-c-truncate--MinWidth: 0; + --pf-v6-c-truncate__start--MinWidth: 0; } diff --git a/clients/ui/frontend/src/app/components/design/DividedGallery.scss b/clients/ui/frontend/src/app/components/design/DividedGallery.scss index 002cf987..31662f09 100644 --- a/clients/ui/frontend/src/app/components/design/DividedGallery.scss +++ b/clients/ui/frontend/src/app/components/design/DividedGallery.scss @@ -1,27 +1,27 @@ .kubeflowdivided-gallery { position: relative; - background-color: var(--pf-v5-global--BackgroundColor--100); + background-color: var(--pf-v6-global--BackgroundColor--100); &__border { width: 2px; position: absolute; - top: var(--pf-v5-global--spacer--md); - bottom: var(--pf-v5-global--spacer--md); + top: var(--pf-v6-global--spacer--md); + bottom: var(--pf-v6-global--spacer--md); left: 0; background-color: white; content: ' '; } &__item { - border-left: 1px solid var(--pf-v5-global--BorderColor--100); - padding: 0 var(--pf-v5-global--spacer--xl); - margin: var(--pf-v5-global--spacer--md) 0; + border-left: 1px solid var(--pf-v6-global--BorderColor--100); + padding: 0 var(--pf-v6-global--spacer--xl); + margin: var(--pf-v6-global--spacer--md) 0; } - .pf-v5-l-gallery { + .pf-v6-l-gallery { position: relative; } &__close { position: absolute; - top: var(--pf-v5-global--spacer--xs); + top: var(--pf-v6-global--spacer--xs); right: 0; } } diff --git a/clients/ui/frontend/src/app/components/design/InfoGalleryItem.tsx b/clients/ui/frontend/src/app/components/design/InfoGalleryItem.tsx index 3ce5f3f1..eaf8d9f9 100644 --- a/clients/ui/frontend/src/app/components/design/InfoGalleryItem.tsx +++ b/clients/ui/frontend/src/app/components/design/InfoGalleryItem.tsx @@ -66,8 +66,8 @@ const InfoGalleryItem: React.FC = ({ isInline onClick={onClick} style={{ - fontSize: 'var(--pf-v5-global--FontSize--md)', - fontWeight: 'var(--pf-v5-global--FontWeight--bold)', + fontSize: 'var(--pf-v6-global--FontSize--md)', + fontWeight: 'var(--pf-v6-global--FontWeight--bold)', }} > {title} diff --git a/clients/ui/frontend/src/app/components/design/ScrolledGallery.tsx b/clients/ui/frontend/src/app/components/design/ScrolledGallery.tsx index 3d619970..4c85f141 100644 --- a/clients/ui/frontend/src/app/components/design/ScrolledGallery.tsx +++ b/clients/ui/frontend/src/app/components/design/ScrolledGallery.tsx @@ -22,8 +22,8 @@ const ScrolledGallery: React.FC = ({ display: 'grid', gridAutoFlow: 'column', overflowY: 'auto', - gap: 'var(--pf-v5-global--spacer--md)', - paddingBottom: 'var(--pf-v5-global--spacer--sm)', + gap: 'var(--pf-v6-global--spacer--md)', + paddingBottom: 'var(--pf-v6-global--spacer--sm)', }} {...rest} > diff --git a/clients/ui/frontend/src/app/components/design/TypeBorderCard.scss b/clients/ui/frontend/src/app/components/design/TypeBorderCard.scss index 799ac8c3..ea20bbf8 100644 --- a/clients/ui/frontend/src/app/components/design/TypeBorderCard.scss +++ b/clients/ui/frontend/src/app/components/design/TypeBorderCard.scss @@ -1,7 +1,7 @@ .kubeflowtype-bordered-card { position: relative; border-radius: 16px; - border: 1px solid var(--pf-v5-global--BorderColor--100); + border: 1px solid var(--pf-v6-global--BorderColor--100); padding: 1px; &:after { diff --git a/clients/ui/frontend/src/app/components/pf-overrides/FormSection.scss b/clients/ui/frontend/src/app/components/pf-overrides/FormSection.scss new file mode 100644 index 00000000..351e535b --- /dev/null +++ b/clients/ui/frontend/src/app/components/pf-overrides/FormSection.scss @@ -0,0 +1,8 @@ +.kf-form-section { + &__desc { + margin-top: var(--pf-v6-global--spacer--sm); + font-size: var(--pf-v6-global--FontSize--sm); + color: var(--pf-v6-global--Color--200); + font-weight: initial; + } +} diff --git a/clients/ui/frontend/src/app/components/pf-overrides/FormSection.tsx b/clients/ui/frontend/src/app/components/pf-overrides/FormSection.tsx new file mode 100644 index 00000000..17fc71b1 --- /dev/null +++ b/clients/ui/frontend/src/app/components/pf-overrides/FormSection.tsx @@ -0,0 +1,35 @@ +import * as React from 'react'; +import { FormSection as PFFormSection, FormSectionProps, Content } from '@patternfly/react-core'; + +import './FormSection.scss'; + +type Props = FormSectionProps & { + description?: React.ReactNode; +}; + +// Remove once https://github.com/patternfly/patternfly/issues/6663 is fixed +const FormSection: React.FC = ({ + description, + title, + titleElement: TitleElement = 'div', + ...props +}) => ( + + {title} + + {description} + + + ) : ( + title + ) + } + /> +); + +export default FormSection; diff --git a/clients/ui/frontend/src/app/pages/modelRegistry/ModelRegistryRoutes.tsx b/clients/ui/frontend/src/app/pages/modelRegistry/ModelRegistryRoutes.tsx index c64a58c7..c37d93a2 100644 --- a/clients/ui/frontend/src/app/pages/modelRegistry/ModelRegistryRoutes.tsx +++ b/clients/ui/frontend/src/app/pages/modelRegistry/ModelRegistryRoutes.tsx @@ -12,6 +12,8 @@ import ModelVersionsArchive from './screens/ModelVersionsArchive/ModelVersionsAr import ModelVersionsArchiveDetails from './screens/ModelVersionsArchive/ModelVersionArchiveDetails'; import ArchiveModelVersionDetails from './screens/ModelVersionsArchive/ArchiveModelVersionDetails'; import RegisteredModelsArchiveDetails from './screens/RegisteredModelsArchive/RegisteredModelArchiveDetails'; +import RegisterModel from './screens/RegisterModel/RegisterModel'; +import RegisterVersion from './screens/RegisterModel/RegisterVersion'; const ModelRegistryRoutes: React.FC = () => ( @@ -34,6 +36,7 @@ const ModelRegistryRoutes: React.FC = () => ( path={ModelVersionsTab.DETAILS} element={} /> + } /> } /> ( } /> + } /> + } /> } /> diff --git a/clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelRegistry.tsx b/clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelRegistry.tsx index ddc33484..75cf9f11 100644 --- a/clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelRegistry.tsx +++ b/clients/ui/frontend/src/app/pages/modelRegistry/screens/ModelRegistry.tsx @@ -3,7 +3,7 @@ import ApplicationsPage from '~/app/components/ApplicationsPage'; import TitleWithIcon from '~/app/components/design/TitleWithIcon'; import { ProjectObjectType } from '~/app/components/design/utils'; import useRegisteredModels from '~/app/hooks/useRegisteredModels'; -import { filterLiveModels } from '~/app/utils'; +import { filterLiveModels } from '~/app/pages/modelRegistry/screens/utils'; import ModelRegistrySelectorNavigator from './ModelRegistrySelectorNavigator'; import RegisteredModelListView from './RegisteredModels/RegisteredModelListView'; import { modelRegistryUrl } from './routeUtils'; diff --git a/clients/ui/frontend/src/app/pages/modelRegistry/screens/RegisterModel/PrefilledModelRegistryField.tsx b/clients/ui/frontend/src/app/pages/modelRegistry/screens/RegisterModel/PrefilledModelRegistryField.tsx new file mode 100644 index 00000000..980da8d3 --- /dev/null +++ b/clients/ui/frontend/src/app/pages/modelRegistry/screens/RegisterModel/PrefilledModelRegistryField.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { FormGroup, TextInput } from '@patternfly/react-core'; + +type PrefilledModelRegistryFieldProps = { + mrName?: string; +}; + +const PrefilledModelRegistryField: React.FC = ({ mrName }) => ( + + + +); + +export default PrefilledModelRegistryField; diff --git a/clients/ui/frontend/src/app/pages/modelRegistry/screens/RegisterModel/RegisterModel.tsx b/clients/ui/frontend/src/app/pages/modelRegistry/screens/RegisterModel/RegisterModel.tsx new file mode 100644 index 00000000..c6cffb9d --- /dev/null +++ b/clients/ui/frontend/src/app/pages/modelRegistry/screens/RegisterModel/RegisterModel.tsx @@ -0,0 +1,116 @@ +import React from 'react'; +import { + Breadcrumb, + BreadcrumbItem, + Form, + FormGroup, + PageSection, + Stack, + StackItem, + TextArea, + TextInput, +} from '@patternfly/react-core'; +import spacing from '@patternfly/react-styles/css/utilities/Spacing/spacing'; +import { useParams, useNavigate } from 'react-router'; +import { Link } from 'react-router-dom'; +import FormSection from '~/app/components/pf-overrides/FormSection'; +import ApplicationsPage from '~/app/components/ApplicationsPage'; +import { modelRegistryUrl, registeredModelUrl } from '~/app/pages/modelRegistry/screens/routeUtils'; +import { ValueOf } from '~/typeHelpers'; +import { useRegisterModelData, RegistrationCommonFormData } from './useRegisterModelData'; +import { isRegisterModelSubmitDisabled, registerModel } from './utils'; +import { useRegistrationCommonState } from './useRegistrationCommonState'; +import RegistrationCommonFormSections from './RegistrationCommonFormSections'; +import PrefilledModelRegistryField from './PrefilledModelRegistryField'; +import RegistrationFormFooter from './RegistrationFormFooter'; + +const RegisterModel: React.FC = () => { + const { modelRegistry: mrName } = useParams(); + const navigate = useNavigate(); + + const { isSubmitting, submitError, setSubmitError, handleSubmit, apiState, author } = + useRegistrationCommonState(); + + const [formData, setData] = useRegisterModelData(); + const isSubmitDisabled = isSubmitting || isRegisterModelSubmitDisabled(formData); + const { modelName, modelDescription } = formData; + + const onSubmit = () => + handleSubmit(async () => { + const { registeredModel } = await registerModel(apiState, formData, author); + navigate(registeredModelUrl(registeredModel.id, mrName)); + }); + const onCancel = () => navigate(modelRegistryUrl(mrName)); + + return ( + + Model registry - {mrName}} + /> + Register model + + } + loaded + empty={false} + > + +
+ + + + + + + + setData('modelName', value)} + /> + + +