diff --git a/src/app/contribute/knowledge/page.tsx b/src/app/contribute/knowledge/page.tsx index ea98c861..063a0234 100644 --- a/src/app/contribute/knowledge/page.tsx +++ b/src/app/contribute/knowledge/page.tsx @@ -1,9 +1,9 @@ // src/app/contribute/knowledge/page.tsx 'use client'; +import { useEffect, useState } from 'react'; import { AppLayout } from '@/components/AppLayout'; import KnowledgeFormGithub from '@/components/Contribute/Knowledge/Github'; import KnowledgeFormNative from '@/components/Contribute/Knowledge/Native'; -import { useEffect, useState } from 'react'; const KnowledgeFormPage: React.FunctionComponent = () => { const [deploymentType, setDeploymentType] = useState(); @@ -17,7 +17,7 @@ const KnowledgeFormPage: React.FunctionComponent = () => { getEnvVariables(); }, []); - return {deploymentType === 'native' ? : }; + return {deploymentType === 'native' ? : }; }; export default KnowledgeFormPage; diff --git a/src/components/Contribute/AuthorInformation.tsx b/src/components/Contribute/AuthorInformation.tsx index bee52301..b36c86a8 100644 --- a/src/components/Contribute/AuthorInformation.tsx +++ b/src/components/Contribute/AuthorInformation.tsx @@ -1,28 +1,29 @@ -import React, { useEffect, useState } from 'react'; -import { checkSkillFormCompletion } from './Skill/validation'; -import { checkKnowledgeFormCompletion } from './Knowledge/validation'; -import { ValidatedOptions, FormGroup, TextInput, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; +import React from 'react'; +import { + ValidatedOptions, + FormGroup, + TextInput, + FormHelperText, + HelperText, + HelperTextItem, + Form, + Flex, + FlexItem, + Content +} from '@patternfly/react-core'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; -export enum FormType { - Knowledge, - Skill -} - interface Props { - formType: FormType; - reset: boolean; - formData: object; - setDisableAction: React.Dispatch>; email: string; - setEmail: React.Dispatch>; + setEmail: (val: string) => void; name: string; - setName: React.Dispatch>; + setName: (val: string) => void; } -const AuthorInformation: React.FC = ({ formType, reset, formData, setDisableAction, email, setEmail, name, setName }) => { - const [validEmail, setValidEmail] = useState(); - const [validName, setValidName] = useState(); - const [validEmailError, setValidEmailError] = useState('Required Field'); +const AuthorInformation: React.FC = ({ email, setEmail, name, setName }) => { + const [validEmail, setValidEmail] = React.useState(ValidatedOptions.default); + const [validName, setValidName] = React.useState(ValidatedOptions.default); + const [validEmailError, setValidEmailError] = React.useState('Required Field'); + const touchedRef = React.useRef(); const validateEmail = (emailStr: string) => { const email = emailStr.trim(); @@ -30,91 +31,74 @@ const AuthorInformation: React.FC = ({ formType, reset, formData, setDisa if (re.test(email)) { setValidEmail(ValidatedOptions.success); setValidEmailError(''); - if (formType === FormType.Knowledge) { - setDisableAction(!checkKnowledgeFormCompletion(formData)); - return; - } - setDisableAction(!checkSkillFormCompletion(formData)); return; } const errMsg = email ? 'Please enter a valid email address.' : 'Required field'; - setDisableAction(true); setValidEmail(ValidatedOptions.error); setValidEmailError(errMsg); - return; }; const validateName = (nameStr: string) => { const name = nameStr.trim(); - if (name.length > 0) { - setValidName(ValidatedOptions.success); - if (formType === FormType.Knowledge) { - setDisableAction(!checkKnowledgeFormCompletion(formData)); - return; - } - setDisableAction(!checkSkillFormCompletion(formData)); - return; - } - setDisableAction(true); - setValidName(ValidatedOptions.error); - return; + setValidName(name.length > 0 ? ValidatedOptions.success : ValidatedOptions.error); }; - useEffect(() => { - setValidEmail(ValidatedOptions.default); - setValidName(ValidatedOptions.default); - }, [reset]); - return ( - <> -

- Author Information - * -

-

Provide your information required for a GitHub DCO sign-off.

