diff --git a/src/app/contribute/knowledge/[...slug]/page.tsx b/src/app/contribute/knowledge/[...slug]/page.tsx index 932eebb0..cb74a00c 100644 --- a/src/app/contribute/knowledge/[...slug]/page.tsx +++ b/src/app/contribute/knowledge/[...slug]/page.tsx @@ -12,7 +12,7 @@ const KnowledgeViewPage = async ({ params }: PageProps) => { return ( - + ); }; diff --git a/src/app/contribute/knowledge/edit/[...slug]/page.tsx b/src/app/contribute/knowledge/edit/[...slug]/page.tsx index cfcc684d..e9217269 100644 --- a/src/app/contribute/knowledge/edit/[...slug]/page.tsx +++ b/src/app/contribute/knowledge/edit/[...slug]/page.tsx @@ -12,7 +12,7 @@ const EditKnowledgePage = async ({ params }: PageProps) => { return ( - + ); }; diff --git a/src/app/contribute/skill/[...slug]/page.tsx b/src/app/contribute/skill/[...slug]/page.tsx index 56833ba4..ad08b51d 100644 --- a/src/app/contribute/skill/[...slug]/page.tsx +++ b/src/app/contribute/skill/[...slug]/page.tsx @@ -12,7 +12,7 @@ const SkillViewPage = async ({ params }: PageProps) => { return ( - + ); }; diff --git a/src/app/contribute/skill/edit/[...slug]/page.tsx b/src/app/contribute/skill/edit/[...slug]/page.tsx index 72e4ac17..276b7656 100644 --- a/src/app/contribute/skill/edit/[...slug]/page.tsx +++ b/src/app/contribute/skill/edit/[...slug]/page.tsx @@ -12,7 +12,7 @@ const EditSkillPage = async ({ params }: PageProps) => { return ( - + ); }; diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx index dc422479..1c6ab36d 100644 --- a/src/app/dashboard/page.tsx +++ b/src/app/dashboard/page.tsx @@ -2,7 +2,6 @@ 'use client'; import React from 'react'; -import '@patternfly/react-core/dist/styles/base.css'; import { AppLayout } from '@/components/AppLayout'; import DashboardPage from '@/components/Dashboard/DashboardPage'; diff --git a/src/app/documents/page.tsx b/src/app/documents/page.tsx index bc0b985e..1387b543 100644 --- a/src/app/documents/page.tsx +++ b/src/app/documents/page.tsx @@ -2,7 +2,6 @@ 'use client'; import React from 'react'; -import '@patternfly/react-core/dist/styles/base.css'; import { AppLayout } from '@/components/AppLayout'; import Documents from '@/components/Documents/Documents'; diff --git a/src/app/experimental/chat-eval/page.tsx b/src/app/experimental/chat-eval/page.tsx index 50eb3200..2c4da5bd 100644 --- a/src/app/experimental/chat-eval/page.tsx +++ b/src/app/experimental/chat-eval/page.tsx @@ -2,7 +2,6 @@ 'use client'; import * as React from 'react'; -import '@patternfly/react-core/dist/styles/base.css'; import { AppLayout, FeaturePages } from '@/components/AppLayout'; import ChatEval from '@/components/Experimental/ChatEval/ChatEval'; diff --git a/src/app/experimental/fine-tune/page.tsx b/src/app/experimental/fine-tune/page.tsx index 8b347fe5..5f13b32b 100644 --- a/src/app/experimental/fine-tune/page.tsx +++ b/src/app/experimental/fine-tune/page.tsx @@ -2,7 +2,6 @@ 'use client'; import * as React from 'react'; -import '@patternfly/react-core/dist/styles/base.css'; import { AppLayout, FeaturePages } from '@/components/AppLayout'; import FineTuning from '@/components/Experimental/FineTuning/FineTuningJobs'; diff --git a/src/app/index.tsx b/src/app/index.tsx index 32991ff3..8f4339e6 100644 --- a/src/app/index.tsx +++ b/src/app/index.tsx @@ -2,16 +2,8 @@ 'use client'; import * as React from 'react'; -import { ThemeProvider } from '../context/ThemeContext'; -import '@patternfly/react-core/dist/styles/base.css'; -import Dashboard from './dashboard/page'; +import DashboardPage from './dashboard/page'; -const Home: React.FunctionComponent = () => { - return ( - - - - ); -}; +const Home: React.FunctionComponent = () => ; export default Home; diff --git a/src/components/Contribute/ContributePageHeader.tsx b/src/components/Contribute/ContributePageHeader.tsx index e47cf6f7..3c4737f6 100644 --- a/src/components/Contribute/ContributePageHeader.tsx +++ b/src/components/Contribute/ContributePageHeader.tsx @@ -4,8 +4,9 @@ import * as React from 'react'; import { useRouter } from 'next/navigation'; import { PageSection, Flex, FlexItem, Title, PageBreadcrumb, Breadcrumb, BreadcrumbItem } from '@patternfly/react-core'; -import { EditFormData, KnowledgeFormData, SkillFormData } from '@/types'; +import { ContributionFormData, EditFormData, KnowledgeFormData, SkillFormData } from '@/types'; import PageDescriptionWithHelp from '@/components/Common/PageDescriptionWithHelp'; +import { isDraftDataExist } from '@/components/Contribute/Utils/autoSaveUtils'; import { DraftContributionLabel, KnowledgeContributionLabel, @@ -15,6 +16,7 @@ import { interface Props { editFormData?: EditFormData; + draftData?: ContributionFormData; isEdit?: boolean; isSkill?: boolean; description: React.ReactNode; @@ -25,6 +27,7 @@ interface Props { const ContributePageHeader: React.FC = ({ editFormData, + draftData, isEdit = false, isSkill = false, description, @@ -33,9 +36,10 @@ const ContributePageHeader: React.FC = ({ actions }) => { const router = useRouter(); + const currentData = draftData || editFormData?.formData; const contributionType = isSkill ? 'skill' : 'knowledge'; - const viewUrl = `/contribute/${isSkill ? 'skill' : 'knowledge'}/${editFormData?.formData.branchName}${editFormData?.isDraft ? '/isDraft' : ''}`; - const contributionTitle = editFormData?.formData?.submissionSummary || `Draft ${contributionType} contribution`; + const viewUrl = `/contribute/${isSkill ? 'skill' : 'knowledge'}/${currentData?.branchName}`; + const contributionTitle = currentData?.submissionSummary || `Draft ${contributionType} contribution`; return ( <> @@ -58,9 +62,7 @@ const ContributePageHeader: React.FC = ({ router.push(viewUrl); }} > - {!editFormData - ? `Contribute ${contributionType}` - : editFormData?.formData?.submissionSummary || `Draft ${contributionType} contribution`} + {!editFormData ? `Contribute ${contributionType}` : currentData?.submissionSummary || `Draft ${contributionType} contribution`} ) : null} @@ -78,16 +80,16 @@ const ContributePageHeader: React.FC = ({ {!editFormData ? `Submit ${contributionType} contribution` - : editFormData?.formData?.submissionSummary || `Draft ${isSkill ? 'skill' : 'knowledge'} contribution`} + : currentData?.submissionSummary || `Draft ${isSkill ? 'skill' : 'knowledge'} contribution`} {isSkill ? : } - {editFormData?.isDraft ? ( + {currentData && isDraftDataExist(currentData.branchName) ? ( ) : null} - {editFormData && !editFormData.isSubmitted ? ( + {!editFormData ? ( diff --git a/src/components/Contribute/Knowledge/Edit/EditKnowledge.tsx b/src/components/Contribute/Knowledge/Edit/EditKnowledge.tsx index e29c4414..082dfd85 100644 --- a/src/components/Contribute/Knowledge/Edit/EditKnowledge.tsx +++ b/src/components/Contribute/Knowledge/Edit/EditKnowledge.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useSession } from 'next-auth/react'; -import { KnowledgeEditFormData } from '@/types'; +import { KnowledgeEditFormData, KnowledgeFormData } from '@/types'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Modal, ModalVariant, ModalBody } from '@patternfly/react-core'; @@ -13,26 +13,22 @@ import KnowledgeForm from '@/components/Contribute/Knowledge/Edit/KnowledgeForm' interface EditKnowledgeClientComponentProps { branchName: string; - isDraft: boolean; } -const EditKnowledge: React.FC = ({ branchName, isDraft }) => { +const EditKnowledge: React.FC = ({ branchName }) => { const { data: session } = useSession(); const [isLoading, setIsLoading] = useState(true); const [loadingMsg, setLoadingMsg] = useState(''); - const [knowledgeEditFormData, setKnowledgeEditFormData] = useState(); + const [knowledgeData, setKnowledgeData] = useState<{ draftData?: KnowledgeFormData; editFormData?: KnowledgeEditFormData }>(); const router = useRouter(); useEffect(() => { - if (isDraft) { - fetchDraftKnowledgeChanges({ branchName, setIsLoading, setLoadingMsg, setKnowledgeEditFormData }); - return; - } - - setLoadingMsg('Fetching knowledge data from branch : ' + branchName); + setLoadingMsg('Fetching knowledge data'); const fetchFormData = async () => { + const draftData = fetchDraftKnowledgeChanges(branchName); + const { editFormData, error } = await fetchKnowledgeBranchChanges(session, branchName); - if (error) { + if (error && !draftData) { setLoadingMsg(error); return; } @@ -43,10 +39,10 @@ const EditKnowledge: React.FC = ({ branchName } setIsLoading(false); - setKnowledgeEditFormData(editFormData); + setKnowledgeData({ draftData, editFormData }); }; fetchFormData(); - }, [branchName, isDraft, session]); + }, [branchName, session]); const handleOnClose = () => { router.push('/dashboard'); @@ -63,7 +59,7 @@ const EditKnowledge: React.FC = ({ branchName ); } - return ; + return ; }; export default EditKnowledge; diff --git a/src/components/Contribute/Knowledge/Edit/KnowledgeForm.tsx b/src/components/Contribute/Knowledge/Edit/KnowledgeForm.tsx index bbaf5e39..49184869 100644 --- a/src/components/Contribute/Knowledge/Edit/KnowledgeForm.tsx +++ b/src/components/Contribute/Knowledge/Edit/KnowledgeForm.tsx @@ -21,7 +21,7 @@ import { ActionGroupAlertContent } from '@/components/Contribute/types'; import KnowledgeContributionSidePanelHelp from '@/components/SidePanelContents/KnowledgeContributionSidePanelHelp'; import ContributePageHeader from '@/components/Contribute/ContributePageHeader'; import ContributeAlertGroup from '@/components/Contribute/ContributeAlertGroup'; -import { storeDraftData, deleteDraftData, doSaveDraft, isDraftDataExist } from '@/components/Contribute/Utils/autoSaveUtils'; +import { storeDraftData, deleteDraftData, formDataChanged, isDraftDataExist } from '@/components/Contribute/Utils/autoSaveUtils'; import { getDefaultKnowledgeFormData } from '@/components/Contribute/Utils/contributionUtils'; import { UploadKnowledgeDocuments } from '@/components/Contribute/Utils/documentUtils'; import { submitKnowledgeData } from '@/components/Contribute/Utils/submitUtils'; @@ -34,17 +34,19 @@ import '../knowledge.css'; export interface KnowledgeFormProps { knowledgeEditFormData?: KnowledgeEditFormData; + draftData?: KnowledgeFormData; } -export const KnowledgeForm: React.FunctionComponent = ({ knowledgeEditFormData }) => { +export const KnowledgeForm: React.FunctionComponent = ({ knowledgeEditFormData, draftData }) => { const router = useRouter(); const { data: session } = useSession(); + const currentData: KnowledgeFormData | undefined = draftData || knowledgeEditFormData?.formData; const [knowledgeFormData, setKnowledgeFormData] = React.useState( - knowledgeEditFormData?.formData + currentData ? { - ...knowledgeEditFormData.formData, - seedExamples: knowledgeEditFormData.formData.seedExamples.map((example, index) => ({ + ...currentData, + seedExamples: currentData.seedExamples.map((example, index) => ({ ...example, isExpanded: index === 0, immutable: example.immutable !== undefined ? example.immutable : true, // Ensure immutable is set @@ -59,10 +61,12 @@ export const KnowledgeForm: React.FunctionComponent = ({ kno answerValidationError: qa.answerValidationError || '' })) })), - uploadedFiles: knowledgeEditFormData.formData.uploadedFiles + uploadedFiles: currentData.uploadedFiles } : getDefaultKnowledgeFormData() ); + const lastUpdateRef = React.useRef(JSON.stringify(knowledgeFormData)); + const [actionGroupAlertContent, setActionGroupAlertContent] = React.useState(); const [scrollableRef, setScrollableRef] = React.useState(); @@ -79,21 +83,23 @@ export const KnowledgeForm: React.FunctionComponent = ({ kno const setFilePath = React.useCallback((filePath: string) => setKnowledgeFormData((prev) => ({ ...prev, filePath })), []); React.useEffect(() => { - const storeDraft = async () => { - // If no change in the form data and there is no existing draft present, skip storing the draft. - if (!doSaveDraft(knowledgeFormData) && !isDraftDataExist(knowledgeFormData.branchName)) return; - - const draftContributionStr = JSON.stringify(knowledgeFormData); - storeDraftData( - knowledgeFormData.branchName, - knowledgeFormData.filePath, - draftContributionStr, - !!knowledgeEditFormData?.isSubmitted, - knowledgeEditFormData?.oldFilesPath || '' - ); + const storeDraft = () => { + if (isDraftDataExist(knowledgeFormData.branchName) && !formDataChanged(knowledgeFormData, knowledgeEditFormData?.formData)) { + deleteDraftData(knowledgeFormData.branchName); + lastUpdateRef.current = JSON.stringify(knowledgeFormData); + return; + } + + const draftChanges = formDataChanged(knowledgeFormData, JSON.parse(lastUpdateRef.current)); + if (draftChanges) { + const draftContributionStr = JSON.stringify(knowledgeFormData); + lastUpdateRef.current = draftContributionStr; + storeDraftData(knowledgeFormData.branchName, knowledgeFormData.filePath, draftContributionStr, knowledgeEditFormData?.oldFilesPath || ''); + } }; + storeDraft(); - }, [knowledgeEditFormData?.isSubmitted, knowledgeEditFormData?.oldFilesPath, knowledgeFormData]); + }, [knowledgeEditFormData?.formData, knowledgeEditFormData?.oldFilesPath, knowledgeFormData]); const updateActionGroupAlertContent = (newContent: ActionGroupAlertContent | undefined) => { // In order to restart the timer, we must re-create the Alert not re-use it. Clear it for one round then set the new info @@ -116,11 +122,7 @@ export const KnowledgeForm: React.FunctionComponent = ({ kno return isDocUploaded; } - const result = await submitKnowledgeData( - knowledgeFormData, - updateActionGroupAlertContent, - knowledgeEditFormData?.isSubmitted ? knowledgeEditFormData : undefined - ); + const result = await submitKnowledgeData(knowledgeFormData, updateActionGroupAlertContent, knowledgeEditFormData); if (result) { //Remove draft if present in the local storage deleteDraftData(knowledgeFormData.branchName); @@ -135,6 +137,7 @@ export const KnowledgeForm: React.FunctionComponent = ({ kno } @@ -143,8 +146,8 @@ export const KnowledgeForm: React.FunctionComponent = ({ kno @@ -196,7 +199,7 @@ export const KnowledgeForm: React.FunctionComponent = ({ kno isValid && handleSubmit()}> - {knowledgeEditFormData?.isSubmitted ? 'Update' : 'Submit'} + {knowledgeEditFormData ? 'Update' : 'Submit'} diff --git a/src/components/Contribute/Knowledge/KnowledgeFormActions.tsx b/src/components/Contribute/Knowledge/KnowledgeFormActions.tsx index a967d710..b97a4380 100644 --- a/src/components/Contribute/Knowledge/KnowledgeFormActions.tsx +++ b/src/components/Contribute/Knowledge/KnowledgeFormActions.tsx @@ -215,9 +215,7 @@ export const KnowledgeFormActions: React.FunctionComponent = ({ {isDevMode && setKnowledgeFormData ? Autofill : null} {!setKnowledgeFormData ? ( - router.push(`/contribute/knowledge/edit/${knowledgeFormData.branchName}${isDraft ? '/isDraft' : ''}`)}> - Edit contribution - + router.push(`/contribute/knowledge/edit/${knowledgeFormData.branchName}`)}>Edit contribution ) : null} {setKnowledgeFormData ? setIsUploadYamlModalOpen(true)}>Upload YAML : null} handleViewYaml()}>View YAML diff --git a/src/components/Contribute/Knowledge/View/ViewKnowledge.tsx b/src/components/Contribute/Knowledge/View/ViewKnowledge.tsx index 3c647475..8b98799c 100644 --- a/src/components/Contribute/Knowledge/View/ViewKnowledge.tsx +++ b/src/components/Contribute/Knowledge/View/ViewKnowledge.tsx @@ -2,7 +2,7 @@ 'use client'; import * as React from 'react'; -import { KnowledgeEditFormData } from '@/types'; +import { KnowledgeEditFormData, KnowledgeFormData } from '@/types'; import { PageSection, Flex, @@ -15,6 +15,7 @@ import { import KnowledgeContributionSidePanelHelp from '@/components/SidePanelContents/KnowledgeContributionSidePanelHelp'; import ViewContributionSection from '@/components/Common/ViewContributionSection'; import { ActionGroupAlertContent } from '@/components/Contribute/types'; +import { isDraftDataExist } from '@/components/Contribute/Utils/autoSaveUtils'; import ContributePageHeader from '@/components/Contribute/ContributePageHeader'; import ContributeAlertGroup from '@/components/Contribute/ContributeAlertGroup'; import KnowledgeFormActions from '@/components/Contribute/Knowledge/KnowledgeFormActions'; @@ -23,28 +24,35 @@ import KnowledgeSeedExampleCard from '@/components/Contribute/Knowledge/Knowledg import '../knowledge.css'; interface ViewKnowledgeProps { - knowledgeEditFormData: KnowledgeEditFormData; + knowledgeEditFormData?: KnowledgeEditFormData; + draftData?: KnowledgeFormData; } -const ViewKnowledge: React.FC = ({ knowledgeEditFormData }) => { +const ViewKnowledge: React.FC = ({ knowledgeEditFormData, draftData }) => { const [actionGroupAlertContent, setActionGroupAlertContent] = React.useState(); const [scrollableRef, setScrollableRef] = React.useState(); const [bodyRef, setBodyRef] = React.useState(); + const currentData = draftData || knowledgeEditFormData?.formData; + + if (!currentData) { + return null; + } return ( } helpText="Learn more about knowledge contributions" actions={ } @@ -61,8 +69,8 @@ const ViewKnowledge: React.FC = ({ knowledgeEditFormData }) Contributors - {knowledgeEditFormData.formData.name} - {knowledgeEditFormData.formData.email} + {currentData.name} + {currentData.email} ]} @@ -77,13 +85,13 @@ const ViewKnowledge: React.FC = ({ knowledgeEditFormData }) Contribution summary - {knowledgeEditFormData.formData.submissionSummary} + {currentData.submissionSummary} , Directory path - {knowledgeEditFormData.formData.filePath} + {currentData.filePath} ]} @@ -100,7 +108,7 @@ const ViewKnowledge: React.FC = ({ knowledgeEditFormData }) Examples - {knowledgeEditFormData.formData.seedExamples?.map((seedExample, index) => ( + {currentData.seedExamples?.map((seedExample, index) => ( = ({ branchName, isDraft }) => { +const ViewKnowledgePage: React.FC = ({ branchName }) => { const router = useRouter(); const { data: session } = useSession(); const [isLoading, setIsLoading] = useState(true); const [loadingMsg, setLoadingMsg] = useState(''); - const [knowledgeEditFormData, setKnowledgeEditFormData] = useState(); + const [knowledgeData, setKnowledgeData] = useState<{ draftData?: KnowledgeFormData; editFormData?: KnowledgeEditFormData }>(); useEffect(() => { - if (isDraft) { - fetchDraftKnowledgeChanges({ branchName, setIsLoading, setLoadingMsg, setKnowledgeEditFormData }); - return; - } - - setLoadingMsg('Fetching knowledge data from branch : ' + branchName); + setLoadingMsg('Fetching knowledge data'); const fetchFormData = async () => { + const draftData = fetchDraftKnowledgeChanges(branchName); + const { editFormData, error } = await fetchKnowledgeBranchChanges(session, branchName); - if (error) { + if (error && !draftData) { setLoadingMsg(error); return; } + + // If there is only one associated knowledge file, set it for each seed example + if (editFormData?.formData.uploadedFiles.length === 1) { + editFormData.formData.seedExamples.forEach((seedExample) => (seedExample.knowledgeFile = editFormData?.formData.uploadedFiles[0])); + } + setIsLoading(false); - setKnowledgeEditFormData(editFormData); + setKnowledgeData({ draftData, editFormData }); }; + fetchFormData(); - }, [branchName, isDraft, session]); + }, [branchName, session]); const handleOnClose = () => { router.push('/dashboard'); setIsLoading(false); }; - if (isLoading || !knowledgeEditFormData?.formData) { + if (isLoading || (!knowledgeData?.editFormData && !knowledgeData?.draftData)) { return ( handleOnClose()}> @@ -57,7 +60,7 @@ const ViewKnowledgePage: React.FC = ({ branchName, isDraft }) => { ); } - return ; + return ; }; export default ViewKnowledgePage; diff --git a/src/components/Contribute/Skill/Edit/EditSkill.tsx b/src/components/Contribute/Skill/Edit/EditSkill.tsx index 94dc145a..b9e9c360 100644 --- a/src/components/Contribute/Skill/Edit/EditSkill.tsx +++ b/src/components/Contribute/Skill/Edit/EditSkill.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; -import { SkillEditFormData } from '@/types'; +import { SkillEditFormData, SkillFormData } from '@/types'; import { Modal, ModalVariant, ModalBody } from '@patternfly/react-core'; import { useSession } from 'next-auth/react'; import { fetchDraftSkillChanges } from '@/components/Contribute/Utils/autoSaveUtils'; @@ -13,34 +13,32 @@ import SkillForm from '@/components/Contribute/Skill/Edit/SkillForm'; interface EditSkillClientComponentProps { branchName: string; - isDraft: boolean; } -const EditSkill: React.FC = ({ branchName, isDraft }) => { +const EditSkill: React.FC = ({ branchName }) => { const { data: session } = useSession(); const [isLoading, setIsLoading] = useState(true); const [loadingMsg, setLoadingMsg] = useState(''); - const [skillEditFormData, setSkillEditFormData] = useState(); + const [skillData, setSkillData] = useState<{ draftData?: SkillFormData; editFormData?: SkillEditFormData }>(); const router = useRouter(); useEffect(() => { - if (isDraft) { - fetchDraftSkillChanges({ branchName, setIsLoading, setLoadingMsg, setSkillEditFormData }); - return; - } + setLoadingMsg('Fetching skill data'); const fetchBranchChanges = async () => { setLoadingMsg('Fetching skill data from branch: ' + branchName); + const draftData = fetchDraftSkillChanges(branchName); + const { editFormData, error } = await fetchSkillBranchChanges(session, branchName); - if (error) { + if (error && !draftData) { setLoadingMsg(error); return; } setIsLoading(false); - setSkillEditFormData(editFormData); + setSkillData({ draftData, editFormData }); }; fetchBranchChanges(); - }, [branchName, isDraft, session]); + }, [branchName, session]); const handleOnClose = () => { router.push('/dashboard'); @@ -57,7 +55,7 @@ const EditSkill: React.FC = ({ branchName, isDraf ); } - return ; + return ; }; export default EditSkill; diff --git a/src/components/Contribute/Skill/Edit/SkillForm.tsx b/src/components/Contribute/Skill/Edit/SkillForm.tsx index cd405213..9e428aaa 100644 --- a/src/components/Contribute/Skill/Edit/SkillForm.tsx +++ b/src/components/Contribute/Skill/Edit/SkillForm.tsx @@ -20,7 +20,7 @@ import SkillContributionSidePanelHelp from '@/components/SidePanelContents/Skill import { ActionGroupAlertContent } from '@/components/Contribute/types'; import ContributeAlertGroup from '@/components/Contribute/ContributeAlertGroup'; import ContributePageHeader from '@/components/Contribute/ContributePageHeader'; -import { deleteDraftData, doSaveDraft, isDraftDataExist, storeDraftData } from '@/components/Contribute/Utils/autoSaveUtils'; +import { deleteDraftData, formDataChanged, isDraftDataExist, storeDraftData } from '@/components/Contribute/Utils/autoSaveUtils'; import { getDefaultSkillFormData } from '@/components/Contribute/Utils/contributionUtils'; import { submitSkillData } from '@/components/Contribute/Utils/submitUtils'; import { isSkillSeedExamplesValid, isDetailsValid } from '@/components/Contribute/Utils/validationUtils'; @@ -32,15 +32,17 @@ import './skills.css'; export interface Props { skillEditFormData?: SkillEditFormData; + draftData?: SkillFormData; } -export const SkillForm: React.FunctionComponent = ({ skillEditFormData }) => { +export const SkillForm: React.FunctionComponent = ({ skillEditFormData, draftData }) => { const router = useRouter(); + const currentData: SkillFormData | undefined = draftData || skillEditFormData?.formData; const [skillFormData, setSkillFormData] = React.useState( - skillEditFormData?.formData + currentData ? { - ...skillEditFormData.formData, - seedExamples: skillEditFormData.formData.seedExamples.map((example) => ({ + ...currentData, + seedExamples: currentData.seedExamples.map((example) => ({ ...example, immutable: example.immutable !== undefined ? example.immutable : true, // Ensure immutable is set isContextValid: example.isContextValid || ValidatedOptions.default, @@ -57,23 +59,25 @@ export const SkillForm: React.FunctionComponent = ({ skillEditFormData }) } : getDefaultSkillFormData() ); + const lastUpdateRef = React.useRef(JSON.stringify(skillFormData)); const [actionGroupAlertContent, setActionGroupAlertContent] = React.useState(); const isValid = isDetailsValid(skillFormData) && isSkillSeedExamplesValid(skillFormData); React.useEffect(() => { - if (!doSaveDraft(skillFormData) && !isDraftDataExist(skillFormData.branchName)) { + if (isDraftDataExist(skillFormData.branchName) && !formDataChanged(skillFormData, skillEditFormData?.formData)) { + deleteDraftData(skillFormData.branchName); + lastUpdateRef.current = JSON.stringify(skillFormData); return; } - storeDraftData( - skillFormData.branchName, - skillFormData.filePath, - JSON.stringify(skillFormData), - !!skillEditFormData?.isSubmitted, - skillEditFormData?.oldFilesPath || '' - ); - }, [skillEditFormData?.isSubmitted, skillEditFormData?.oldFilesPath, skillFormData]); + if (formDataChanged(skillFormData, JSON.parse(lastUpdateRef.current))) { + const draftContributionStr = JSON.stringify(skillFormData); + lastUpdateRef.current = draftContributionStr; + + storeDraftData(skillFormData.branchName, skillFormData.filePath, draftContributionStr, skillEditFormData?.oldFilesPath || ''); + } + }, [skillEditFormData?.formData, skillEditFormData?.isSubmitted, skillEditFormData?.oldFilesPath, skillFormData]); const updateActionGroupAlertContent = (newContent: ActionGroupAlertContent | undefined) => { // In order to restart the timer, we must re-create the Alert not re-use it. Clear it for one round then set the new info @@ -109,6 +113,7 @@ export const SkillForm: React.FunctionComponent = ({ skillEditFormData }) } @@ -57,8 +63,8 @@ const ViewSkill: React.FC = ({ skillEditFormData }) => { Contributors - {skillEditFormData.formData.name} - {skillEditFormData.formData.email} + {currentData.name} + {currentData.email} ]} @@ -73,13 +79,13 @@ const ViewSkill: React.FC = ({ skillEditFormData }) => { Contribution summary - {skillEditFormData.formData.submissionSummary} + {currentData.submissionSummary} , Directory path - {skillEditFormData.formData.filePath} + {currentData.filePath} ]} @@ -95,7 +101,7 @@ const ViewSkill: React.FC = ({ skillEditFormData }) => { Examples - + ]} diff --git a/src/components/Contribute/Skill/View/ViewSkillPage.tsx b/src/components/Contribute/Skill/View/ViewSkillPage.tsx index fa694862..0728b81d 100644 --- a/src/components/Contribute/Skill/View/ViewSkillPage.tsx +++ b/src/components/Contribute/Skill/View/ViewSkillPage.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { useSession } from 'next-auth/react'; -import { SkillEditFormData } from '@/types'; +import { SkillEditFormData, SkillFormData } from '@/types'; import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Modal, ModalVariant, ModalBody } from '@patternfly/react-core'; @@ -13,41 +13,37 @@ import ViewSkill from '@/components/Contribute/Skill/View/ViewSkill'; interface ViewKnowledgeClientComponentProps { branchName: string; - isDraft: boolean; } -const ViewSkillPage: React.FC = ({ branchName, isDraft }) => { +const ViewSkillPage: React.FC = ({ branchName }) => { const router = useRouter(); const { data: session } = useSession(); const [isLoading, setIsLoading] = useState(true); const [loadingMsg, setLoadingMsg] = useState(''); - const [skillEditFormData, setSkillEditFormData] = useState(); + const [skillData, setSkillData] = useState<{ draftData?: SkillFormData; editFormData?: SkillEditFormData }>(); useEffect(() => { - if (isDraft) { - fetchDraftSkillChanges({ branchName, setIsLoading, setLoadingMsg, setSkillEditFormData }); - return; - } - - setLoadingMsg('Fetching knowledge data from branch : ' + branchName); + setLoadingMsg('Fetching skill data'); const fetchFormData = async () => { + const draftData = fetchDraftSkillChanges(branchName); + const { editFormData, error } = await fetchSkillBranchChanges(session, branchName); - if (error) { + if (error && !draftData) { setLoadingMsg(error); return; } setIsLoading(false); - setSkillEditFormData(editFormData); + setSkillData({ draftData, editFormData }); }; fetchFormData(); - }, [branchName, isDraft, session]); + }, [branchName, session]); const handleOnClose = () => { router.push('/dashboard'); setIsLoading(false); }; - if (isLoading || !skillEditFormData?.formData) { + if (isLoading || (!skillData?.editFormData && !skillData?.draftData)) { return ( handleOnClose()}> @@ -57,7 +53,7 @@ const ViewSkillPage: React.FC = ({ branchName ); } - return ; + return ; }; export default ViewSkillPage; diff --git a/src/components/Contribute/Utils/autoSaveUtils.ts b/src/components/Contribute/Utils/autoSaveUtils.ts index 75827734..a2fcec18 100644 --- a/src/components/Contribute/Utils/autoSaveUtils.ts +++ b/src/components/Contribute/Utils/autoSaveUtils.ts @@ -1,5 +1,4 @@ -import { ContributionFormData, DraftEditFormInfo, KnowledgeEditFormData, KnowledgeFormData, SkillEditFormData, SkillFormData } from '@/types'; -import { KnowledgeSchemaVersion, SkillSchemaVersion } from '@/types/const'; +import { ContributionFormData, DraftEditFormInfo, KnowledgeFormData, SkillFormData } from '@/types'; import { devLog } from '@/utils/devlog'; export const TOOLTIP_FOR_DISABLE_COMPONENT = 'This action can be performed once the draft changes are either submitted or discarded.'; @@ -18,9 +17,9 @@ export const clearAllDraftData = () => { } }; -export const storeDraftData = (branchName: string, taxonomy: string, data: string, isSubmitted: boolean, oldFilesPath: string) => { +export const storeDraftData = (branchName: string, taxonomy: string, data: string, oldFilesPath: string) => { localStorage.setItem(branchName, data); - addToDraftList(branchName, isSubmitted, oldFilesPath, taxonomy); + addToDraftList(branchName, oldFilesPath, taxonomy); }; export const isDraftDataExist = (branchName: string): boolean => { @@ -41,14 +40,13 @@ const getDraftInfo = (branchName: string): DraftEditFormInfo | undefined => { return undefined; }; -const addToDraftList = (branchName: string, isSubmitted: boolean, oldFilesPath: string, taxonomy: string) => { +const addToDraftList = (branchName: string, oldFilesPath: string, taxonomy: string) => { const existingDrafts = localStorage.getItem('draftContributions'); let draftContributions: DraftEditFormInfo[] = []; const draft: DraftEditFormInfo = { branchName, lastUpdated: new Date(Date.now()).toUTCString(), isKnowledgeDraft: branchName.includes('knowledge-contribution'), - isSubmitted, oldFilesPath, taxonomy }; @@ -121,30 +119,31 @@ const optionalKeys = [ ]; /* eslint-disable @typescript-eslint/no-explicit-any */ -export const doSaveDraft = (knowledgeFormData: any): boolean => { +export const formDataChanged = (knowledgeFormData: any, prevFormData: any): boolean => { + if (!prevFormData) { + return true; + } for (const [key, value] of Object.entries(knowledgeFormData)) { if (value) { - if (optionalKeys.includes(key)) { - continue; - } else { - if (Array.isArray(value) && value.length == 0) { - return false; - } else if (Array.isArray(value)) { + if (!optionalKeys.includes(key)) { + if (Array.isArray(value)) { + if (value.length == 0) { + return false; + } for (let i = 0; i < value.length; i++) { - if (doSaveDraft(value[i])) { + if (formDataChanged(value[i], prevFormData[key][i])) { return true; - } else { - continue; } } - } else if (value !== null && typeof value === 'object') { - if (doSaveDraft(value)) { + } else if (typeof value === 'object') { + if (formDataChanged(value, prevFormData[key])) { return true; - } else { - continue; } } else { - return true; + console.log(`${key} changed: `, value !== prevFormData[key]); + if (value !== prevFormData[key]) { + return true; + } } } } @@ -152,71 +151,49 @@ export const doSaveDraft = (knowledgeFormData: any): boolean => { return false; }; -interface DraftChangePros { - branchName: string; - setIsLoading: (loading: boolean) => void; - setLoadingMsg: (msg: string) => void; - setKnowledgeEditFormData?: (data: KnowledgeEditFormData) => void; - setSkillEditFormData?: (data: SkillEditFormData) => void; -} -export function fetchDraftKnowledgeChanges({ branchName, setIsLoading, setLoadingMsg, setKnowledgeEditFormData }: DraftChangePros) { +export const fetchDraftKnowledgeChanges = (branchName: string): KnowledgeFormData | undefined => { devLog('Fetching draft data from the local storage for knowledge contribution:', branchName); - setLoadingMsg(`Fetching draft knowledge data for ${branchName}`); const draftInfo = getDraftInfo(branchName); + if (!draftInfo) { + return; + } + const contributionData = localStorage.getItem(branchName); - if (contributionData != null) { - const knowledgeExistingFormData: KnowledgeFormData = JSON.parse(contributionData, (key, value) => { - if (key === 'filesToUpload' && Array.isArray(value)) { - return value.map((meta: File) => { - return new File([''], meta.name, { - type: 'text/markdown', - lastModified: Date.now() - }); - }); - } - return value; - }); - devLog('Draft data retrieved from local storage :', knowledgeExistingFormData); - const knowledgeEditFormData: KnowledgeEditFormData = { - isEditForm: true, - isSubmitted: !!draftInfo?.isSubmitted, - isDraft: true, - version: KnowledgeSchemaVersion, - formData: knowledgeExistingFormData, - pullRequestNumber: 0, - oldFilesPath: draftInfo?.oldFilesPath ? draftInfo.oldFilesPath : '' - }; - if (setKnowledgeEditFormData) { - setKnowledgeEditFormData(knowledgeEditFormData); - } - setIsLoading(false); - } else { + if (!contributionData) { console.warn('Contribution draft data is not present in the local storage.'); + return; } -} -export function fetchDraftSkillChanges({ branchName, setIsLoading, setLoadingMsg, setSkillEditFormData }: DraftChangePros) { - devLog('Fetching draft data from the local storage for skill contribution:', branchName); - setLoadingMsg(`Fetching draft skill data for ${branchName}`); + const knowledgeExistingFormData: KnowledgeFormData = JSON.parse(contributionData, (key, value) => { + if (key === 'filesToUpload' && Array.isArray(value)) { + return value.map((meta: File) => { + return new File([''], meta.name, { + type: 'text/markdown', + lastModified: Date.now() + }); + }); + } + return value; + }); + devLog('Draft data retrieved from local storage :', knowledgeExistingFormData); + + return knowledgeExistingFormData; +}; + +export const fetchDraftSkillChanges = (branchName: string): SkillFormData | undefined => { const draftInfo = getDraftInfo(branchName); + if (!draftInfo) { + return; + } + const contributionData = localStorage.getItem(branchName); - if (contributionData != null) { - const skillExistingFormData: SkillFormData = JSON.parse(contributionData); - devLog('Draft skill data retrieved from local storage :', skillExistingFormData); - const skillEditFormData: SkillEditFormData = { - isEditForm: true, - isSubmitted: draftInfo ? draftInfo.isSubmitted : false, - isDraft: true, - version: SkillSchemaVersion, - pullRequestNumber: 0, - formData: skillExistingFormData, - oldFilesPath: draftInfo?.oldFilesPath ? draftInfo.oldFilesPath : '' - }; - if (setSkillEditFormData) { - setSkillEditFormData(skillEditFormData); - } - setIsLoading(false); - } else { + if (!contributionData) { console.warn('Contribution draft data is not present in the local storage.'); + return; } -} + + const skillExistingFormData: SkillFormData = JSON.parse(contributionData); + devLog('Draft skill data retrieved from local storage :', skillExistingFormData); + + return skillExistingFormData; +}; diff --git a/src/components/Dashboard/ContributionActions.tsx b/src/components/Dashboard/ContributionActions.tsx index ff55601d..5fa77f76 100644 --- a/src/components/Dashboard/ContributionActions.tsx +++ b/src/components/Dashboard/ContributionActions.tsx @@ -50,9 +50,7 @@ const ContributionActions: React.FC = ({ contribution, onUpdateContributi const deleteRef = React.useRef(null); const handleEditContribution = () => { - router.push( - `/contribute/${contribution.isKnowledge ? 'knowledge' : 'skill'}/edit/${contribution.branchName}${contribution.isDraft ? '/isDraft' : ''}` - ); + router.push(`/contribute/${contribution.isKnowledge ? 'knowledge' : 'skill'}/edit/${contribution.branchName}`); }; const deleteContribution = async (branchName: string) => { diff --git a/src/components/Dashboard/ContributionCard.tsx b/src/components/Dashboard/ContributionCard.tsx index baf4f19c..5a9eddf1 100644 --- a/src/components/Dashboard/ContributionCard.tsx +++ b/src/components/Dashboard/ContributionCard.tsx @@ -55,11 +55,7 @@ const ContributionCard: React.FC = ({ contribution, onUpdateContributions component="a" variant="link" isInline - onClick={() => - router.push( - `/contribute/${contribution.isKnowledge ? 'knowledge' : 'skill'}/${contribution.branchName}${contribution.isDraft ? '/isDraft' : ''}` - ) - } + onClick={() => router.push(`/contribute/${contribution.isKnowledge ? 'knowledge' : 'skill'}/${contribution.branchName}`)} > diff --git a/src/components/Dashboard/ContributionTableRow.tsx b/src/components/Dashboard/ContributionTableRow.tsx index 3dfd10d8..f3c7999e 100644 --- a/src/components/Dashboard/ContributionTableRow.tsx +++ b/src/components/Dashboard/ContributionTableRow.tsx @@ -28,11 +28,7 @@ const ContributionTableRow: React.FC = ({ contribution, onUpdateContribut - router.push( - `/contribute/${contribution.isKnowledge ? 'knowledge' : 'skill'}/${contribution.branchName}${contribution.isDraft ? '/isDraft' : ''}` - ) - } + onClick={() => router.push(`/contribute/${contribution.isKnowledge ? 'knowledge' : 'skill'}/${contribution.branchName}`)} > diff --git a/src/components/Dashboard/DashboardPage.tsx b/src/components/Dashboard/DashboardPage.tsx index c5fe0a8d..4bfc7c9b 100644 --- a/src/components/Dashboard/DashboardPage.tsx +++ b/src/components/Dashboard/DashboardPage.tsx @@ -144,7 +144,7 @@ const DashboardPage: React.FunctionComponent = () => { lastUpdated: new Date(draft.lastUpdated), isDraft: true, isKnowledge: draft.isKnowledgeDraft, - isSubmitted: draft.isSubmitted, + isSubmitted: false, state: 'draft', taxonomy: draft.taxonomy })), diff --git a/src/types/index.ts b/src/types/index.ts index 7b4904ef..797bbf4f 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -14,7 +14,6 @@ export interface DraftEditFormInfo { author?: string; lastUpdated: string; isKnowledgeDraft: boolean; - isSubmitted: boolean; oldFilesPath: string; taxonomy: string; }