diff --git a/typescript-sdk/package-lock.json b/typescript-sdk/package-lock.json index 63f65d4350..9b78af1eb6 100644 --- a/typescript-sdk/package-lock.json +++ b/typescript-sdk/package-lock.json @@ -29,12 +29,12 @@ "vite-plugin-dts": "3.9.1" }, "peerDependencies": { - "@microsoft/kiota-abstractions": "1.0.0-preview.56", - "@microsoft/kiota-http-fetchlibrary": "1.0.0-preview.55", - "@microsoft/kiota-serialization-form": "1.0.0-preview.45", - "@microsoft/kiota-serialization-json": "1.0.0-preview.56", - "@microsoft/kiota-serialization-multipart": "1.0.0-preview.34", - "@microsoft/kiota-serialization-text": "1.0.0-preview.53" + "@microsoft/kiota-abstractions": "1.0.0-preview.57", + "@microsoft/kiota-http-fetchlibrary": "1.0.0-preview.56", + "@microsoft/kiota-serialization-form": "1.0.0-preview.46", + "@microsoft/kiota-serialization-json": "1.0.0-preview.57", + "@microsoft/kiota-serialization-multipart": "1.0.0-preview.35", + "@microsoft/kiota-serialization-text": "1.0.0-preview.54" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1249,13 +1249,13 @@ "dev": true }, "node_modules/@microsoft/kiota-abstractions": { - "version": "1.0.0-preview.56", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-abstractions/-/kiota-abstractions-1.0.0-preview.56.tgz", - "integrity": "sha512-mVrmuCvD9V/h19/cwXDQFPEObq5ipM+HK39rhvXe1EsaBjHdbmIaiiVJMNNy9ZRKSniUreb5f12ailomLrWATA==", + "version": "1.0.0-preview.57", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-abstractions/-/kiota-abstractions-1.0.0-preview.57.tgz", + "integrity": "sha512-U6rIiel+39XfljrLzWU3vsgkYExHOodJuuIEwqvKZd6rqaAXOLJHq/AR78Gu48ySy5KRXOEgtfRnRMsJpr04fA==", "peer": true, "dependencies": { "@opentelemetry/api": "^1.7.0", - "@std-uritemplate/std-uritemplate": "^0.0.59", + "@std-uritemplate/std-uritemplate": "^1.0.1", "guid-typescript": "^1.0.9", "tinyduration": "^3.3.0", "tslib": "^2.6.2", @@ -1263,9 +1263,9 @@ } }, "node_modules/@microsoft/kiota-http-fetchlibrary": { - "version": "1.0.0-preview.55", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-http-fetchlibrary/-/kiota-http-fetchlibrary-1.0.0-preview.55.tgz", - "integrity": "sha512-5AGHsXc3FNW5Vwf1cz3ON65ZmnW8ZHatRgZRWzT0LjeGGFiKz/3CNKVgYG1Xudk5w7JwuM1DO9VAlqfw8uiHqQ==", + "version": "1.0.0-preview.56", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-http-fetchlibrary/-/kiota-http-fetchlibrary-1.0.0-preview.56.tgz", + "integrity": "sha512-J2OkBBPMSzw24e6cZoBwymvrqhF5YoPIPRYmCDQvTGjRwV3VbAMmJIc0oxWYA2yj+i8Opp2D4X7nTly4hckFEw==", "peer": true, "dependencies": { "@microsoft/kiota-abstractions": "*", @@ -1275,9 +1275,9 @@ } }, "node_modules/@microsoft/kiota-serialization-form": { - "version": "1.0.0-preview.45", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-form/-/kiota-serialization-form-1.0.0-preview.45.tgz", - "integrity": "sha512-UnYECyOCCq5Rr+EtKRo/MSY+7r+M7ZoCMUvKX5jAP3b3cpkKy33wSi269SlFxh7ibNmjWB1tnDENuu+iVC4H9w==", + "version": "1.0.0-preview.46", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-form/-/kiota-serialization-form-1.0.0-preview.46.tgz", + "integrity": "sha512-0DpWPkUbzyuxxlaGvIbuRbK44CpIW1c28kS0zHUKBN5PBbhAojBQd3zEK0L0IUVcF8FABADdSGklHQ16J8v2vg==", "peer": true, "dependencies": { "@microsoft/kiota-abstractions": "*", @@ -1286,9 +1286,9 @@ } }, "node_modules/@microsoft/kiota-serialization-json": { - "version": "1.0.0-preview.56", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-json/-/kiota-serialization-json-1.0.0-preview.56.tgz", - "integrity": "sha512-PKQ9xJP7sj+kWFZVkF9TDlsoYA7BDP+4SNq4+A5Dod0qxi2pdb3UoBZUMLr8DENaaKWStQpNpx2+Yad8wJlxxw==", + "version": "1.0.0-preview.57", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-json/-/kiota-serialization-json-1.0.0-preview.57.tgz", + "integrity": "sha512-P0fiHMfLV4qVyxcvEGGBWaGv1bwfZ+SAhw+9gYR53UBZZiIdW182cykbE3hSD2Xd0kolSrYseNrYO4U7GI873Q==", "peer": true, "dependencies": { "@microsoft/kiota-abstractions": "*", @@ -1297,9 +1297,9 @@ } }, "node_modules/@microsoft/kiota-serialization-multipart": { - "version": "1.0.0-preview.34", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-multipart/-/kiota-serialization-multipart-1.0.0-preview.34.tgz", - "integrity": "sha512-dPkIXIdnO5SACdKyAh3HYsuvFJ2I1L/Z0BUnzv9gGGQW8h8DsFi3J1HEYsahhD7en35WWCSKVzESe+aWtp9hDw==", + "version": "1.0.0-preview.35", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-multipart/-/kiota-serialization-multipart-1.0.0-preview.35.tgz", + "integrity": "sha512-YuHBp40D9KT4ijawm77I3YftxBuG5u1dl0GAm6tyHGjXSxHt/HRgjMy92dnOnDeGCTSUXwoCe1Ut2zBoHwi2FQ==", "peer": true, "dependencies": { "@microsoft/kiota-abstractions": "*", @@ -1308,9 +1308,9 @@ } }, "node_modules/@microsoft/kiota-serialization-text": { - "version": "1.0.0-preview.53", - "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-text/-/kiota-serialization-text-1.0.0-preview.53.tgz", - "integrity": "sha512-W6BwkmvL85pLxmY38o60vc2RtBDatSeT4x/MJEAXkVZCpIkXh0p0Pe/Fiy3xkyvRNdHTUbklz5wpw1shLucleg==", + "version": "1.0.0-preview.54", + "resolved": "https://registry.npmjs.org/@microsoft/kiota-serialization-text/-/kiota-serialization-text-1.0.0-preview.54.tgz", + "integrity": "sha512-UCa0fTROYkB3vgUILCAwfPmDjEiy3lFwh6STQ6Cg7wGRY4sRIW1dzct6GtjP0jiRCs8UJwL9q5SfyzvKYit5qw==", "peer": true, "dependencies": { "@microsoft/kiota-abstractions": "*", @@ -1771,9 +1771,9 @@ } }, "node_modules/@std-uritemplate/std-uritemplate": { - "version": "0.0.59", - "resolved": "https://registry.npmjs.org/@std-uritemplate/std-uritemplate/-/std-uritemplate-0.0.59.tgz", - "integrity": "sha512-S9RcLJbm9ZgrHGZ//iDPH3T2pssGC5ae+jugRsKTooubZ9VjZc4W3sH+kBAh5rQY+D/mC6pWmFQrY3oIPtSPiw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@std-uritemplate/std-uritemplate/-/std-uritemplate-1.0.3.tgz", + "integrity": "sha512-osJfqMCD3RU4PHTUmg8LQ1JIDmAEcAZi4qmPaJ8V7DG7U70vJoTKvKIDzvUk+JK34OqkanrFkWW871/IsmSU7g==", "peer": true }, "node_modules/@types/argparse": { diff --git a/ui/tests/specs/e2e.spec.ts b/ui/tests/specs/e2e.spec.ts index 66048988b3..fec934939e 100644 --- a/ui/tests/specs/e2e.spec.ts +++ b/ui/tests/specs/e2e.spec.ts @@ -15,22 +15,42 @@ test("End to End - Create artifact", async ({ page }) => { // Click the "Create artifact" button await page.getByTestId("btn-toolbar-create-artifact").click(); - await expect(page.getByTestId("create-artifact-form-group")).toHaveValue(""); + await expect(page.getByTestId("create-artifact-modal-group")).toHaveValue(""); // Create a new artifact - await page.getByTestId("create-artifact-form-group").fill("e2e"); - await page.getByTestId("create-artifact-form-id").fill("MyArtifact"); - await page.getByTestId("create-artifact-form-type-select").click(); - await page.getByTestId("create-artifact-form-OPENAPI").click(); + + // Fill out page 1 of the create artifact wizard + await page.getByTestId("create-artifact-modal-group").fill("e2e"); + await page.getByTestId("create-artifact-modal-id").fill("MyArtifact"); + await page.getByTestId("create-artifact-modal-type-select").click(); + await page.getByTestId("create-artifact-modal-OPENAPI").click(); + + // Click "Next" on the wizard + await page.locator("#next-wizard-page").click(); + + // Fill out page 2 of the create artifact wizard + await page.getByTestId("create-artifact-modal-artifact-metadata-name").fill("Test Artifact"); + await page.getByTestId("create-artifact-modal-artifact-metadata-description").fill("Artifact description."); + + // Click "Next" on the wizard + await page.locator("#next-wizard-page").click(); + + // Fill out page 3 of the create artifact wizard + await page.getByTestId("create-artifact-modal-version").fill("1.0.0"); await page.locator("#artifact-content").fill(OPENAPI_DATA_STR); - await page.getByTestId("create-artifact-modal-btn-create").click(); + + // Click "Next" on the wizard + await page.locator("#next-wizard-page").click(); + + // Leave page 4 empty and click "Complete" + await page.locator("#next-wizard-page").click(); // Make sure we redirected to the artifact page. await expect(page).toHaveURL(/.+\/explore\/e2e\/MyArtifact/); // Assert the meta-data is as expected - await expect(page.getByTestId("artifact-details-name")).toHaveText("No name"); - await expect(page.getByTestId("artifact-details-description")).toHaveText("No description"); + await expect(page.getByTestId("artifact-details-name")).toHaveText("Test Artifact"); + await expect(page.getByTestId("artifact-details-description")).toHaveText("Artifact description."); await expect(page.getByTestId("artifact-details-labels")).toHaveText("No labels"); }); @@ -41,7 +61,7 @@ test("End to End - Edit artifact metadata", async ({ page }) => { // Click the "Edit" button to show the modal await page.getByTestId("artifact-btn-edit").click(); - await expect(page.getByTestId("edit-metadata-modal-name")).toBeEmpty(); + await expect(page.getByTestId("edit-metadata-modal-name")).toHaveValue("Test Artifact"); // Change/add some values await page.getByTestId("edit-metadata-modal-name").fill("Empty API Spec"); diff --git a/ui/ui-app/package-lock.json b/ui/ui-app/package-lock.json index 52eeb1c940..b755f21aa9 100644 --- a/ui/ui-app/package-lock.json +++ b/ui/ui-app/package-lock.json @@ -11,7 +11,7 @@ "license": "Apache-2.0", "dependencies": { "@apicurio/apicurio-registry-sdk": "file:../../typescript-sdk", - "@apicurio/common-ui-components": "2.0.1", + "@apicurio/common-ui-components": "2.0.2", "@apicurio/data-models": "1.1.27", "@microsoft/kiota-abstractions": "1.0.0-preview.57", "@microsoft/kiota-http-fetchlibrary": "1.0.0-preview.56", @@ -104,9 +104,9 @@ "link": true }, "node_modules/@apicurio/common-ui-components": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@apicurio/common-ui-components/-/common-ui-components-2.0.1.tgz", - "integrity": "sha512-6EBDSc/1baehL97w15mh/HjP93CXV8ncW6DN8GvT53DMIuALjbMEMLweKsasEZ9rgysv6hxq5Wc74aC0QzAm+w==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@apicurio/common-ui-components/-/common-ui-components-2.0.2.tgz", + "integrity": "sha512-6sZ30V2YwVEmGEiSYqsFxergUZuD9FUHvnv793f3JQaMUJurqXFXbWn6qkfauN1BvkgqA8qQVTkdHEEDXT41gw==", "peerDependencies": { "@patternfly/patternfly": "~5", "@patternfly/react-core": "~5", diff --git a/ui/ui-app/package.json b/ui/ui-app/package.json index 8b92be702b..acb4cba0b9 100644 --- a/ui/ui-app/package.json +++ b/ui/ui-app/package.json @@ -31,7 +31,7 @@ "vite-tsconfig-paths": "4.3.2" }, "dependencies": { - "@apicurio/common-ui-components": "2.0.1", + "@apicurio/common-ui-components": "2.0.2", "@apicurio/apicurio-registry-sdk": "file:../../typescript-sdk", "@apicurio/data-models": "1.1.27", "@microsoft/kiota-abstractions": "1.0.0-preview.57", diff --git a/ui/ui-app/src/app/components/modals/CreateArtifactForm.css b/ui/ui-app/src/app/components/modals/CreateArtifactForm.css deleted file mode 100644 index a7e1050d3d..0000000000 --- a/ui/ui-app/src/app/components/modals/CreateArtifactForm.css +++ /dev/null @@ -1,17 +0,0 @@ -.group-and-id { - display: flex; - flex-direction: row; -} - -.group-and-id .separator { - margin-left: 10px; - margin-right: 10px; -} - -.group-and-id .group { - flex-basis: 325px; -} - -.group-and-id .artifact-id { - -} diff --git a/ui/ui-app/src/app/components/modals/CreateArtifactForm.tsx b/ui/ui-app/src/app/components/modals/CreateArtifactForm.tsx deleted file mode 100644 index 868bec4572..0000000000 --- a/ui/ui-app/src/app/components/modals/CreateArtifactForm.tsx +++ /dev/null @@ -1,314 +0,0 @@ -import { FunctionComponent, useEffect, useState } from "react"; -import "./CreateArtifactForm.css"; -import { - FileUpload, - Form, - FormGroup, - FormHelperText, - HelperText, - HelperTextItem, - Tab, - Tabs, - TabTitleText, - TextInput -} from "@patternfly/react-core"; -import { ExclamationCircleIcon } from "@patternfly/react-icons"; -import { If, ObjectSelect, UrlUpload } from "@apicurio/common-ui-components"; -import { UrlService, useUrlService } from "@services/useUrlService.ts"; -import { ArtifactTypesService, useArtifactTypesService } from "@services/useArtifactTypesService.ts"; -import { detectContentType } from "@utils/content.utils.ts"; -import { ContentTypes } from "@models/contentTypes.model.ts"; -import { isStringEmptyOrUndefined } from "@utils/string.utils.ts"; -import { CreateArtifact } from "@sdk/lib/generated-client/models"; - -/** - * Properties - */ -export type CreateArtifactFormProps = { - groupId?: string; - onValid: (valid: boolean) => void; - onChange: (groupId: string|null, data: CreateArtifact) => void; -}; - -type ArtifactTypeItem = { - value?: string; - label?: string; - isDivider?: boolean; -}; - -const DEFAULT_ARTIFACT_TYPE: ArtifactTypeItem = { - value: "", - label: "Auto-Detect" -}; -const DIVIDER: ArtifactTypeItem = { - isDivider: true -}; - -/** - * Models the Create Artifact modal dialog. - */ -export const CreateArtifactForm: FunctionComponent = (props: CreateArtifactFormProps) => { - const [content, setContent] = useState(); - const [contentType, setContentType] = useState(ContentTypes.APPLICATION_JSON); - const [contentIsLoading, setContentIsLoading] = useState(false); - const [artifactId, setArtifactId] = useState(""); - const [groupId, setGroupId] = useState(""); - const [artifactType, setArtifactType] = useState(""); - const [tabKey, setTabKey] = useState(0); - const [isFormValid, setFormValid] = useState(false); - const [isArtifactIdValid, setArtifactIdValid] = useState(true); - const [isGroupIdValid, setGroupIdValid] = useState(true); - const [artifactTypes, setArtifactTypes] = useState([]); - const [artifactTypeOptions, setArtifactTypeOptions] = useState([]); - const [selectedType, setSelectedType] = useState(DEFAULT_ARTIFACT_TYPE); - - const urlService: UrlService = useUrlService(); - const atService: ArtifactTypesService = useArtifactTypesService(); - - const onFileTextChange = (_event: any, value: string | undefined): void => { - setContent(value); - setContentType(detectContentType(artifactType, value as string)); - }; - - const onFileClear = (): void => { - onFileTextChange(null, ""); - }; - - const onFileReadStarted = (): void => { - setContentIsLoading(true); - }; - - const onFileReadFinished = (): void => { - setContentIsLoading(false); - }; - - const checkIdValid = (id: string|null): boolean => { - if (!id) { - //id is optional, server can generate it - return true; - } else { - // character % breaks the ui - const isAscii = (str: string) => { - for (let i = 0; i < str.length; i++){ - if(str.charCodeAt(i)>127){ - return false; - } - } - return true; - }; - return id.indexOf("%") == -1 && isAscii(id); - } - }; - - const artifactIdValidated = (): any => { - if (isArtifactIdValid) { - if (!artifactId) { - return "default"; - } - return "success"; - } else { - return "error"; - } - }; - - const groupValidated = (): any => { - if (isGroupIdValid) { - if (!groupId) { - return "default"; - } - return "success"; - } else { - return "error"; - } - }; - - const fireOnChange = (): void => { - const data: CreateArtifact = { - artifactId, - artifactType - }; - if (!isStringEmptyOrUndefined(content)) { - data.firstVersion = { - content: { - contentType: contentType, - content: content as string - } - }; - } - const gid: string|null = groupId === "" ? null : groupId; - props.onChange(gid, data); - }; - - useEffect(() => { - atService.allTypesWithLabels().then(setArtifactTypes); - }, []); - - useEffect(() => { - setContentType(detectContentType(artifactType, content as string)); - }, [content]); - - useEffect(() => { - const items: ArtifactTypeItem[] = artifactTypes.map(item => ({ - value: item.id, - label: item.label - })); - setArtifactTypeOptions([ - DEFAULT_ARTIFACT_TYPE, - DIVIDER, - ...items - ]); - }, [artifactTypes]); - - useEffect(() => { - setArtifactType(selectedType.value as string); - }, [selectedType]); - - useEffect(() => { - const artifactIdValid: boolean = checkIdValid(artifactId); - const groupIdValid: boolean = checkIdValid(groupId); - - setArtifactIdValid(artifactIdValid); - setGroupIdValid(groupIdValid); - let valid: boolean = artifactIdValid && groupIdValid; - - // Note: content can be empty, but if it is then an artifact type MUST be provided (since we cannot detect it from the content). - if (isStringEmptyOrUndefined(content) && isStringEmptyOrUndefined(artifactType)) { - valid = false; - } - - setFormValid(valid); - fireOnChange(); - }, [artifactType, artifactId, groupId, content]); - - useEffect(() => { - if (props.onValid) { - props.onValid(isFormValid); - } - }, [isFormValid]); - - return ( -
- -
- setGroupId(value)} - validated={groupValidated()} - /> - / - setArtifactId(value)} - validated={artifactIdValidated()} - /> -
- - - - }>Character % and non ASCII characters are not allowed - - - - - - (Optional) Group Id and Artifact Id are optional. If Artifact Id is left blank, the server will generate one for you. - - -
- -
- item.isDivider} - itemToTestId={(item) => `create-artifact-form-${item.value}`} - itemToString={(item) => item.label} /> -
-
- - { - setTabKey(eventKey as number); - onFileTextChange( null, undefined); - _event.preventDefault(); - _event.stopPropagation(); - }} - isBox={false} - role="region" - > - From file} - aria-label="Upload from file" - > - - - From URL} - aria-label="Upload from URL" - > - { - onFileTextChange(null, value); - }} - onUrlFetch={(url) => urlService.fetchUrlContent(url)} - /> - - - -
- ); - -}; diff --git a/ui/ui-app/src/app/components/modals/CreateArtifactModal.css b/ui/ui-app/src/app/components/modals/CreateArtifactModal.css new file mode 100644 index 0000000000..e1390cc2c4 --- /dev/null +++ b/ui/ui-app/src/app/components/modals/CreateArtifactModal.css @@ -0,0 +1,8 @@ +.pf-v5-c-page__main-wizard .pf-v5-c-wizard__footer, .pf-v5-c-modal-box .pf-v5-c-wizard__footer, .pf-v5-c-drawer > .pf-v5-c-wizard__footer { + box-shadow: none; + border-top: 1px solid #ccc; +} + +.url-upload textarea, textarea#artifact-content { + height: 200px; +} diff --git a/ui/ui-app/src/app/components/modals/CreateArtifactModal.tsx b/ui/ui-app/src/app/components/modals/CreateArtifactModal.tsx index 9ebb10d452..fd91424c88 100644 --- a/ui/ui-app/src/app/components/modals/CreateArtifactModal.tsx +++ b/ui/ui-app/src/app/components/modals/CreateArtifactModal.tsx @@ -1,11 +1,95 @@ import { FunctionComponent, useEffect, useState } from "react"; -import { Button, Modal } from "@patternfly/react-core"; -import { CreateArtifactForm } from "@app/components"; +import "./CreateArtifactModal.css"; +import { + FileUpload, + Form, + FormGroup, + FormHelperText, + Grid, + GridItem, + HelperText, + HelperTextItem, + Modal, + Tab, + Tabs, + TabTitleText, + TextArea, + TextInput, + Wizard, + WizardStep, + WizardFooterProps +} from "@patternfly/react-core"; import { CreateArtifact } from "@sdk/lib/generated-client/models"; +import { If, ObjectSelect, UrlUpload } from "@apicurio/common-ui-components"; +import { ExclamationCircleIcon } from "@patternfly/react-icons"; +import { UrlService, useUrlService } from "@services/useUrlService.ts"; +import { ArtifactTypesService, useArtifactTypesService } from "@services/useArtifactTypesService.ts"; +import { ArtifactLabel, LabelsFormGroup } from "@app/components"; +import { listToLabels } from "@utils/labels.utils.ts"; +import { detectContentType } from "@utils/content.utils.ts"; + + +export type ValidType = "default" | "success" | "error"; + +export type Validities = { + groupId?: ValidType; + artifactId?: ValidType; + artifactType?: ValidType; + artifactName?: ValidType; + artifactDescription?: ValidType; + versionNumber?: ValidType; + versionName?: ValidType; + versionDescription?: ValidType; +}; + +const checkIdValid = (id: string | undefined): boolean => { + if (!id) { + //id is optional, server can generate it + return true; + } else { + // character % breaks the ui + const isAscii = (str: string) => { + for (let i = 0; i < str.length; i++){ + if (str.charCodeAt(i) > 127){ + return false; + } + } + return true; + }; + return id.indexOf("%") == -1 && isAscii(id); + } +}; + +const validateField = (value: string | undefined): ValidType => { + const isValid: boolean = checkIdValid(value); + if (!isValid) { + return "error"; + } + if (value === undefined || value === null || value === "") { + return "default"; + } + return "success"; +}; + const EMPTY_FORM_DATA: CreateArtifact = { }; + +type ArtifactTypeItem = { + value?: string; + label?: string; + isDivider?: boolean; +}; + +const DEFAULT_ARTIFACT_TYPE: ArtifactTypeItem = { + value: "", + label: "Auto-Detect" +}; +const DIVIDER: ArtifactTypeItem = { + isDivider: true +}; + /** * Properties */ @@ -13,24 +97,127 @@ export type CreateArtifactModalProps = { groupId?: string; isOpen: boolean; onClose: () => void; - onCreate: (groupId: string|null, data: CreateArtifact) => void; + onCreate: (groupId: string | undefined, data: CreateArtifact) => void; }; /** * Models the Create Artifact modal dialog. */ export const CreateArtifactModal: FunctionComponent = (props: CreateArtifactModalProps) => { - const [isFormValid, setFormValid] = useState(false); - const [groupId, setGroupId] = useState(null); - const [formData, setFormData] = useState(EMPTY_FORM_DATA); + const [validities, setValidities] = useState({}); + const [groupId, setGroupId] = useState(); + const [data, setData] = useState(EMPTY_FORM_DATA); + const [artifactTypes, setArtifactTypes] = useState([]); + const [artifactTypeOptions, setArtifactTypeOptions] = useState([]); + const [selectedType, setSelectedType] = useState(DEFAULT_ARTIFACT_TYPE); + const [artifactLabels, setArtifactLabels] = useState([]); + const [contentTabKey, setContentTabKey] = useState(0); + const [contentIsLoading, setContentIsLoading] = useState(false); + const [versionLabels, setVersionLabels] = useState([]); + + const urlService: UrlService = useUrlService(); + const atService: ArtifactTypesService = useArtifactTypesService(); + + const setArtifactId = (newArtifactId: string): void => { + setData({ + ...data, + artifactId: newArtifactId + }); + }; + + const setArtifactType = (newArtifactType: string): void => { + setData({ + ...data, + artifactType: newArtifactType + }); + }; + + const setArtifactName = (newName: string): void => { + setData({ + ...data, + name: newName + }); + }; + + const setArtifactDescription = (newDescription: string): void => { + setData({ + ...data, + description: newDescription + }); + }; + + const _setArtifactLabels = (newLabels: ArtifactLabel[]): void => { + setArtifactLabels(newLabels); + setData({ + ...data, + labels: listToLabels(newLabels) + }); + }; + + const onFileTextChange = (_event: any, value: string | undefined): void => { + setData({ + ...data, + firstVersion: { + ...data.firstVersion, + content: { + content: value, + contentType: detectContentType(data.artifactType, value) + } + } + }); + }; - const onCreateArtifactFormValid = (isValid: boolean): void => { - setFormValid(isValid); + const onFileClear = (): void => { + onFileTextChange(null, undefined); }; - const onCreateArtifactFormChange = (groupId: string|null, data: CreateArtifact): void => { - setGroupId(groupId); - setFormData(data); + const onFileReadStarted = (): void => { + setContentIsLoading(true); + }; + + const onFileReadFinished = (): void => { + setContentIsLoading(false); + }; + + const setVersionNumber = (newVersion: string): void => { + setData({ + ...data, + firstVersion: { + ...data.firstVersion, + version: newVersion + } + }); + }; + + const setVersionName = (newName: string): void => { + setData({ + ...data, + firstVersion: { + ...data.firstVersion, + name: newName + } + }); + }; + + const setVersionDescription = (newDescription: string): void => { + setData({ + ...data, + firstVersion: { + ...data.firstVersion, + description: newDescription + } + }); + }; + + const _setVersionLabels = (newLabels: ArtifactLabel[]): void => { + setVersionLabels(newLabels); + setData({ + ...data, + firstVersion: { + ...data.firstVersion, + labels: listToLabels(newLabels) + } + }); }; const fireCloseEvent = (): void => { @@ -38,16 +225,92 @@ export const CreateArtifactModal: FunctionComponent = }; const fireCreateEvent = (): void => { - props.onCreate(groupId, formData); + console.debug("---"); + console.debug(data); + props.onCreate(groupId, data); }; + useEffect(() => { + atService.allTypesWithLabels().then(setArtifactTypes); + }, []); + useEffect(() => { if (props.isOpen) { - setFormValid(false); - setFormData(EMPTY_FORM_DATA); + setData(EMPTY_FORM_DATA); + if (props.groupId) { + setGroupId(props.groupId); + } else { + setGroupId(""); + } } }, [props.isOpen]); + useEffect(() => { + const items: ArtifactTypeItem[] = artifactTypes.map(item => ({ + value: item.id, + label: item.label + })); + setArtifactTypeOptions([ + DEFAULT_ARTIFACT_TYPE, + DIVIDER, + ...items + ]); + }, [artifactTypes]); + + useEffect(() => { + setArtifactType(selectedType.value as string); + }, [selectedType]); + + useEffect(() => { + setValidities({ + groupId: validateField(groupId), + artifactId: validateField(data.artifactId) + }); + }, [groupId, data]); + + const hasArtifactType: boolean = data.artifactType !== undefined && data.artifactType !== null && data.artifactType !== ""; + const hasVersionContent: boolean = data.firstVersion?.content?.content !== undefined; + const versionContentTitle: string = hasArtifactType ? "Version Content (optional)" : "Version Content"; + + const isGroupIdValid: boolean = validities.groupId !== "error"; + const isArtifactIdValid: boolean = validities.artifactId !== "error"; + const isCoordinates1Valid: boolean = isGroupIdValid && isArtifactIdValid; + const isValid: boolean = isCoordinates1Valid; + + const coordinatesStepFooter: Partial = { + nextButtonProps: { + id: "next-wizard-page" + }, + onClose: props.onClose, + isNextDisabled: !isCoordinates1Valid + }; + const artifactMetadataStepFooter: Partial = { + nextButtonProps: { + id: "next-wizard-page" + }, + onClose: props.onClose + }; + const versionContentStepFooter: Partial = { + nextButtonProps: { + id: "next-wizard-page" + }, + onClose: props.onClose + }; + if (!hasVersionContent) { + versionContentStepFooter.onNext = fireCreateEvent; + versionContentStepFooter.nextButtonText = "Create"; + versionContentStepFooter.isNextDisabled = !isValid; + } + const versionMetadataStepFooter: Partial = { + nextButtonProps: { + id: "next-wizard-page" + }, + nextButtonText: "Create", + isNextDisabled: !isValid, + onNext: fireCreateEvent, + onClose: props.onClose, + }; + return ( = isOpen={props.isOpen} onClose={fireCloseEvent} className="create-artifact-modal pf-m-redhat-font" - actions={[ - , - - ]} > - + + +
+ + setGroupId(value)} + validated={validities.groupId} + /> + + + + }>Character % and non ASCII characters are not allowed + + + + + + setArtifactId(value)} + validated={validities.artifactId} + /> + + + + }>Character % and non ASCII characters are not allowed + + + + + + item.isDivider} + itemToTestId={(item) => `create-artifact-modal-${item.value}`} + itemToString={(item) => item.label} + appendTo="document" + /> + + + Note: If "Auto-Detect" is chosen, Version Content will be required. + + + +
+
+ +
+ + + + setArtifactName(value)} + /> + + + + + +