Skip to content

Commit

Permalink
feat: added mustache template rendering
Browse files Browse the repository at this point in the history
  • Loading branch information
jaybuidl committed Oct 12, 2023
1 parent 7f990aa commit 49830da
Show file tree
Hide file tree
Showing 10 changed files with 150 additions and 108 deletions.
18 changes: 14 additions & 4 deletions kleros-sdk/dataMappings/dataMappings.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createPublicClient, http, parseAbiItem, webSocket } from "viem";
import { createPublicClient, parseAbiItem, webSocket } from "viem";
import { arbitrumGoerli } from "viem/chains";
import fetch from "node-fetch";
import mustache from "mustache";
import { DisputeDetails } from "./disputeDetails";

const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY;

Expand Down Expand Up @@ -37,7 +39,7 @@ export const fetchAction = async (link: string, seek, populate) => {
const response = await fetch(link);
const fetchedData = await response.json();
console.log(fetchedData);
let populatedData = {};
const populatedData = {};

seek.forEach((key, idx) => {
const foundValue = findNestedKey(fetchedData, key);
Expand Down Expand Up @@ -85,7 +87,7 @@ export const callAction = async (source, inputs, seek, populate) => {
args: inputs.slice(2),
});

let populatedData = {};
const populatedData = {};

seek.map((item) => {
if (typeof data == "object") {
Expand Down Expand Up @@ -126,7 +128,7 @@ export const eventAction = async (source, inputs, seek, populate) => {

const eventData = contractEvent[0].args;

let populatedData = {};
const populatedData = {};

seek.map((item, index) => {
populatedData[populate[index]] = eventData[item];
Expand Down Expand Up @@ -155,3 +157,11 @@ export const executeAction = async (action) => {
export const parseTemplateWithData = (template, data) => {
return template.replace(/\{\{(.*?)\}\}/g, (_, key) => data[key.trim()] || "");
};

export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDetails => {
const render = mustache.render(mustacheTemplate, data);
console.log("MUSTACHE RENDER: ", render);
const dispute = JSON.parse(render);
// TODO: validate the object according to the DisputeDetails type or a JSON schema
return dispute;
};
2 changes: 1 addition & 1 deletion web/.env.devnet.public
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Do not enter sensitive information here.
export REACT_APP_DEPLOYMENT=devnet
export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/kleroscoredev
export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/kleros/kleros-v2-core-devnet
export REACT_APP_DISPUTE_TEMPLATE_ARBGOERLI_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/templateregistrydevnet
3 changes: 2 additions & 1 deletion web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"@parcel/watcher": "~2.2.0",
"@types/amqplib": "^0.10.1",
"@types/busboy": "^1.5.0",
"@types/mustache": "^4.2.3",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.7",
"@types/styled-components": "^5.1.26",
Expand All @@ -69,7 +70,7 @@
"dependencies": {
"@filebase/client": "^0.0.5",
"@kleros/kleros-v2-contracts": "workspace:^",
"@kleros/ui-components-library": "^2.6.2",
"@kleros/ui-components-library": "^2.6.3",
"@sentry/react": "^7.55.2",
"@sentry/tracing": "^7.55.2",
"@supabase/supabase-js": "^2.33.1",
Expand Down
24 changes: 14 additions & 10 deletions web/src/components/DisputeCard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ import { useVotingHistory } from "queries/useVotingHistory";
import DisputeInfo from "./DisputeInfo";
import PeriodBanner from "./PeriodBanner";
import { isUndefined } from "utils/index";
import { populateTemplate } from "utils/dataMappings";
import { DisputeDetails } from "utils/disputeDetails";
import { INVALID_DISPUTE_DATA_ERROR } from "consts/index";

const StyledCard = styled(Card)`
width: 100%;
Expand Down Expand Up @@ -95,14 +98,18 @@ const DisputeCard: React.FC<IDisputeCard> = ({ id, arbitrated, period, lastPerio
? lastPeriodChange
: getPeriodEndTimestamp(lastPeriodChange, currentPeriodIndex, court.timesPerPeriod);
const { data: disputeTemplate } = useDisputeTemplate(id, arbitrated.id as `0x${string}`);
const title = isUndefined(disputeTemplate) ? (
<StyledSkeleton />
) : (
disputeTemplate?.title ?? "The dispute's template is not correct please vote refuse to arbitrate"
);
let disputeDetails: DisputeDetails | undefined;
try {
if (disputeTemplate) {
disputeDetails = populateTemplate(disputeTemplate.templateData, {});
}
} catch (e) {
console.error(e);
}
const title = isUndefined(disputeDetails) ? <StyledSkeleton /> : disputeDetails?.title ?? INVALID_DISPUTE_DATA_ERROR;
const { data: courtPolicy } = useCourtPolicy(court.id);
const courtName = courtPolicy?.name;
const category = disputeTemplate ? disputeTemplate.category : undefined;
const category = disputeTemplate?.category;
const { data: votingHistory } = useVotingHistory(id);
const localRounds = votingHistory?.dispute?.disputeKitDispute?.localRounds;
const navigate = useNavigate();
Expand All @@ -127,10 +134,7 @@ const DisputeCard: React.FC<IDisputeCard> = ({ id, arbitrated, period, lastPerio
<PeriodBanner isCard={false} id={parseInt(id)} period={currentPeriodIndex} />
<ListContainer>
<ListTitle>
<TruncatedTitle
text={disputeTemplate?.title ?? "The dispute's template is not correct please vote refuse to arbitrate"}
maxLength={50}
/>
<TruncatedTitle text={disputeDetails?.title ?? INVALID_DISPUTE_DATA_ERROR} maxLength={50} />
</ListTitle>
<DisputeInfo
courtId={court?.id}
Expand Down
2 changes: 2 additions & 0 deletions web/src/consts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ export const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.
export const TELEGRAM_REGEX = /^@\w{5,32}$/;
export const ETH_ADDRESS_REGEX = /^0x[a-fA-F0-9]{40}$/;
export const ETH_SIGNATURE_REGEX = /^0x[a-fA-F0-9]{130}$/;

export const INVALID_DISPUTE_DATA_ERROR = `The dispute data is not valid, please vote "Refuse to arbitrate"`;
67 changes: 34 additions & 33 deletions web/src/pages/Cases/CaseDetails/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";
import { useDisputeTemplate } from "queries/useDisputeTemplate";
import { useCourtPolicy } from "queries/useCourtPolicy";
import { isUndefined } from "utils/index";
import { deepParseTemplateWithData, executeAction } from "utils/dataMappings";
import { populateTemplate, executeAction } from "utils/dataMappings";
import { Answer, DisputeDetails } from "utils/disputeDetails";
import { Periods } from "consts/periods";
import { IPFS_GATEWAY } from "consts/index";
import { INVALID_DISPUTE_DATA_ERROR, IPFS_GATEWAY } from "consts/index";
import PolicyIcon from "svgs/icons/policy.svg";
import { StyledSkeleton } from "components/StyledSkeleton";
import DisputeInfo from "components/DisputeCard/DisputeInfo";
Expand Down Expand Up @@ -115,69 +116,69 @@ interface IOverview {
const Overview: React.FC<IOverview> = ({ arbitrable, courtID, currentPeriodIndex }) => {
const { id } = useParams();
const { data: disputeTemplate } = useDisputeTemplate(id, arbitrable);
const { data: disputeDetails } = useDisputeDetailsQuery(id);
const { data: dispute } = useDisputeDetailsQuery(id);
const { data: courtPolicy } = useCourtPolicy(courtID);
const { data: votingHistory } = useVotingHistory(id);

const [parsedDisputeTemplate, setParsedDisputeTemplate] = useState<any>({});
const [disputeDetails, setDisputeDetails] = useState<DisputeDetails | undefined>(undefined);

const localRounds = votingHistory?.dispute?.disputeKitDispute?.localRounds;
const courtName = courtPolicy?.name;
const court = disputeDetails?.dispute?.court;
const court = dispute?.dispute?.court;
const rewards = court ? `≥ ${formatEther(court.feeForJuror)} ETH` : undefined;
const category = disputeTemplate ? disputeTemplate.category : undefined;
const disputeTemplateInput = disputeTemplate?.templateData;
const dataMappingsInput = disputeTemplate?.templateDataMappings;

useEffect(() => {
if (!disputeTemplateInput || !dataMappingsInput) return;
const fetchData = async () => {
let parsedMapping;
try {
const parsedTemplate = JSON.parse(disputeTemplate?.templateData);
const parsedMapping = JSON.parse(disputeTemplate?.templateDataMappings);

let populatedData = {};

parsedMapping = JSON.parse(dataMappingsInput);
} catch (e) {
console.error(e);
setDisputeDetails(undefined);
// return;
parsedMapping = JSON.parse("[]");
}
try {
let data = {};
for (const action of parsedMapping) {
const result = await executeAction(action);
populatedData = { ...populatedData, ...result };
data = { ...data, ...result };
}

const finalTemplate = deepParseTemplateWithData(parsedTemplate, populatedData);
setParsedDisputeTemplate(finalTemplate);
setDisputeDetails(populateTemplate(disputeTemplateInput, data));
} catch (e) {
console.error(e);
}
};

fetchData();
}, [disputeTemplate]);
}, [disputeTemplateInput, dataMappingsInput]);

return (
<>
<Container>
<h1>
{isUndefined(disputeTemplate) ? (
<StyledSkeleton />
) : (
parsedDisputeTemplate?.template?.content ??
"The dispute's template is not correct please vote refuse to arbitrate"
)}
{isUndefined(disputeTemplate) ? <StyledSkeleton /> : disputeDetails?.title ?? INVALID_DISPUTE_DATA_ERROR}
</h1>
<QuestionAndDescription>
<ReactMarkdown>{parsedDisputeTemplate?.question}</ReactMarkdown>
<ReactMarkdown>{parsedDisputeTemplate?.description}</ReactMarkdown>
<ReactMarkdown>{disputeDetails?.question ?? INVALID_DISPUTE_DATA_ERROR}</ReactMarkdown>
<ReactMarkdown>{disputeDetails?.description ?? INVALID_DISPUTE_DATA_ERROR}</ReactMarkdown>
</QuestionAndDescription>
{parsedDisputeTemplate?.frontendUrl && (
<a href={parsedDisputeTemplate?.frontendUrl} target="_blank" rel="noreferrer">
{disputeDetails?.frontendUrl && (
<a href={disputeDetails?.frontendUrl} target="_blank" rel="noreferrer">
Go to arbitrable
</a>
)}
<VotingOptions>
{parsedDisputeTemplate && <h3>Voting Options</h3>}
{disputeDetails && <h3>Voting Options</h3>}
<Answers>
{parsedDisputeTemplate?.options?.titles?.map((title: string, i: number) => (
<span key={i}>
{disputeDetails?.answers?.map((answer: Answer, i: number) => (
<span key={answer.id}>
<small>Option {i + 1}:</small>
<label>{title}. </label>
<label>{parsedDisputeTemplate.options.descriptions[i]}</label>
<label>{answer.title}. </label>
<label>{answer.description}</label>
</span>
))}
</Answers>
Expand All @@ -194,8 +195,8 @@ const Overview: React.FC<IOverview> = ({ arbitrable, courtID, currentPeriodIndex
<ShadeArea>
<p>Make sure you understand the Policies</p>
<LinkContainer>
{parsedDisputeTemplate?.policyURI && (
<StyledA href={`${IPFS_GATEWAY}${parsedDisputeTemplate.policyURI}`} target="_blank" rel="noreferrer">
{disputeDetails?.policyURI && (
<StyledA href={`${IPFS_GATEWAY}${disputeDetails.policyURI}`} target="_blank" rel="noreferrer">
<PolicyIcon />
Dispute Policy
</StyledA>
Expand Down
60 changes: 27 additions & 33 deletions web/src/pages/DisputeTemplateView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import styled from "styled-components";
import { Textarea } from "@kleros/ui-components-library";
import PolicyIcon from "svgs/icons/policy.svg";
import ReactMarkdown from "components/ReactMarkdown";
import { IPFS_GATEWAY } from "consts/index";
import { deepParseTemplateWithData, executeAction } from "utils/dataMappings";
import { INVALID_DISPUTE_DATA_ERROR, IPFS_GATEWAY } from "consts/index";
import { populateTemplate, executeAction } from "utils/dataMappings";
import { Answer, DisputeDetails } from "utils/disputeDetails";

const Container = styled.div`
width: 50%;
Expand Down Expand Up @@ -78,40 +79,36 @@ const StyledTextArea = styled(Textarea)`
`;

const DisputeTemplateView: React.FC = () => {
const [parsedDisputeTemplate, setParsedDisputeTemplate] = useState<any>({});
const [disputeDetails, setDisputeDetails] = useState<DisputeDetails | undefined>(undefined);
const [disputeTemplateInput, setDisputeTemplateInput] = useState<string>("");
const [dataMappingsInput, setDataMappingsInput] = useState<string>("");

useEffect(() => {
if (!disputeTemplateInput || !dataMappingsInput) return;

const fetchData = async () => {
let parsedTemplate, parsedMapping;

let parsedMapping;
try {
parsedTemplate = JSON.parse(disputeTemplateInput);
parsedMapping = JSON.parse(dataMappingsInput);
} catch (e) {
console.error(e);
setParsedDisputeTemplate(undefined);
setDisputeDetails(undefined);
return;
}

try {
let populatedData = {};

let data = {};
for (const action of parsedMapping) {
const result = await executeAction(action);
populatedData = { ...populatedData, ...result };
data = { ...data, ...result };
}

const finalTemplate = deepParseTemplateWithData(parsedTemplate, populatedData);
setParsedDisputeTemplate(finalTemplate);
console.log("disputeTemplateInput: ", disputeTemplateInput);
console.log("data: ", data);
const finalDisputeDetails = populateTemplate(disputeTemplateInput, data);
console.log("finalTemplate: ", finalDisputeDetails);
setDisputeDetails(finalDisputeDetails);
} catch (e) {
console.error(e);
}
};

fetchData();
}, [disputeTemplateInput, dataMappingsInput]);

Expand All @@ -127,43 +124,40 @@ const DisputeTemplateView: React.FC = () => {
onChange={(e) => setDataMappingsInput(e.target.value)}
placeholder="Enter data mappings"
/>
<Overview disputeTemplate={parsedDisputeTemplate} />
<Overview disputeDetails={disputeDetails} />
</Wrapper>
);
};

const Overview: React.FC<{ disputeTemplate: any }> = ({ disputeTemplate }) => {
const Overview: React.FC<{ disputeDetails: DisputeDetails | undefined }> = ({ disputeDetails }) => {
return (
<>
<Container>
<h1>
{disputeTemplate?.template?.content ??
"The dispute's template is not correct please vote refuse to arbitrate"}
</h1>
<h1>{disputeDetails?.title ?? INVALID_DISPUTE_DATA_ERROR}</h1>
<QuestionAndDescription>
<ReactMarkdown>{disputeTemplate?.question}</ReactMarkdown>
<ReactMarkdown>{disputeTemplate?.description}</ReactMarkdown>
<ReactMarkdown>{disputeDetails?.question ?? INVALID_DISPUTE_DATA_ERROR}</ReactMarkdown>
<ReactMarkdown>{disputeDetails?.description ?? INVALID_DISPUTE_DATA_ERROR}</ReactMarkdown>
</QuestionAndDescription>
{disputeTemplate?.frontendUrl && (
<a href={disputeTemplate?.frontendUrl} target="_blank" rel="noreferrer">
{disputeDetails?.frontendUrl && (
<a href={disputeDetails?.frontendUrl} target="_blank" rel="noreferrer">
Go to arbitrable
</a>
)}
<VotingOptions>
{disputeTemplate && <h3>Voting Options</h3>}
{disputeTemplate?.options?.titles?.map((title: string, i: number) => (
<span key={i}>
{disputeDetails && <h3>Voting Options</h3>}
{disputeDetails?.answers?.map((answer: Answer, i: number) => (
<span key={answer.id}>
<small>Option {i + 1}:</small>
<label>{title}. </label>
<label>{disputeTemplate.options.descriptions[i]}</label>
<label>{answer.title}. </label>
<label>{answer.description}</label>
</span>
))}
</VotingOptions>
<ShadeArea>
<p>Make sure you understand the Policies</p>
<LinkContainer>
{disputeTemplate?.policyURI && (
<StyledA href={`${IPFS_GATEWAY}${disputeTemplate.policyURI}`} target="_blank" rel="noreferrer">
{disputeDetails?.policyURI && (
<StyledA href={`${IPFS_GATEWAY}${disputeDetails.policyURI}`} target="_blank" rel="noreferrer">
<PolicyIcon />
Dispute Policy
</StyledA>
Expand Down
Loading

0 comments on commit 49830da

Please sign in to comment.