From c58cdd866f4c27f539615ad0f607cc6a91bf9997 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9rard=20Dethier?= Date: Tue, 24 Oct 2023 13:43:21 +0200 Subject: [PATCH] fix: concurrent publish. logion-network/logion-internal#1037 --- src/ClientExtrinsicSubmitter.tsx | 90 +- src/components/blocknone/BlockNone.tsx | 17 + src/loc/CustomItemActions.tsx | 5 +- src/loc/LocPublishLinkButton.tsx | 14 +- src/loc/LocPublishPrivateFileButton.tsx | 6 +- src/loc/LocPublishPublicDataButton.tsx | 14 +- src/loc/RequestVoteButton.test.tsx | 2 +- src/loc/TemplateItemActions.tsx | 5 +- .../CustomItemActions.test.tsx.snap | 7182 +++++++++++++--- src/loc/__snapshots__/LocItems.test.tsx.snap | 106 + .../TemplateItemActions.test.tsx.snap | 7488 ++++++++++++++--- .../CreateProtectionRequestForm.test.tsx | 2 + 12 files changed, 12291 insertions(+), 2640 deletions(-) create mode 100644 src/components/blocknone/BlockNone.tsx diff --git a/src/ClientExtrinsicSubmitter.tsx b/src/ClientExtrinsicSubmitter.tsx index 09b807b1..d7ea43b5 100644 --- a/src/ClientExtrinsicSubmitter.tsx +++ b/src/ClientExtrinsicSubmitter.tsx @@ -1,6 +1,5 @@ import { ISubmittableResult } from '@logion/client'; import { useEffect, useState } from 'react'; -import { flushSync } from 'react-dom'; import ExtrinsicSubmissionResult, { isSuccessful } from './ExtrinsicSubmissionResult'; @@ -16,53 +15,78 @@ export interface Props { slim?: boolean, } +interface State { + result: ISubmittableResult | null; + error: any; + submitted: boolean; + notified: boolean; + callEnded: boolean; + call?: Call; + setState: (newState: State) => void; +} + +const INITIAL_STATE: State = { + result: null, + error: null, + submitted: false, + notified: false, + callEnded: false, + setState: () => {}, +}; + +let persistentState: State = INITIAL_STATE; + +function setPersistentState(stateUpdate: Partial) { + persistentState = { + ...persistentState, + ...stateUpdate + }; + persistentState.setState(persistentState); +} + +export function resetPersistenState() { + persistentState = INITIAL_STATE; +} + export default function ClientExtrinsicSubmitter(props: Props) { - const [ result, setResult ] = useState(null); - const [ error, setError ] = useState(null); - const [ submitted, setSubmitted ] = useState(false); - const [ notified, setNotified ] = useState(false); - const [ callEnded, setCallEnded ] = useState(false); + const [ state, setState ] = useState(persistentState); + if(setState !== persistentState.setState) { + persistentState.setState = setState; + } useEffect(() => { - if(!submitted && props.call !== undefined) { - flushSync(() => setSubmitted(true)); + if(props.call !== undefined && (!state.submitted || (state.notified && props.call !== state.call))) { + setPersistentState({ + ...INITIAL_STATE, + call: props.call, + submitted: true, + setState, + }); (async function() { - setResult(null); - setError(null); - setCallEnded(false); try { - await props.call!((callbackResult: ISubmittableResult) => setResult(callbackResult)); - setCallEnded(true); + await props.call!((callbackResult: ISubmittableResult) => setPersistentState({ result: callbackResult })); + setPersistentState({ callEnded: true }); } catch(e) { console.log(e); - setError(e); + setPersistentState({ callEnded: true, error: e}); } })(); } - }, [ setResult, setError, props, submitted ]); + }, [ state, props ]); useEffect(() => { - if (result !== null && isSuccessful(result) && !notified && props.onSuccess && callEnded) { - setNotified(true); + if (state.result !== null && isSuccessful(state.result) && !state.notified && props.onSuccess && state.callEnded) { + setPersistentState({ notified: true }); props.onSuccess(); } - }, [ result, notified, setNotified, props, callEnded ]); - - useEffect(() => { - if (error !== null && !notified && props.onError) { - setNotified(true); - props.onError!(); - } - }, [ notified, setNotified, props, error ]); + }, [ state, props ]); useEffect(() => { - if(submitted && props.call === undefined) { - setSubmitted(false); - setNotified(false); - setResult(null); - setError(null); + if (state.error !== null && !state.notified && props.onError) { + setPersistentState({ notified: true }); + props.onError(); } - }, [ setResult, setError, props, submitted ]); + }, [ state, props ]); if(props.call === undefined) { return null; @@ -70,8 +94,8 @@ export default function ClientExtrinsicSubmitter(props: Props) { return ( diff --git a/src/components/blocknone/BlockNone.tsx b/src/components/blocknone/BlockNone.tsx new file mode 100644 index 00000000..8e281f1c --- /dev/null +++ b/src/components/blocknone/BlockNone.tsx @@ -0,0 +1,17 @@ +import { ReactNode } from "react"; + +export interface Props { + show: boolean; + children: ReactNode; +} + +export default function BlockNone(props: Props) { + + return ( +
+ { props.children } +
+ ); +} diff --git a/src/loc/CustomItemActions.tsx b/src/loc/CustomItemActions.tsx index 125ca71a..74f4d9d9 100644 --- a/src/loc/CustomItemActions.tsx +++ b/src/loc/CustomItemActions.tsx @@ -12,6 +12,7 @@ import { ContributionMode } from "./types"; import { useLogionChain } from "src/logion-chain"; import DeleteButton from "./DeleteButton"; import PublishItemButton from "./PublishItemButton"; +import BlockNone from "src/components/blocknone/BlockNone"; export interface Props { locItem: LocItem; @@ -35,9 +36,9 @@ export default function CustomItemActions(props: Props) { { canRequestReviewMemo && } { canReviewMemo && } - { canPublishMemo && } + { canDeleteMemo && } - { canAcknowledgeMemo && } + { locItem.status === "PUBLISHED" && !canAcknowledgeMemo && { + const estimateFees = useCallback(async (target: UUID | undefined) => { if (target && locState instanceof OpenLoc) { return locState.estimateFeesPublishLink({ target }); } else { - throw Error("Invalid type"); + return new Fees({ inclusionFee: 0n }); } - }, [ locState ]) - - if(!client) { - return null; - } + }, [ locState ]); return ( { + const estimateFees = useCallback(async (hash: Hash | undefined) => { if (hash && locState instanceof OpenLoc) { return locState.estimateFeesPublishFile({ hash }); } else { - throw Error("Invalid type"); + return new Fees({ inclusionFee: 0n }); } }, [ locState ]) diff --git a/src/loc/LocPublishPublicDataButton.tsx b/src/loc/LocPublishPublicDataButton.tsx index 44534601..e7cb5420 100644 --- a/src/loc/LocPublishPublicDataButton.tsx +++ b/src/loc/LocPublishPublicDataButton.tsx @@ -1,5 +1,5 @@ import { OpenLoc } from "@logion/client"; -import { UUID, Hash } from "@logion/node-api"; +import { UUID, Hash, Fees } from "@logion/node-api"; import { MetadataItem } from "./LocItem"; import LocPublishButton from "./LocPublishButton"; @@ -13,19 +13,15 @@ export interface Props { } export default function LocPublishPublicDataButton(props: Props) { - const { signer, client } = useLogionChain(); + const { signer } = useLogionChain(); const { locState } = useLocContext(); - const estimateFees = useCallback((nameHash: Hash | undefined) => { + const estimateFees = useCallback(async (nameHash: Hash | undefined) => { if (nameHash && locState instanceof OpenLoc) { return locState.estimateFeesPublishMetadata({ nameHash }); } else { - throw Error("Invalid type"); + return new Fees({ inclusionFee: 0n }); } - }, [ locState ]) - - if(!client) { - return null; - } + }, [ locState ]); return ( { const locState = new ClosedLoc(); setLocState(locState); locState.legalOfficer.requestVote = async (params: any) => { - params.callback(mockSubmittableResult(true, "ExtrinsicFailed", true)); + params.callback(mockSubmittableResult(false, "ExtrinsicFailed", true)); throw new Error(); }; render(); diff --git a/src/loc/TemplateItemActions.tsx b/src/loc/TemplateItemActions.tsx index b02ea548..922fcaa1 100644 --- a/src/loc/TemplateItemActions.tsx +++ b/src/loc/TemplateItemActions.tsx @@ -13,6 +13,7 @@ import ButtonGroup from "src/common/ButtonGroup"; import ClearItemButton from "./ClearItemButton"; import PublishItemButton from "./PublishItemButton"; import SetItemButton from "./SetItemButton"; +import BlockNone from "src/components/blocknone/BlockNone"; export interface Props { item: LocItem; @@ -40,9 +41,9 @@ export default function TemplateItemActions(props: Props) { { !item.isSet && canAddMemo && } { item.isSet && canRequestReviewMemo && } { item.isSet && canReviewMemo && } - { item.isSet && canPublishMemo && } + { item.isSet && canDeleteMemo && } - { item.isSet && canAcknowledgeMemo && } + { item.isSet && item.status === "PUBLISHED" && !canAcknowledgeMemo && + + + + + + + + + + + + + + + + + + - + + /> + + + + `; exports[`CustomItemActions Links can be acknowledged by owner when published 1`] = ` - + + + + + + +`; + +exports[`CustomItemActions Links can be published or deleted by requester when approved 1`] = ` + + + + + + + + `; -exports[`CustomItemActions Links can be published or deleted by requester when approved 1`] = ` +exports[`CustomItemActions Links can be published or deleted by requester when approved and submitted by issuer 1`] = ` - + + + - + + + +`; + +exports[`CustomItemActions Links can be reviewed by owner when submitted 1`] = ` + + + + + + + + `; -exports[`CustomItemActions Links can be published or deleted by requester when approved and submitted by issuer 1`] = ` +exports[`CustomItemActions Links can be submitted for review or deleted by issuer when draft 1`] = ` - + Request review + + + + + + + + + +`; + +exports[`CustomItemActions Links can be submitted for review or deleted by requester when draft 1`] = ` + + + + + + + + `; -exports[`CustomItemActions Links can be reviewed by owner when submitted 1`] = ` +exports[`CustomItemActions Private files are read-only for issuer when acknowledged 1`] = ` - + - -`; - -exports[`CustomItemActions Links can be submitted for review or deleted by issuer when draft 1`] = ` - - - + + `; -exports[`CustomItemActions Links can be submitted for review or deleted by requester when draft 1`] = ` +exports[`CustomItemActions Private files are read-only for owner when acknowledged 1`] = ` - - - -`; - -exports[`CustomItemActions Private files are read-only for issuer when acknowledged 1`] = ` - - + + + - -`; - -exports[`CustomItemActions Private files are read-only for owner when acknowledged 1`] = ` - + /> + - - -`; - -exports[`CustomItemActions Private files can be acknowledged by issuer when published 1`] = ` - - + + + + + + + +`; + +exports[`CustomItemActions Private files can be acknowledged by issuer when published 1`] = ` + + + + + + + + +`; + +exports[`CustomItemActions Private files can be acknowledged by owner when published 1`] = ` + + + + + + + + +`; + +exports[`CustomItemActions Private files can be published or deleted by requester when approved 1`] = ` + + + + + + + + `; -exports[`CustomItemActions Private files can be acknowledged by owner when published 1`] = ` +exports[`CustomItemActions Private files can be published or deleted by requester when approved and submitted by issuer 1`] = ` - + + + + + + `; -exports[`CustomItemActions Private files can be published or deleted by requester when approved 1`] = ` +exports[`CustomItemActions Private files can be reviewed by owner when submitted 1`] = ` - - + + + + + /> + `; -exports[`CustomItemActions Private files can be published or deleted by requester when approved and submitted by issuer 1`] = ` +exports[`CustomItemActions Private files can be submitted for review or deleted by issuer when draft 1`] = ` - + Request review + + + + /> + - -`; - -exports[`CustomItemActions Private files can be reviewed by owner when submitted 1`] = ` - - - -`; - -exports[`CustomItemActions Private files can be submitted for review or deleted by issuer when draft 1`] = ` - - - - -`; - -exports[`CustomItemActions Private files can be submitted for review or deleted by requester when draft 1`] = ` - - - - -`; - -exports[`CustomItemActions Public data are read-only for issuer when acknowledged 1`] = ` - - - -`; - -exports[`CustomItemActions Public data are read-only for owner when acknowledged 1`] = ` - - - -`; - -exports[`CustomItemActions Public data are read-only for requester when published 1`] = ` - - - -`; - -exports[`CustomItemActions Public data can be acknowledged by issuer when published 1`] = ` - - - -`; - -exports[`CustomItemActions Public data can be acknowledged by owner when published 1`] = ` - - - -`; - -exports[`CustomItemActions Public data can be published or deleted by requester when approved 1`] = ` - - + + + +`; + +exports[`CustomItemActions Private files can be submitted for review or deleted by requester when draft 1`] = ` + + + + + /> + + + + `; -exports[`CustomItemActions Public data can be published or deleted by requester when approved and submitted by issuer 1`] = ` +exports[`CustomItemActions Public data are read-only for issuer when acknowledged 1`] = ` + + + + + + + + + +`; + +exports[`CustomItemActions Public data are read-only for owner when acknowledged 1`] = ` + + + + + + + + + +`; + +exports[`CustomItemActions Public data are read-only for requester when published 1`] = ` + + + + + + + + + +`; + +exports[`CustomItemActions Public data can be acknowledged by issuer when published 1`] = ` + + + + + + + + +`; + +exports[`CustomItemActions Public data can be acknowledged by owner when published 1`] = ` + + + + + + + + +`; + +exports[`CustomItemActions Public data can be published or deleted by requester when approved 1`] = ` - + + + + + + + +`; + +exports[`CustomItemActions Public data can be published or deleted by requester when approved and submitted by issuer 1`] = ` + + + + + + + `; @@ -2492,6 +6466,220 @@ exports[`CustomItemActions Public data can be reviewed by owner when submitted 1 } } /> + + + + + + `; @@ -2502,6 +6690,113 @@ exports[`CustomItemActions Public data can be submitted for review or deleted by > Request review + + + + + + `; @@ -2615,6 +7017,113 @@ exports[`CustomItemActions Public data can be submitted for review or deleted by > Request review + + + + + + `; diff --git a/src/loc/__snapshots__/LocItems.test.tsx.snap b/src/loc/__snapshots__/LocItems.test.tsx.snap index 9827b173..9945f9fa 100644 --- a/src/loc/__snapshots__/LocItems.test.tsx.snap +++ b/src/loc/__snapshots__/LocItems.test.tsx.snap @@ -278,6 +278,33 @@ exports[`LOLocItems renders with single draft item 1`] = ` > Request review +
+ +
+
+ +
@@ -690,6 +743,33 @@ exports[`UserLocItems renders with single draft item 1`] = ` > Request review +
+ +
+
+ +
diff --git a/src/loc/__snapshots__/TemplateItemActions.test.tsx.snap b/src/loc/__snapshots__/TemplateItemActions.test.tsx.snap index 79efafcf..82fb5407 100644 --- a/src/loc/__snapshots__/TemplateItemActions.test.tsx.snap +++ b/src/loc/__snapshots__/TemplateItemActions.test.tsx.snap @@ -4,6 +4,152 @@ exports[`TemplateItemActions Links are read-only for issuer when acknowledged 1` + + + + + + + + + + + + + + + + + + - + + + + + + +`; + +exports[`TemplateItemActions Links can be acknowledged by owner when published 1`] = ` + + + + + + + + +`; + +exports[`TemplateItemActions Links can be published or cleared by requester when approved 1`] = ` + + + + + + + + `; -exports[`TemplateItemActions Links can be acknowledged by owner when published 1`] = ` +exports[`TemplateItemActions Links can be published or cleared by requester when approved and submitted by issuer 1`] = ` - + + + + + + `; -exports[`TemplateItemActions Links can be published or cleared by requester when approved 1`] = ` +exports[`TemplateItemActions Links can be reviewed by owner when submitted 1`] = ` - - + + + + + + +`; + +exports[`TemplateItemActions Links can be set by requester when empty 1`] = ` + + + + + + + + `; -exports[`TemplateItemActions Links can be published or cleared by requester when approved and submitted by issuer 1`] = ` +exports[`TemplateItemActions Links can be submitted for review or cleared by issuer when draft 1`] = ` - + Request review + + + + + + + + + +`; + +exports[`TemplateItemActions Links can be submitted for review or cleared by requester when draft 1`] = ` + + + + + + + + `; -exports[`TemplateItemActions Links can be reviewed by owner when submitted 1`] = ` +exports[`TemplateItemActions Private files are read-only for issuer when acknowledged 1`] = ` - + + + + - -`; - -exports[`TemplateItemActions Links can be set by requester when empty 1`] = ` - - + + `; -exports[`TemplateItemActions Links can be submitted for review or cleared by issuer when draft 1`] = ` +exports[`TemplateItemActions Private files are read-only for owner when acknowledged 1`] = ` - - + + + + + `; -exports[`TemplateItemActions Links can be submitted for review or cleared by requester when draft 1`] = ` +exports[`TemplateItemActions Private files are read-only for requester when published 1`] = ` - - + + + - -`; - -exports[`TemplateItemActions Private files are read-only for issuer when acknowledged 1`] = ` - + /> + `; -exports[`TemplateItemActions Private files are read-only for owner when acknowledged 1`] = ` +exports[`TemplateItemActions Private files can be acknowledged by issuer when published 1`] = ` - + + /> + + + + `; -exports[`TemplateItemActions Private files are read-only for requester when published 1`] = ` +exports[`TemplateItemActions Private files can be acknowledged by owner when published 1`] = ` - + + /> + + + + `; -exports[`TemplateItemActions Private files can be acknowledged by issuer when published 1`] = ` +exports[`TemplateItemActions Private files can be published or cleared by requester when approved 1`] = ` - + + + + + + `; -exports[`TemplateItemActions Private files can be acknowledged by owner when published 1`] = ` +exports[`TemplateItemActions Private files can be published or cleared by requester when approved and submitted by issuer 1`] = ` - + + + - -`; - -exports[`TemplateItemActions Private files can be published or cleared by requester when approved 1`] = ` + + + + +`; + +exports[`TemplateItemActions Private files can be reviewed by owner when submitted 1`] = ` - - + + + + + + +`; + +exports[`TemplateItemActions Private files can be set by requester when empty 1`] = ` + + + + + + + + `; -exports[`TemplateItemActions Private files can be published or cleared by requester when approved and submitted by issuer 1`] = ` +exports[`TemplateItemActions Private files can be submitted for review or cleared by issuer when draft 1`] = ` - + Request review + + + + + + + + + +`; + +exports[`TemplateItemActions Private files can be submitted for review or cleared by requester when draft 1`] = ` + + + + + - -`; - -exports[`TemplateItemActions Private files can be reviewed by owner when submitted 1`] = ` - - - -`; - -exports[`TemplateItemActions Private files can be set by requester when empty 1`] = ` - - - -`; - -exports[`TemplateItemActions Private files can be submitted for review or cleared by issuer when draft 1`] = ` - - - - -`; - -exports[`TemplateItemActions Private files can be submitted for review or cleared by requester when draft 1`] = ` - - - - -`; - -exports[`TemplateItemActions Public data are read-only for issuer when acknowledged 1`] = ` - - - -`; - -exports[`TemplateItemActions Public data are read-only for owner when acknowledged 1`] = ` - - - -`; - -exports[`TemplateItemActions Public data are read-only for requester when published 1`] = ` - - - -`; - -exports[`TemplateItemActions Public data can be acknowledged by issuer when published 1`] = ` - - - -`; - -exports[`TemplateItemActions Public data can be acknowledged by owner when published 1`] = ` - - - -`; - -exports[`TemplateItemActions Public data can be published or cleared by requester when approved 1`] = ` - - - + + + `; -exports[`TemplateItemActions Public data can be published or cleared by requester when approved and submitted by issuer 1`] = ` +exports[`TemplateItemActions Public data are read-only for issuer when acknowledged 1`] = ` + + + + + + + + + +`; + +exports[`TemplateItemActions Public data are read-only for owner when acknowledged 1`] = ` + + + + + + + + + +`; + +exports[`TemplateItemActions Public data are read-only for requester when published 1`] = ` + + + + + + + + + +`; + +exports[`TemplateItemActions Public data can be acknowledged by issuer when published 1`] = ` - + + + + + + +`; + +exports[`TemplateItemActions Public data can be acknowledged by owner when published 1`] = ` + + + + + + + + +`; + +exports[`TemplateItemActions Public data can be published or cleared by requester when approved 1`] = ` + + + + + + + + + +`; + +exports[`TemplateItemActions Public data can be published or cleared by requester when approved and submitted by issuer 1`] = ` + + + + + + + `; @@ -2675,6 +6855,226 @@ exports[`TemplateItemActions Public data can be reviewed by owner when submitted } } /> + + + + + + `; @@ -2704,6 +7104,58 @@ exports[`TemplateItemActions Public data can be set by requester when empty 1`] } } /> + + + + + + `; @@ -2716,6 +7168,116 @@ exports[`TemplateItemActions Public data can be submitted for review or cleared > Request review + + + + + + `; @@ -2834,6 +7506,116 @@ exports[`TemplateItemActions Public data can be submitted for review or cleared > Request review + + + + + + `; diff --git a/src/wallet-user/trust-protection/CreateProtectionRequestForm.test.tsx b/src/wallet-user/trust-protection/CreateProtectionRequestForm.test.tsx index edd20c72..0ac634ce 100644 --- a/src/wallet-user/trust-protection/CreateProtectionRequestForm.test.tsx +++ b/src/wallet-user/trust-protection/CreateProtectionRequestForm.test.tsx @@ -15,6 +15,7 @@ import { resetSubmitting } from "../../logion-chain/__mocks__/SignatureMock"; import { TEST_WALLET_USER2 } from "../TestData"; import CreateProtectionRequestForm from "./CreateProtectionRequestForm"; +import { resetPersistenState } from "src/ClientExtrinsicSubmitter"; test("renders", () => { const tree = shallowRender() @@ -97,6 +98,7 @@ describe("CreateProtectionRequestForm", () => { it("should call submit when form is correctly filled for recovery and no recovery already in progress", async () => { resetSubmitting(); + resetPersistenState(); render();