- - setEmail(value)} - onBlur={() => validateEmail(email)} - /> - {validEmail === ValidatedOptions.error && ( - - - } variant={validEmail}> - {validEmailError} - - - - )} - - - setName(value)} - onBlur={() => validateName(name)} - /> - {validName === ValidatedOptions.error && ( - - - } variant={validName}> - Required field - - - - )} - - + + + Author Information + Provide your information required for a GitHub DCO sign-off. + + +
+ + { + touchedRef.current = true; + setEmail(value); + }} + onBlur={() => validateEmail(email)} + /> + {validEmail === ValidatedOptions.error && ( + + + } variant={validEmail}> + {validEmailError} + + + + )} + + + setName(value)} + onBlur={() => validateName(name)} + /> + {validName === ValidatedOptions.error && ( + + + } variant={validName}> + Required field + + + + )} + +
+
+
); }; diff --git a/src/components/Contribute/ContributeAlertGroup.tsx b/src/components/Contribute/ContributeAlertGroup.tsx new file mode 100644 index 00000000..4a349e19 --- /dev/null +++ b/src/components/Contribute/ContributeAlertGroup.tsx @@ -0,0 +1,44 @@ +// src/components/Contribute/Native/Knowledge/index.tsx +'use client'; +import React from 'react'; +import { AlertGroup, Alert, AlertActionCloseButton, Spinner } from '@patternfly/react-core'; +import { ActionGroupAlertContent } from '@/components/Contribute/types'; + +export interface ContributeAlertGroupProps { + actionGroupAlertContent?: ActionGroupAlertContent; + onCloseActionGroupAlert: () => void; +} + +export const ContributeAlertGroup: React.FunctionComponent = ({ actionGroupAlertContent, onCloseActionGroupAlert }) => { + if (!actionGroupAlertContent) { + return null; + } + + return ( + + } + > +

+ {actionGroupAlertContent.waitAlert && } + {actionGroupAlertContent.message} +
+ {!actionGroupAlertContent.waitAlert && + actionGroupAlertContent.success && + actionGroupAlertContent.url && + actionGroupAlertContent.url.trim().length > 0 && ( + + View your new branch + + )} +

