Skip to content

Commit

Permalink
feat: enable full-draft mode.
Browse files Browse the repository at this point in the history
  • Loading branch information
gdethier committed Jul 4, 2024
1 parent 5d9f52c commit e6717a1
Show file tree
Hide file tree
Showing 9 changed files with 156 additions and 21 deletions.
4 changes: 2 additions & 2 deletions src/loc/DataLocRequest.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ async function checkFormDisabled() {
});
}

function setupLocsState(legalOfficersWithValidIdentityLoc: LegalOfficerClass[]) {
function setupLocsState(legalOfficersWithNonVoidIdentityLoc: LegalOfficerClass[]) {
const draftRequest = {
locId,
locsState: () => locsState,
Expand All @@ -88,7 +88,7 @@ function setupLocsState(legalOfficersWithValidIdentityLoc: LegalOfficerClass[])
}),
} as DraftRequest;
const locsState = {
legalOfficersWithValidIdentityLoc,
legalOfficersWithNonVoidIdentityLoc,
requestTransactionLoc: () => Promise.resolve(draftRequest),
requestCollectionLoc: () => Promise.resolve(draftRequest),
} as unknown as LocsState;
Expand Down
12 changes: 6 additions & 6 deletions src/loc/DataLocRequest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ export default function DataLocRequest(props: Props) {
const [ legalOfficer, setLegalOfficer ] = useState<LegalOfficerClass | null>(null);
const { locsState } = useUserContext();
const navigate = useNavigate();
const legalOfficersWithValidIdentityLoc = useMemo(() => {
const legalOfficersWithNonVoidIdentityLoc = useMemo(() => {
if (locsState !== undefined) {
return locsState.legalOfficersWithValidIdentityLoc
return locsState.legalOfficersWithNonVoidIdentityLoc
} else {
return []
}
Expand All @@ -42,12 +42,12 @@ export default function DataLocRequest(props: Props) {
>
<Row>
<Col md={ 6 }>
{ legalOfficersWithValidIdentityLoc.length > 0 &&
{ legalOfficersWithNonVoidIdentityLoc.length > 0 &&
<Frame>
<SelectLegalOfficer
legalOfficer={ legalOfficer }
legalOfficerNumber={ 1 }
legalOfficers={ legalOfficersWithValidIdentityLoc }
legalOfficers={ legalOfficersWithNonVoidIdentityLoc }
mode="select"
otherLegalOfficer={ null }
setLegalOfficer={ setLegalOfficer }
Expand All @@ -57,7 +57,7 @@ export default function DataLocRequest(props: Props) {
/>
</Frame>
}
{ legalOfficersWithValidIdentityLoc.length > 0 &&
{ legalOfficersWithNonVoidIdentityLoc.length > 0 &&
<Frame className="request-additional-id-loc-frame">
<p className="info-text">If you do not see the Logion Legal officer you are looking for,
please request an Identity LOC to the Logion Legal Officer of your choice by
Expand All @@ -67,7 +67,7 @@ export default function DataLocRequest(props: Props) {
</ButtonGroup>
</Frame>
}
{ legalOfficersWithValidIdentityLoc.length === 0 &&
{ legalOfficersWithNonVoidIdentityLoc.length === 0 &&
<Frame className="request-id-loc-frame">
<p className="info-text">To submit a { locType } LOC request, you must select a Logion Legal
Officer who already executed an Identity LOC linked to your Polkadot address.</p>
Expand Down
10 changes: 10 additions & 0 deletions src/loc/DraftLocInstructions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ export default function DraftLocInstructions(props: Props) {
<li>Public data will be publicly available on the logion blockchain and public certificate.</li>
<li>Confidential documents will not be publicly available and will stay confidential between you and your Legal Officer.</li>
</ul>
{
props.locType !== "Identity" &&
<p>You must have a valid Identity LOC In order to submit this LOC for review.{" "}
Also, if your LOC contains a link, its target must be closed and not void.</p>
}
<p>You won't be able to cancel this request if it is the target of a link in another request.</p>
{
props.locType === "Identity" &&
<p>You won't be able to cancel this request as long as other requests rely on it.</p>
}
</>
}
/>
Expand Down
42 changes: 41 additions & 1 deletion src/loc/LocDetailsTab.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ function buildLocMock(params: MockParameters): { loc: LocData, locState: LocRequ
closed: params.status === "CLOSED",
createdOn,
closedOn: params.status === "CLOSED" ? closedOn : undefined,
links: [],
} as unknown as LocData;

if(params.voidLoc) {
Expand All @@ -190,10 +191,49 @@ function buildLocMock(params: MockParameters): { loc: LocData, locState: LocRequ
};
}

const locState = {
const locsState = {
draftRequests: {
Collection: [],
Identity: [],
Transaction: [],
},
openLocs: {
Collection: [],
Identity: [],
Transaction: [],
},
closedLocs: {
Collection: [],
Identity: [],
Transaction: [],
},
voidedLocs: {
Collection: [],
Identity: [],
Transaction: [],
},
pendingRequests: {
Collection: [],
Identity: [],
Transaction: [],
},
rejectedRequests: {
Collection: [],
Identity: [],
Transaction: [],
},
acceptedRequests: {
Collection: [],
Identity: [],
Transaction: [],
},
};

const locState = {
isLogionIdentity: () => params.isLogionIdentityLoc === true,
isLogionData: () => params.locType === "Collection" || params.locType === "Transaction",
locsState: () => locsState,
data: () => loc,
} as unknown as LocRequestState;

return { loc, locState };
Expand Down
60 changes: 59 additions & 1 deletion src/loc/LocDetailsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { Row } from "src/common/Grid";
import { Viewer } from "src/common/CommonContext";
import Button from "src/common/Button";
import Icon from "src/common/Icon";
import { useCallback, useEffect, useState } from "react";
import { useCallback, useEffect, useMemo, useState } from "react";
import WarningDialog from "src/common/WarningDialog";
import { useLocContext } from "./LocContext";
import { useNavigate } from "react-router-dom";
Expand Down Expand Up @@ -242,6 +242,25 @@ export function LocDetailsTabContent(props: ContentProps) {
});
}, [ mutateLocState ]);

const canSubmit = useMemo(() => {
return loc
&& (loc.locType === "Identity" || (legalOfficer && locState?.locsState().hasValidIdentityLoc(legalOfficer)))
&& allLinkTargetsValid(locState);
}, [ loc, legalOfficer, locState ]);

const canCancel = useMemo(() => {
return loc && locState
&& hasNoIncomingLink(locState)
&& (
loc.locType !== "Identity"
|| (
legalOfficer
&& locState?.locsState().draftRequests["Transaction"].find(loc => loc.isOwner(legalOfficer.account)) === undefined
&& locState?.locsState().draftRequests["Collection"].find(loc => loc.isOwner(legalOfficer.account)) === undefined
)
)
}, [ loc, legalOfficer, locState ]);

if(!loc) {
return null;
}
Expand Down Expand Up @@ -389,6 +408,7 @@ export function LocDetailsTabContent(props: ContentProps) {
<Button
variant="danger"
onClick={ confirmCancel }
disabled={ !canCancel }
>
<Icon icon={{ id: "void_inv" }}/> Cancel request
</Button>
Expand All @@ -397,6 +417,7 @@ export function LocDetailsTabContent(props: ContentProps) {
loc.status === "DRAFT" &&
<Button
onClick={ confirmSubmit }
disabled={ !canSubmit }
>
<Icon icon={{ id: "submit" }}/> Submit request
</Button>
Expand Down Expand Up @@ -461,3 +482,40 @@ export function LocDetailsTabContent(props: ContentProps) {
</WarningDialog>
</>);
}

function allLinkTargetsValid(locState: LocRequestState | null) {
if(locState) {
return locState.data().links.find(link => !targetValid(locState, link.target)) === undefined;
} else {
return false;
}
}

function targetValid(locState: LocRequestState, targetId: UUID) {
const target = locState.locsState().findByIdOrUndefined(targetId);
return target && target.data().status === "CLOSED" && target.data().voidInfo === undefined;
}

function hasNoIncomingLink(locState: LocRequestState) {
const locsState = locState.locsState();
const all = mergeRequests(locsState.draftRequests)
.concat(mergeRequests(locsState.acceptedRequests))
.concat(mergeRequests(locsState.rejectedRequests))
.concat(mergeRequests(locsState.openLocs))
.concat(mergeRequests(locsState.closedLocs))
.concat(mergeRequests(locsState.voidedLocs))
;
return all.find(request => hasLinkTo(request, locState)) === undefined;
}

function mergeRequests(requests: Record<LocType, LocRequestState[]>) {
return ([] as LocRequestState[])
.concat(requests["Collection"])
.concat(requests["Identity"])
.concat(requests["Transaction"])
;
}

function hasLinkTo(request: LocRequestState, target: LocRequestState) {
return request.data().links.find(link => link.target.equalTo(target.data().id)) !== undefined;
}
17 changes: 11 additions & 6 deletions src/loc/LocLinkButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import LocLinkButton from "./LocLinkButton";
import { clickByName, shallowRender, typeByLabel } from "../tests";
import { render, screen, waitFor } from "@testing-library/react";
import { setClientMock } from "src/logion-chain/__mocks__/LogionChainMock";
import { LocsState, LogionClient } from "@logion/client";
import { LogionClient } from "@logion/client";
import { buildLocRequest } from "./TestData";
import { UUID } from "@logion/node-api";
import { setupQueriesGetLegalOfficerCase } from "src/test/Util";
import { setupApiMock, api, OPEN_IDENTITY_LOC, OPEN_IDENTITY_LOC_ID } from "src/__mocks__/LogionMock";
import { setLocState } from "./__mocks__/LocContextMock";

jest.mock("./LocContext");
jest.mock("../logion-chain");
Expand All @@ -21,17 +22,21 @@ describe("LocLinkButton", () => {
})

it("links to an existing LOC", async () => {
const locsState = {
findByIdOrUndefined: () => ({
data: () => buildLocRequest(UUID.fromDecimalStringOrThrow(OPEN_IDENTITY_LOC_ID), OPEN_IDENTITY_LOC),
}),
};
setClientMock({
locsState: () => Promise.resolve({
findById: () => ({
data: () => buildLocRequest(UUID.fromDecimalStringOrThrow(OPEN_IDENTITY_LOC_ID), OPEN_IDENTITY_LOC),
}),
}) as unknown as LocsState,
locsState: () => Promise.resolve(locsState),
logionApi: api.object(),
} as unknown as LogionClient);
setupApiMock(api => {
setupQueriesGetLegalOfficerCase(api, UUID.fromDecimalStringOrThrow(OPEN_IDENTITY_LOC_ID), OPEN_IDENTITY_LOC);
});
setLocState({
locsState: () => locsState,
});

render(<LocLinkButton text="Link to an existing LOC"/>);
await clickByName(content => /Link to an existing LOC/i.test(content));
Expand Down
10 changes: 5 additions & 5 deletions src/loc/LocLinkExistingLocDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ export interface Props {
}

export default function LocLinkExistingDialog(props: Props) {
const { api, client } = useLogionChain();
const { mutateLocState, locItems } = useLocContext();
const { client } = useLogionChain();
const { mutateLocState, locItems, locState } = useLocContext();
const { control, handleSubmit, setError, clearErrors, formState: { errors }, reset } = useForm<FormValues>({
defaultValues: {
locId: ""
Expand All @@ -32,9 +32,9 @@ export default function LocLinkExistingDialog(props: Props) {
setError("locId", { type: "value", message: "Invalid LOC ID" })
return
}
const loc = await api!.queries.getLegalOfficerCase(locId);
const loc = locState?.locsState().findByIdOrUndefined(locId);
if (!loc) {
setError("locId", { type: "value", message: "LOC not found on chain" })
setError("locId", { type: "value", message: "LOC not found" })
return
}
const alreadyLinked = locItems.find(item => item.type === 'Linked LOC' && item.as<LinkData>().linkedLoc.id.toString() === locId.toString())
Expand All @@ -55,7 +55,7 @@ export default function LocLinkExistingDialog(props: Props) {
});
reset();
props.exit();
}, [ props, locItems, api, setError, clearErrors, reset, client, mutateLocState ])
}, [ props, locItems, locState, setError, clearErrors, reset, client, mutateLocState ])

return (
<>
Expand Down
8 changes: 8 additions & 0 deletions src/loc/__snapshots__/DraftLocInstructions.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@ exports[`DraftLocInstructions renders 1`] = `
Confidential documents will not be publicly available and will stay confidential between you and your Legal Officer.
</li>
</ul>
<p>
You must have a valid Identity LOC In order to submit this LOC for review.
Also, if your LOC contains a link, its target must be closed and not void.
</p>
<p>
You won't be able to cancel this request if it is the target of a link in another request.
</p>
</React.Fragment>
}
/>
Expand Down
Loading

0 comments on commit e6717a1

Please sign in to comment.