diff --git a/src/components/ProjectDeployments/ProjectDeployments.tsx b/src/components/ProjectDeployments/ProjectDeployments.tsx index de25fb6f6..bf5a601a5 100644 --- a/src/components/ProjectDeployments/ProjectDeployments.tsx +++ b/src/components/ProjectDeployments/ProjectDeployments.tsx @@ -3,7 +3,10 @@ import * as React from 'react'; import { useTranslation } from 'react-i18next'; -import { Markdown } from '@subql/components'; +import { useCreateDeployment } from '@hooks'; +import { Markdown, Typography } from '@subql/components'; +import { Form, Modal, Radio } from 'antd'; +import { useForm } from 'antd/es/form/Form'; import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; @@ -19,29 +22,90 @@ type Deployment = NewDeployment & { createdAt?: Date }; type Props = { deployments: Deployment[]; projectId: string; + onRefresh: () => Promise; }; -const ProjectDeployments: React.FC = ({ deployments, projectId }) => { +const ProjectDeployments: React.FC = ({ deployments, projectId, onRefresh }) => { const { t } = useTranslation(); + const updateDeployment = useCreateDeployment(projectId); + const [deploymentModal, setDeploymentModal] = React.useState(false); + const [form] = useForm(); + const [currentDeployment, setCurrentDeployment] = React.useState(); + const [addDeploymentsLoading, setAddDeploymentsLoading] = React.useState(false); + + const handleSubmitUpdate = async () => { + try { + setAddDeploymentsLoading(true); + await form.validateFields(); + await updateDeployment({ + ...currentDeployment, + ...form.getFieldsValue(), + }); + await onRefresh(); + form.resetFields(); + setDeploymentModal(false); + } finally { + setAddDeploymentsLoading(false); + } + }; return ( <> + setDeploymentModal(false)} + title="Edit Deployment" + width={572} + cancelButtonProps={{ + style: { + display: 'none', + }, + }} + okText="Update" + okButtonProps={{ + shape: 'round', + size: 'large', + loading: addDeploymentsLoading, + }} + onOk={() => { + handleSubmitUpdate(); + }} + > +
+
+ + { + form.setFieldValue('description', e); + }} + > + +
+
+
{t('deployments.header1')} + RECOMMENDED {t('deployments.header2')} {t('deployments.header3')} {t('deployments.header4')} - {/* {t('general.action')} */} + {t('general.action')} {deployments.map((deployment, index) => ( - +

{deployment.version}

+ +

+ RECOMMENDED +

+

{deployment.deploymentId}

@@ -58,6 +122,18 @@ const ProjectDeployments: React.FC = ({ deployments, projectId }) => { {deployment.createdAt ? dayjs(deployment.createdAt).utc(true).fromNow() : 'N/A'}

+ + { + form.setFieldValue('description', deployment.description || ''); + setCurrentDeployment(deployment); + setDeploymentModal(true); + }} + > + Edit + + ))} diff --git a/src/containers/ProjectRegistry.ts b/src/containers/ProjectRegistry.ts index 2905475ec..0fccdaf86 100644 --- a/src/containers/ProjectRegistry.ts +++ b/src/containers/ProjectRegistry.ts @@ -80,6 +80,7 @@ function useProjectRegistryImpl(logger: Logger) { id: BigNumberish, deploymentId: string, version: string, + recommended?: boolean, ): Promise => { if (!contracts) { throw new Error('ProjectRegistry contract not available'); @@ -90,7 +91,7 @@ function useProjectRegistryImpl(logger: Logger) { id, cidToBytes32(deploymentId), cidToBytes32(version), - true, + !!recommended, ); tx.wait().then((receipt) => { diff --git a/src/hooks/useCreateDeployment.tsx b/src/hooks/useCreateDeployment.tsx index 8c4949861..2bc44c1f1 100644 --- a/src/hooks/useCreateDeployment.tsx +++ b/src/hooks/useCreateDeployment.tsx @@ -6,11 +6,13 @@ import { ContractReceipt } from '@ethersproject/contracts'; import { useProjectMetadata, useProjectRegistry } from '../containers'; import { NewDeployment } from '../models'; -export function useCreateDeployment(projectId: string): (deploymentDetails: NewDeployment) => Promise { +export function useCreateDeployment( + projectId: string, +): (deploymentDetails: NewDeployment & { recommended: boolean }) => Promise { const projectRegistry = useProjectRegistry(); const { uploadVersionMetadata } = useProjectMetadata(); - const createDeployment = async (deploymentDetails: NewDeployment) => { + const createDeployment = async (deploymentDetails: NewDeployment & { recommended: boolean }) => { const versionCid = await uploadVersionMetadata({ version: deploymentDetails.version, description: deploymentDetails.description, @@ -18,7 +20,12 @@ export function useCreateDeployment(projectId: string): (deploymentDetails: NewD console.log('Uploaded version details', versionCid); - const tx = await projectRegistry.updateDeployment(projectId, deploymentDetails.deploymentId, versionCid); + const tx = await projectRegistry.updateDeployment( + projectId, + deploymentDetails.deploymentId, + versionCid, + deploymentDetails.recommended, + ); return await tx.wait(1); }; diff --git a/src/pages/studio/Project/Deployments.tsx b/src/pages/studio/Project/Deployments.tsx index e44e85143..3c2b648ee 100644 --- a/src/pages/studio/Project/Deployments.tsx +++ b/src/pages/studio/Project/Deployments.tsx @@ -1,7 +1,8 @@ // Copyright 2020-2022 SubQuery Pte Ltd authors & contributors // SPDX-License-Identifier: Apache-2.0 -import { useGetProjectDeploymentsQuery } from '@subql/react-hooks'; +import { forwardRef, useImperativeHandle } from 'react'; +import { useGetProjectDeploymentsLazyQuery } from '@subql/react-hooks'; import { uniqBy } from 'ramda'; import { ProjectDeployments, Spinner } from '../../../components'; @@ -18,8 +19,12 @@ type Props = { }; }; -const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { - const query = useGetProjectDeploymentsQuery({ +export interface DeploymendRef { + refresh: () => void; +} + +const DeploymentsTab = forwardRef(({ projectId, currentDeployment }, ref) => { + const [getProjDeployments] = useGetProjectDeploymentsLazyQuery({ variables: { projectId, }, @@ -27,7 +32,10 @@ const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { const { catSingle } = useIPFS(); const asyncDeployments = useAsyncMemo(async () => { - let projectDeployments = query.data?.project?.deployments.nodes.filter(notEmpty); + const res = await getProjDeployments({ + fetchPolicy: 'network-only', + }); + let projectDeployments = res.data?.project?.deployments.nodes.filter(notEmpty); if (!projectDeployments) { return []; } @@ -61,10 +69,18 @@ const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { createdAt: deployment.createdTimestamp, version: result.version, description: result.description, + // TODO: backend support + recommended: true, }; }), ); - }, [query, currentDeployment]); + }, [currentDeployment]); + + useImperativeHandle(ref, () => ({ + refresh: () => { + return asyncDeployments.refetch(); + }, + })); return renderAsync(asyncDeployments, { loading: () => , @@ -75,9 +91,17 @@ const DeploymentsTab: React.FC = ({ projectId, currentDeployment }) => { return
There has no deployments for this project
; } - return ; + return ( + { + await asyncDeployments.refetch(); + }} + /> + ); }, }); -}; +}); export default DeploymentsTab; diff --git a/src/pages/studio/Project/Project.tsx b/src/pages/studio/Project/Project.tsx index 2b9482d76..26f5cad06 100644 --- a/src/pages/studio/Project/Project.tsx +++ b/src/pages/studio/Project/Project.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { useNavigate, useParams } from 'react-router'; import { ExternalLink } from '@components/ProjectOverview/ProjectOverview'; import { Markdown, Typography } from '@subql/components'; -import { Breadcrumb, Button, Form, Input, Modal } from 'antd'; +import { Breadcrumb, Button, Checkbox, Form, Input, Modal } from 'antd'; import { useForm } from 'antd/es/form/Form'; import clsx from 'clsx'; @@ -13,7 +13,7 @@ import { IPFSImage, Spinner } from '../../../components'; import { useWeb3 } from '../../../containers'; import { useCreateDeployment, useProject } from '../../../hooks'; import { parseError, renderAsync } from '../../../utils'; -import DeploymentsTab from './Deployments'; +import DeploymentsTab, { DeploymendRef } from './Deployments'; import styles from './Project.module.css'; const Project: React.FC = () => { @@ -24,12 +24,20 @@ const Project: React.FC = () => { const [form] = useForm(); const [deploymentModal, setDeploymentModal] = React.useState(false); const createDeployment = useCreateDeployment(id ?? ''); + const deploymentsRef = React.useRef(null); + const [addDeploymentsLoading, setAddDeploymentsLoading] = React.useState(false); const handleSubmitCreate = async () => { - await form.validateFields(); - await createDeployment(form.getFieldsValue()); - form.resetFields(); - setDeploymentModal(false); + try { + setAddDeploymentsLoading(true); + await form.validateFields(); + await createDeployment(form.getFieldsValue()); + await deploymentsRef.current?.refresh(); + form.resetFields(); + setDeploymentModal(false); + } finally { + setAddDeploymentsLoading(false); + } }; return renderAsync(asyncProject, { @@ -63,6 +71,7 @@ const Project: React.FC = () => { okButtonProps={{ shape: 'round', size: 'large', + loading: addDeploymentsLoading, }} onOk={() => { handleSubmitCreate(); @@ -76,11 +85,14 @@ const Project: React.FC = () => { - + + Set as recommended version + + { - form.setFieldValue('deploymentDesc', e); + form.setFieldValue('description', e); }} > @@ -152,6 +164,7 @@ const Project: React.FC = () => { {project.metadata.categories?.map((category) => { return (
{