+
+
+ ); +}; + +export default ContributeAlertGroup; diff --git a/src/components/Contribute/Knowledge/AttributionInformation/AttributionInformation.tsx b/src/components/Contribute/Knowledge/AttributionInformation/AttributionInformation.tsx index 49919d7f..273c0555 100644 --- a/src/components/Contribute/Knowledge/AttributionInformation/AttributionInformation.tsx +++ b/src/components/Contribute/Knowledge/AttributionInformation/AttributionInformation.tsx @@ -1,31 +1,36 @@ import React, { useEffect } from 'react'; -import { checkKnowledgeFormCompletion } from '../validation'; import { KnowledgeFormData } from '@/types'; -import { ValidatedOptions, FormFieldGroupHeader, FormGroup, TextInput, FormHelperText, HelperText, HelperTextItem } from '@patternfly/react-core'; +import { + ValidatedOptions, + FormGroup, + TextInput, + FormHelperText, + HelperText, + HelperTextItem, + FlexItem, + Content, + Flex, + Form +} from '@patternfly/react-core'; import { ExclamationCircleIcon } from '@patternfly/react-icons'; interface Props { - reset: boolean; isEditForm?: boolean; knowledgeFormData: KnowledgeFormData; - setDisableAction: React.Dispatch>; titleWork: string; - setTitleWork: React.Dispatch>; + setTitleWork: (val: string) => void; linkWork: string; - setLinkWork: React.Dispatch>; + setLinkWork: (val: string) => void; revision: string; - setRevision: React.Dispatch>; + setRevision: (val: string) => void; licenseWork: string; - setLicenseWork: React.Dispatch>; + setLicenseWork: (val: string) => void; creators: string; - setCreators: React.Dispatch>; + setCreators: (val: string) => void; } const AttributionInformation: React.FC = ({ - reset, isEditForm, - knowledgeFormData, - setDisableAction, titleWork, setTitleWork, linkWork, @@ -43,14 +48,6 @@ const AttributionInformation: React.FC = ({ const [validLicense, setValidLicense] = React.useState(); const [validCreators, setValidCreators] = React.useState(); - useEffect(() => { - setValidTitle(ValidatedOptions.default); - setValidLink(ValidatedOptions.default); - setValidRevision(ValidatedOptions.default); - setValidLicense(ValidatedOptions.default); - setValidCreators(ValidatedOptions.default); - }, [reset]); - useEffect(() => { if (!isEditForm) { return; @@ -66,10 +63,8 @@ const AttributionInformation: React.FC = ({ const title = titleStr.trim(); if (title.length > 0) { setValidTitle(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); return; } - setDisableAction(true); setValidTitle(ValidatedOptions.error); return; }; @@ -77,186 +72,157 @@ const AttributionInformation: React.FC = ({ const validateLink = (linkStr: string) => { const link = linkStr.trim(); if (link.length === 0) { - setDisableAction(true); setValidLink(ValidatedOptions.error); return; } try { new URL(link); setValidLink(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; } catch (e) { - setDisableAction(true); setValidLink(ValidatedOptions.warning); - return; } }; const validateRevision = (revisionStr: string) => { const revision = revisionStr.trim(); - if (revision.length > 0) { - setValidRevision(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; - } - setDisableAction(true); - setValidRevision(ValidatedOptions.error); - return; + setValidRevision(revision.length > 0 ? ValidatedOptions.success : ValidatedOptions.error); }; const validateLicense = (licenseStr: string) => { const license = licenseStr.trim(); - if (license.length > 0) { - setValidLicense(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; - } - setDisableAction(true); - setValidLicense(ValidatedOptions.error); - return; + setValidLicense(license.length > 0 ? ValidatedOptions.success : ValidatedOptions.error); }; const validateCreators = (creatorsStr: string) => { const creators = creatorsStr.trim(); - if (creators.length > 0) { - setValidCreators(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; - } - setDisableAction(true); - setValidCreators(ValidatedOptions.error); - return; + setValidCreators(creators.length > 0 ? ValidatedOptions.success : ValidatedOptions.error); }; return ( -
- - Attribution Information * -

- ), - id: 'attribution-info-id' - }} - titleDescription="Provide attribution information." - /> - - setLinkWork(value)} - onBlur={() => validateLink(linkWork)} - /> - {validLink === ValidatedOptions.error && ( - - - } variant={validLink}> - Required field - - - - )} - {validLink === ValidatedOptions.warning && ( - - - } variant={validLink}> - Please enter a valid URL. - - - - )} - - - setTitleWork(value)} - onBlur={() => validateTitle(titleWork)} - /> - {validTitle === ValidatedOptions.error && ( - - - } variant={validTitle}> - Required field - - - - )} - - - setRevision(value)} - onBlur={() => validateRevision(revision)} - /> - {validRevision === ValidatedOptions.error && ( - - - } variant={validRevision}> - Required field - - - - )} - - - setLicenseWork(value)} - onBlur={() => validateLicense(licenseWork)} - /> - {validLicense === ValidatedOptions.error && ( - - - } variant={validLicense}> - Required field - - - - )} - - - setCreators(value)} - onBlur={() => validateCreators(creators)} - /> - {validCreators === ValidatedOptions.error && ( - - - } variant={validCreators}> - Required field - - - - )} - -
+ + + Attribution Information + Provide attribution information. + + +
+ + setLinkWork(value)} + onBlur={() => validateLink(linkWork)} + /> + {validLink === ValidatedOptions.error && ( + + + } variant={validLink}> + Required field + + + + )} + {validLink === ValidatedOptions.warning && ( + + + } variant={validLink}> + Please enter a valid URL. + + + + )} + + + setTitleWork(value)} + onBlur={() => validateTitle(titleWork)} + /> + {validTitle === ValidatedOptions.error && ( + + + } variant={validTitle}> + Required field + + + + )} + + + setRevision(value)} + onBlur={() => validateRevision(revision)} + /> + {validRevision === ValidatedOptions.error && ( + + + } variant={validRevision}> + Required field + + + + )} + + + setLicenseWork(value)} + onBlur={() => validateLicense(licenseWork)} + /> + {validLicense === ValidatedOptions.error && ( + + + } variant={validLicense}> + Required field + + + + )} + + + setCreators(value)} + onBlur={() => validateCreators(creators)} + /> + {validCreators === ValidatedOptions.error && ( + + + } variant={validCreators}> + Required field + + + + )} + +
+
+
); }; diff --git a/src/components/Contribute/Knowledge/Native/DocumentInformation/DocumentInformation.tsx b/src/components/Contribute/Knowledge/DocumentInformation/DocumentInformation.tsx similarity index 51% rename from src/components/Contribute/Knowledge/Native/DocumentInformation/DocumentInformation.tsx rename to src/components/Contribute/Knowledge/DocumentInformation/DocumentInformation.tsx index 3bf1c090..0183df9e 100644 --- a/src/components/Contribute/Knowledge/Native/DocumentInformation/DocumentInformation.tsx +++ b/src/components/Contribute/Knowledge/DocumentInformation/DocumentInformation.tsx @@ -1,32 +1,42 @@ // src/components/Contribute/Knowledge/Native/DocumentInformation/DocumentInformation.tsx import React, { useEffect, useState } from 'react'; -import { FormFieldGroupHeader, FormGroup, FormHelperText } from '@patternfly/react-core/dist/dynamic/components/Form'; -import { Button } from '@patternfly/react-core/dist/dynamic/components/Button'; -import { TextInput } from '@patternfly/react-core/dist/dynamic/components/TextInput'; -import { Alert, AlertActionLink, AlertActionCloseButton, AlertGroup } from '@patternfly/react-core/dist/dynamic/components/Alert'; -import { HelperText } from '@patternfly/react-core/dist/dynamic/components/HelperText'; -import { HelperTextItem } from '@patternfly/react-core/dist/dynamic/components/HelperText'; +import { + Alert, + AlertActionLink, + AlertActionCloseButton, + AlertGroup, + Button, + Content, + Flex, + FlexItem, + Form, + FormGroup, + FormHelperText, + HelperText, + HelperTextItem, + Modal, + ModalBody, + ModalFooter, + ModalHeader, + ModalVariant, + TextInput +} from '@patternfly/react-core'; import ExclamationCircleIcon from '@patternfly/react-icons/dist/dynamic/icons/exclamation-circle-icon'; import { ValidatedOptions } from '@patternfly/react-core/dist/esm/helpers/constants'; -import { Modal, ModalVariant } from '@patternfly/react-core/dist/esm/deprecated/components/Modal/Modal'; import { UploadFile } from '@/components/Contribute/Knowledge/UploadFile'; -import { checkKnowledgeFormCompletion } from '@/components/Contribute/Knowledge/validation'; -import { KnowledgeFormData } from '@/types'; + +const GITHUB_KNOWLEDGE_FILES_URL = '/api/github/knowledge-files'; +const NATIVE_GIT_KNOWLEDGE_FILES_URL = '/api/native/git/knowledge-files'; interface Props { - reset: boolean; isEditForm?: boolean; - knowledgeFormData: KnowledgeFormData; - setDisableAction: React.Dispatch>; - + isGithubMode: boolean; knowledgeDocumentRepositoryUrl: string; - setKnowledgeDocumentRepositoryUrl: React.Dispatch>; - + setKnowledgeDocumentRepositoryUrl: (val: string) => void; knowledgeDocumentCommit: string; - setKnowledgeDocumentCommit: React.Dispatch>; - + setKnowledgeDocumentCommit: (val: string) => void; documentName: string; - setDocumentName: React.Dispatch>; + setDocumentName: (val: string) => void; } interface AlertInfo { @@ -37,10 +47,8 @@ interface AlertInfo { } const DocumentInformation: React.FC = ({ - reset, isEditForm, - knowledgeFormData, - setDisableAction, + isGithubMode, knowledgeDocumentRepositoryUrl, setKnowledgeDocumentRepositoryUrl, knowledgeDocumentCommit, @@ -59,12 +67,6 @@ const DocumentInformation: React.FC = ({ const [validCommit, setValidCommit] = useState(ValidatedOptions.default); const [validDocumentName, setValidDocumentName] = useState(ValidatedOptions.default); - useEffect(() => { - setValidRepo(ValidatedOptions.default); - setValidCommit(ValidatedOptions.default); - setValidDocumentName(ValidatedOptions.default); - }, [reset]); - useEffect(() => { if (isEditForm) { setValidRepo(ValidatedOptions.success); @@ -73,28 +75,30 @@ const DocumentInformation: React.FC = ({ } }, [isEditForm]); - const validateCommit = (commitStr: string) => { - const commit = commitStr.trim(); - if (commit.length > 0) { - setValidCommit(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); + const validateRepo = (repoStr: string) => { + const repo = repoStr.trim(); + if (repo.length === 0) { + setValidRepo(ValidatedOptions.error); + return; + } + try { + new URL(repo); + setValidRepo(ValidatedOptions.success); + return; + } catch (e) { + setValidRepo(ValidatedOptions.warning); return; } - setDisableAction(true); - setValidCommit(ValidatedOptions.error); - return; + }; + + const validateCommit = (commitStr: string) => { + const commit = commitStr.trim(); + setValidCommit(commit.length > 0 ? ValidatedOptions.success : ValidatedOptions.error); }; const validateDocumentName = (document: string) => { const documentNameStr = document.trim(); - if (documentNameStr.length > 0) { - setValidDocumentName(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; - } - setDisableAction(true); - setValidDocumentName(ValidatedOptions.error); - return; + setValidDocumentName(documentNameStr.length > 0 ? ValidatedOptions.success : ValidatedOptions.error); }; const handleFilesChange = (files: File[]) => { @@ -130,7 +134,7 @@ const DocumentInformation: React.FC = ({ if (fileContents.length === uploadedFiles.length) { try { - const response = await fetch('/api/native/git/knowledge-files', { + const response = await fetch(isGithubMode ? GITHUB_KNOWLEDGE_FILES_URL : NATIVE_GIT_KNOWLEDGE_FILES_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' @@ -138,7 +142,7 @@ const DocumentInformation: React.FC = ({ body: JSON.stringify({ files: fileContents }) }); - if (response.status === 201) { + if (response.status === 201 || response.ok) { const result = await response.json(); console.log('Files uploaded result:', result); setKnowledgeDocumentRepositoryUrl(result.repoUrl); @@ -150,6 +154,9 @@ const DocumentInformation: React.FC = ({ title: 'Document uploaded successfully!', message: 'Documents have been submitted to local taxonomy knowledge docs repo to be referenced in the knowledge submission.' }; + if (result.prUrl !== '') { + alertInfo.link = result.prUrl; + } setAlertInfo(alertInfo); } else { console.error('Knowledge document upload failed:', response.statusText); @@ -208,117 +215,102 @@ const DocumentInformation: React.FC = ({ }; return ( -
- - Document Information * -

- ), - id: 'doc-info-id' - }} - titleDescription="Add the relevant document's information" - /> - -
- - -
-
- setIsModalOpen(false)} - actions={[ - , - - ]} - > -

{modalText}

-
- {!useFileUpload ? ( - <> - - setKnowledgeDocumentRepositoryUrl(value)} - /> - - - setKnowledgeDocumentCommit(value)} - onBlur={() => validateCommit(knowledgeDocumentCommit)} - /> - {validCommit === ValidatedOptions.error && ( - - - } variant={validCommit}> - Valid commit SHA is required. - - - - )} + + + Document Information + {`Add the relevant document's information.`} + + +
+ + + + + + + + + - - setDocumentName(value)} - onBlur={() => validateDocumentName(documentName)} - /> - {validDocumentName === ValidatedOptions.error && ( - - - } variant={validDocumentName}> - Required field - - - - )} - - - ) : ( - <> - - - - )} + {!useFileUpload ? ( + <> + + setKnowledgeDocumentRepositoryUrl(value)} + onBlur={() => validateRepo(knowledgeDocumentRepositoryUrl)} + /> + + + setKnowledgeDocumentCommit(value)} + onBlur={() => validateCommit(knowledgeDocumentCommit)} + /> + {validCommit === ValidatedOptions.error && ( + + + } variant={validCommit}> + Valid commit SHA is required. + + + + )} + + + setDocumentName(value)} + onBlur={() => validateDocumentName(documentName)} + /> + {validDocumentName === ValidatedOptions.error && ( + + + } variant={validDocumentName}> + Required field + + + + )} + + + ) : ( + + + + + )} + +
{alertInfo && ( = ({ )} -
+ {isModalOpen ? ( + setIsModalOpen(false)}> + + {modalText} + + + + + + ) : null} + ); }; diff --git a/src/components/Contribute/Knowledge/FilePathInformation/FilePathInformation.tsx b/src/components/Contribute/Knowledge/FilePathInformation/FilePathInformation.tsx index daec2be9..19e3847e 100644 --- a/src/components/Contribute/Knowledge/FilePathInformation/FilePathInformation.tsx +++ b/src/components/Contribute/Knowledge/FilePathInformation/FilePathInformation.tsx @@ -1,31 +1,29 @@ import React from 'react'; import PathService from '@/components/PathService/PathService'; -import { FormFieldGroupHeader, FormGroup } from '@patternfly/react-core'; +import { Form, FormGroup, Flex, FlexItem, Content } from '@patternfly/react-core'; interface Props { - reset?: boolean; path: string; - setFilePath: React.Dispatch>; + setFilePath: (val: string) => void; } -const FilePathInformation: React.FC = ({ reset, path, setFilePath }) => { +const FilePathInformation: React.FC = ({ path, setFilePath }) => { return ( -
- - Taxonomy Directory Path * -

- ), - id: 'file-path-info-id' - }} - titleDescription="Specify the file path for the QnA and Attribution files. Once path is selected, please add your leaf directory name for your contribution." - /> - - - -
+ + + File Path Information + + Specify the file path for the QnA and Attribution files. Once path is selected, please add your leaf directory name for your contribution. + + + +
+ + + +
+
+
); }; diff --git a/src/components/Contribute/Knowledge/Github/DocumentInformation/DocumentInformation.tsx b/src/components/Contribute/Knowledge/Github/DocumentInformation/DocumentInformation.tsx deleted file mode 100644 index 1c6ab744..00000000 --- a/src/components/Contribute/Knowledge/Github/DocumentInformation/DocumentInformation.tsx +++ /dev/null @@ -1,383 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { UploadFile } from '../../UploadFile'; -import { checkKnowledgeFormCompletion } from '../../validation'; -import { KnowledgeFormData } from '@/types'; -import { - ValidatedOptions, - FormFieldGroupHeader, - FormGroup, - Button, - Modal, - ModalVariant, - TextInput, - FormHelperText, - HelperText, - HelperTextItem, - AlertGroup, - Alert, - AlertActionCloseButton, - AlertActionLink, - ModalHeader, - ModalBody, - ModalFooter -} from '@patternfly/react-core'; -import { ExclamationCircleIcon } from '@patternfly/react-icons'; - -interface Props { - reset: boolean; - isEditForm?: boolean; - knowledgeFormData: KnowledgeFormData; - setDisableAction: React.Dispatch>; - knowledgeDocumentRepositoryUrl: string; - setKnowledgeDocumentRepositoryUrl: React.Dispatch>; - knowledgeDocumentCommit: string; - setKnowledgeDocumentCommit: React.Dispatch>; - documentName: string; - setDocumentName: React.Dispatch>; -} - -const DocumentInformation: React.FC = ({ - reset, - isEditForm, - knowledgeFormData, - setDisableAction, - knowledgeDocumentRepositoryUrl, - setKnowledgeDocumentRepositoryUrl, - knowledgeDocumentCommit, - setKnowledgeDocumentCommit, - documentName, - setDocumentName -}) => { - const [useFileUpload, setUseFileUpload] = useState(true); - const [uploadedFiles, setUploadedFiles] = useState([]); - const [isModalOpen, setIsModalOpen] = useState(false); - const [modalText, setModalText] = useState(); - const [alertInfo, setAlertInfo] = useState(); - const [validRepo, setValidRepo] = useState(); - const [validCommit, setValidCommit] = useState(); - const [validDocumentName, setValidDocumentName] = useState(); - - interface AlertInfo { - type: 'success' | 'danger' | 'info'; - title: string; - message: string; - link?: string; - } - - useEffect(() => { - setValidRepo(ValidatedOptions.default); - setValidCommit(ValidatedOptions.default); - setValidDocumentName(ValidatedOptions.default); - }, [reset]); - - useEffect(() => { - if (isEditForm) { - setValidRepo(ValidatedOptions.success); - setValidCommit(ValidatedOptions.success); - setValidDocumentName(ValidatedOptions.success); - } - }, [isEditForm]); - - const validateRepo = (repoStr: string) => { - const repo = repoStr.trim(); - if (repo.length === 0) { - setDisableAction(true); - setValidRepo(ValidatedOptions.error); - return; - } - try { - new URL(repo); - setValidRepo(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; - } catch (e) { - setDisableAction(true); - setValidRepo(ValidatedOptions.warning); - return; - } - }; - - const validateCommit = (commitStr: string) => { - const commit = commitStr.trim(); - if (commit.length > 0) { - setValidCommit(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; - } - setDisableAction(true); - setValidCommit(ValidatedOptions.error); - return; - }; - - const validateDocumentName = (document: string) => { - const documentName = document.trim(); - if (documentName.length > 0) { - setValidDocumentName(ValidatedOptions.success); - setDisableAction(!checkKnowledgeFormCompletion(knowledgeFormData)); - return; - } - setDisableAction(true); - setValidDocumentName(ValidatedOptions.error); - return; - }; - - const handleFilesChange = (files: File[]) => { - setUploadedFiles(files); - }; - - const handleDocumentUpload = async () => { - if (uploadedFiles.length > 0) { - const alertInfo: AlertInfo = { - type: 'info', - title: 'Document upload(s) in progress!', - message: 'Document upload(s) is in progress. You will be notified once the upload successfully completes.' - }; - setAlertInfo(alertInfo); - - const fileContents: { fileName: string; fileContent: string }[] = []; - - await Promise.all( - uploadedFiles.map( - (file) => - new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = (e) => { - const fileContent = e.target!.result as string; - fileContents.push({ fileName: file.name, fileContent }); - resolve(); - }; - reader.onerror = reject; - reader.readAsText(file); - }) - ) - ); - - if (fileContents.length === uploadedFiles.length) { - const response = await fetch('/api/github/knowledge-files', { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ files: fileContents }) - }); - - if (!response.ok) { - const alertInfo: AlertInfo = { - type: 'danger', - title: 'Document upload failed', - message: `Upload failed for the added documents. ${response.statusText}` - }; - setAlertInfo(alertInfo); - new Error(response.statusText || 'Document upload failed'); - return; - } - - const result = await response.json(); - - setKnowledgeDocumentRepositoryUrl(result.repoUrl); - setKnowledgeDocumentCommit(result.commitSha); - setDocumentName(result.documentNames.join(', ')); // Populate the patterns field - console.log('Files uploaded:', result.documentNames); - const alertInfo: AlertInfo = { - type: 'success', - title: 'Document uploaded successfully!', - message: 'Documents have been uploaded to your repo to be referenced in the knowledge submission.' - }; - if (result.prUrl !== '') { - alertInfo.link = result.prUrl; - } - setAlertInfo(alertInfo); - } - } - }; - - const onCloseSuccessAlert = () => { - setAlertInfo(undefined); - }; - - const handleAutomaticUpload = () => { - if (knowledgeDocumentRepositoryUrl.length > 0 || knowledgeDocumentCommit.length > 0 || documentName.length > 0) { - setModalText('Switching to automatic upload will clear the document information. Are you sure you want to continue?'); - setIsModalOpen(true); - } else { - setUseFileUpload(true); - } - }; - - const handleManualUpload = () => { - if (uploadedFiles.length > 0) { - setModalText('Switching to manual upload will clear the uploaded files. Are you sure you want to continue?'); - setIsModalOpen(true); - } else { - setUseFileUpload(false); - } - }; - - const handleModalContinue = () => { - if (useFileUpload) { - setUploadedFiles([]); - } else { - setKnowledgeDocumentRepositoryUrl(''); - setValidRepo(ValidatedOptions.default); - setKnowledgeDocumentCommit(''); - setValidCommit(ValidatedOptions.default); - setDocumentName(''); - setValidDocumentName(ValidatedOptions.default); - } - setUseFileUpload(!useFileUpload); - setIsModalOpen(false); - }; - - return ( -
- - -
- - -
-
- setIsModalOpen(false)} - aria-labelledby="file-upload-switch-modal-title" - aria-describedby="file-upload-switch-body-variant" - > - - -

{modalText}

-
- - - , - - -
- - {!useFileUpload ? ( - <> - - setKnowledgeDocumentRepositoryUrl(value)} - onBlur={() => validateRepo(knowledgeDocumentRepositoryUrl)} - /> - {validRepo === ValidatedOptions.error && ( - - - } variant={validRepo}> - Required field - - - - )} - {validRepo === ValidatedOptions.warning && ( - - - } variant="error"> - Please enter a valid URL. - - - - )} - - - setKnowledgeDocumentCommit(value)} - onBlur={() => validateCommit(knowledgeDocumentCommit)} - /> - {validCommit === ValidatedOptions.error && ( - - - } variant={validCommit}> - Valid commit SHA is required. - - - - )} - - - setDocumentName(value)} - onBlur={() => validateDocumentName(documentName)} - /> - {validDocumentName === ValidatedOptions.error && ( - - - } variant={validDocumentName}> - Required field - - - - )} - - - ) : ( - <> - - - - )} - - {alertInfo && ( - - } - actionLinks={ - alertInfo.link && ( - <> - - View it here - - - ) - } - > - {alertInfo.message} - - - )} -
- ); -}; - -export default DocumentInformation; diff --git a/src/components/Contribute/Knowledge/Github/KnowledgeQuestionAnswerPairs/KnowledgeQuestionAnswerPairs.tsx b/src/components/Contribute/Knowledge/Github/KnowledgeQuestionAnswerPairs/KnowledgeQuestionAnswerPairs.tsx deleted file mode 100644 index 16166173..00000000 --- a/src/components/Contribute/Knowledge/Github/KnowledgeQuestionAnswerPairs/KnowledgeQuestionAnswerPairs.tsx +++ /dev/null @@ -1,492 +0,0 @@ -// src/components/Contribute/Knowledge/KnowledgeQuestionAnswerPairs/KnowledgeQuestionAnswerPairs.tsx -import React, { useCallback, useEffect, useRef, useState } from 'react'; -import { KnowledgeSeedExample, QuestionAndAnswerPair } from '@/types'; -import { - FormGroup, - Tooltip, - Button, - TextArea, - FormHelperText, - HelperText, - HelperTextItem, - ValidatedOptions, - Modal, - ModalVariant, - Alert, - Switch, - Spinner, - Stack, - StackItem, - Card, - CardHeader, - CardBody, - ExpandableSection, - Content, - FormFieldGroupHeader -} from '@patternfly/react-core'; -import { CatalogIcon, ExclamationCircleIcon } from '@patternfly/react-icons'; - -interface KnowledgeFile { - filename: string; - content: string; - commitSha: string; - commitDate?: string; -} - -interface Props { - seedExample: KnowledgeSeedExample; - seedExampleIndex: number; - handleContextInputChange: (seedExampleIndex: number, contextValue: string) => void; - handleContextBlur: (seedExampleIndex: number) => void; - handleQuestionInputChange: (seedExampleIndex: number, questionAndAnswerIndex: number, questionValue: string) => void; - handleQuestionBlur: (seedExampleIndex: number, questionAndAnswerIndex: number) => void; - handleAnswerInputChange: (seedExampleIndex: number, questionAndAnswerIndex: number, answerValue: string) => void; - handleAnswerBlur: (seedExampleIndex: number, questionAndAnswerIndex: number) => void; - addDocumentInfo: (repositoryUrl: string, commitSha: string, docName: string) => void; - repositoryUrl: string; - commitSha: string; -} - -const KnowledgeQuestionAnswerPairs: React.FC = ({ - seedExample, - seedExampleIndex, - handleContextInputChange, - handleContextBlur, - handleQuestionInputChange, - handleQuestionBlur, - handleAnswerInputChange, - handleAnswerBlur, - addDocumentInfo, - repositoryUrl, - commitSha -}) => { - const [isModalOpen, setIsModalOpen] = useState(false); - const [knowledgeFiles, setKnowledgeFiles] = useState([]); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(''); - const [expandedFiles, setExpandedFiles] = useState>({}); - const [selectedWordCount, setSelectedWordCount] = useState(0); - const [showAllCommits, setShowAllCommits] = useState(false); - const [contextWordCount, setContextWordCount] = useState(0); - const MAX_WORDS = 500; - - // Ref for the
 elements to track selections TODO: figure out how to make text expansions taller in PF without a custom-pre
-  const preRefs = useRef>({});
-
-  const LOCAL_TAXONOMY_DOCS_ROOT_DIR =
-    `${process.env.NEXT_PUBLIC_LOCAL_TAXONOMY_ROOT_DIR}/taxonomy-knowledge-docs` || `${process.env.HOME}/.instructlab-ui/taxonomy-knowledge-docs`;
-
-  const fetchKnowledgeFiles = async () => {
-    setIsLoading(true);
-    setError('');
-    try {
-      const response = await fetch('/api/github/knowledge-files', {
-        method: 'GET',
-        headers: { 'Content-Type': 'application/json' }
-      });
-
-      const result = await response.json();
-      if (response.ok) {
-        setKnowledgeFiles(result.files);
-        console.log('Fetched knowledge files:', result.files);
-      } else {
-        setError(result.error || 'Failed to fetch knowledge files.');
-        console.error('Error fetching knowledge files:', result.error);
-      }
-    } catch (err) {
-      setError('An error occurred while fetching knowledge files.');
-      console.error('Error fetching knowledge files:', err);
-    } finally {
-      setIsLoading(false);
-    }
-  };
-
-  const handleOpenModal = () => {
-    setIsModalOpen(true);
-    fetchKnowledgeFiles();
-  };
-
-  const handleCloseModal = () => {
-    setIsModalOpen(false);
-    setKnowledgeFiles([]);
-    setError('');
-    setSelectedWordCount(0);
-    setShowAllCommits(false);
-    window.getSelection()?.removeAllRanges();
-  };
-
-  const handleUseSelectedText = (file: KnowledgeFile) => {
-    const selection = window.getSelection();
-    const selectedText = selection?.toString().trim();
-
-    if (!selectedText) {
-      alert('Please select the text you want to use as context.');
-      return;
-    }
-
-    repositoryUrl = `${LOCAL_TAXONOMY_DOCS_ROOT_DIR}/${file.filename}`;
-    const commitShaValue = file.commitSha;
-    const docName = file.filename;
-
-    console.log(
-      `handleUseSelectedText: selectedText="${selectedText}", repositoryUrl=${repositoryUrl}, commitSha=${commitShaValue}, docName=${docName}`
-    );
-
-    handleContextInputChange(seedExampleIndex, selectedText);
-    handleContextBlur(seedExampleIndex);
-    addDocumentInfo(repositoryUrl, commitShaValue, docName);
-    handleCloseModal();
-  };
-
-  const updateSelectedWordCount = (filename: string) => {
-    const selection = window.getSelection();
-    const preElement = preRefs.current[filename];
-    if (selection && preElement) {
-      const anchorNode = selection.anchorNode;
-      const focusNode = selection.focusNode;
-
-      if (preElement.contains(anchorNode) && preElement.contains(focusNode)) {
-        const selectedText = selection.toString().trim();
-        const wordCount = selectedText.split(/\s+/).filter((word) => word.length > 0).length;
-        setSelectedWordCount(wordCount);
-      } else {
-        setSelectedWordCount(0);
-      }
-    }
-  };
-
-  // Attach event listeners for selection changes
-  useEffect(() => {
-    if (isModalOpen) {
-      const handleSelectionChange = () => {
-        // Iterate through all expanded files and update word count
-        Object.keys(expandedFiles).forEach((filename) => {
-          if (expandedFiles[filename]) {
-            updateSelectedWordCount(filename);
-          }
-        });
-      };
-      document.addEventListener('selectionchange', handleSelectionChange);
-      return () => {
-        document.removeEventListener('selectionchange', handleSelectionChange);
-      };
-    } else {
-      setSelectedWordCount(0);
-    }
-  }, [isModalOpen, expandedFiles]);
-
-  const toggleFileContent = (filename: string) => {
-    setExpandedFiles((prev) => ({
-      ...prev,
-      [filename]: !prev[filename]
-    }));
-    console.log(`toggleFileContent: filename=${filename}, expanded=${!expandedFiles[filename]}`);
-  };
-
-  // Group files by commitSha
-  const groupedFiles = knowledgeFiles.reduce>((acc, file) => {
-    if (!acc[file.commitSha]) {
-      acc[file.commitSha] = [];
-    }
-    acc[file.commitSha].push(file);
-    return acc;
-  }, {});
-
-  // Extract commit dates for sorting
-  const commitDateMap: Record = {};
-  knowledgeFiles.forEach((file) => {
-    if (file.commitDate && !commitDateMap[file.commitSha]) {
-      commitDateMap[file.commitSha] = file.commitDate;
-    }
-  });
-
-  // Sort the commit SHAs based on commitDate in descending order (latest first)
-  const sortedCommitShas = Object.keys(groupedFiles).sort((a, b) => {
-    const dateA = new Date(commitDateMap[a] || '').getTime();
-    const dateB = new Date(commitDateMap[b] || '').getTime();
-    return dateB - dateA;
-  });
-
-  // Enforce single commit SHA and repository URL
-  const isSameCommit = (fileCommitSha: string): boolean => {
-    if (!commitSha) {
-      return true;
-    }
-    return fileCommitSha === commitSha;
-  };
-
-  // Determine which commits to display based on the toggle
-  const commitsToDisplay = showAllCommits ? sortedCommitShas : sortedCommitShas.slice(0, 1);
-
-  const setPreRef = useCallback(
-    (filename: string) => (el: HTMLPreElement | null) => {
-      preRefs.current[filename] = el;
-    },
-    []
-  );
-
-  // TODO: replace with a tokenizer library
-  const countWords = (text: string) => {
-    return text.trim().split(/\s+/).filter(Boolean).length;
-  };
-
-  // Update word count whenever context changes
-  useEffect(() => {
-    setContextWordCount(countWords(seedExample.context));
-  }, [seedExample.context]);
-
-  // Handle context input change with word count validation
-  const onContextChange = (_event: React.FormEvent, contextValue: string) => {
-    const wordCount = countWords(contextValue);
-    if (wordCount <= MAX_WORDS) {
-      handleContextInputChange(seedExampleIndex, contextValue);
-    } else {
-      // allow the overage and show validation error
-      handleContextInputChange(seedExampleIndex, contextValue);
-    }
-  };
-
-  return (
-    
-      Select context from your knowledge files} position="top">
-        
-      
-
-