Skip to content

Commit

Permalink
Merge pull request #756 from hats-finance/feat/better-payouts
Browse files Browse the repository at this point in the history
[FEAT] Github connection with payouts
  • Loading branch information
fonstack authored Sep 10, 2024
2 parents 4c49a04 + d10c406 commit 4d28e87
Show file tree
Hide file tree
Showing 19 changed files with 522 additions and 71 deletions.
2 changes: 1 addition & 1 deletion packages/shared/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hats.finance/shared",
"version": "1.1.119",
"version": "1.1.120",
"description": "",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
14 changes: 14 additions & 0 deletions packages/shared/src/types/payout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@ export interface IPayoutResponse {
updatedAt?: Date;
}

export type GithubIssue = {
id: number;
number: number;
title: string;
createdBy: number;
labels: string[];
validLabels: string[];
createdAt: string;
body: string;
txHash?: string;
};

export type IPayoutData = ISinglePayoutData | ISplitPayoutData;

interface IPayoutDataBase {
Expand All @@ -64,6 +76,7 @@ export interface ISinglePayoutData extends IPayoutDataBase {
nftUrl: string;
submissionData?: { id: string; subId: string; idx: number };
decryptedSubmission?: Omit<ISubmittedSubmission, "linkedVault">; // Omit: workaround to avoid circular dependency;
ghIssue?: GithubIssue;
}

// Only for v2 vaults
Expand All @@ -89,6 +102,7 @@ export interface ISplitPayoutBeneficiary {
nftUrl: string;
submissionData?: { id: string; subId: string; idx: number };
decryptedSubmission?: Omit<ISubmittedSubmission, "linkedVault">; // Omit: workaround to avoid circular dependency;
ghIssue?: GithubIssue;
}

export interface IPayoutSignature {
Expand Down
6 changes: 3 additions & 3 deletions packages/shared/src/utils/vaultEditor.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,9 +447,9 @@ export function editedFormToCreateVaultOnChainCall(
descriptionHash,
bountyGovernanceHAT: formatPercentage(getHatsFee()),
bountyHackerHATVested: formatPercentage(editedVaultDescription.parameters.fixedHatsRewardPercetange ?? 0),
arbitratorCanChangeBeneficiary: true,
arbitratorCanChangeBounty: true,
arbitratorCanSubmitClaims: true,
arbitratorCanChangeBeneficiary: false,
arbitratorCanChangeBounty: false,
arbitratorCanSubmitClaims: false,
isTokenLockRevocable: false,
arbitrator: ChainsConfig[+(editedVaultDescription.committee.chainId ?? "1")].arbitratorContract,
} as ICreateVaultOnChainCall;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface FormSelectInputOption {
interface FormSelectInputProps {
name?: string;
label?: string;
helper?: string;
placeholder?: string;
emptyState?: string;
multiple?: boolean;
Expand Down Expand Up @@ -52,6 +53,7 @@ export function FormSelectInputComponent(
noSelectedMark = false,
emptyState,
error,
helper,
placeholder,
label,
}: FormSelectInputProps,
Expand Down Expand Up @@ -145,6 +147,7 @@ export function FormSelectInputComponent(
})}
</SelectMenuOptions>
)}
{!error && helper && <span className="helper">{helper}</span>}
</StyledFormSelectInput>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ export const StyledFormSelectInput = styled.div<{ noMargin: boolean; flexExpand:
margin-left: ${getSpacing(1)};
font-size: var(--xxsmall);
}
span.helper {
display: block;
color: var(--secondary-light);
margin-top: ${getSpacing(0.5)};
margin-left: ${getSpacing(1)};
font-size: var(--xxsmall);
}
`
);

Expand Down
1 change: 1 addition & 0 deletions packages/web/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export enum LocalStorage {
ShowedWhereverCTA = "HATS_SHOWED_WHEREVER_CTA",
Submissions = "HATS_SUBMISSIONS",
SelectedSubmissions = "HATS_USER_SELECTED_SUBMISSIONS",
SelectedVaultInSubmissions = "HATS_USER_SELECTED_VAULT_IN_SUBMISSIONS",
CoingeckoPrices = "HATS_COINGECKO_PRICES",
CompetitionStreak = "HATS_HAS_SEEN_COMPETITION_STREAK",
AirdropModalSeen = "HATS_HAS_SEEN_AIRDROP_MODAL",
Expand Down
3 changes: 3 additions & 0 deletions packages/web/src/hooks/vaults/useUserVaults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ export const useUserVaults = (versions: UserVaultsVersion[] = ["all"]) => {
}
}

foundVaults.sort(
(a, b) => (b.description?.["project-metadata"].starttime ?? 0) - (a.description?.["project-metadata"].starttime ?? 0)
);
setUserVaults(foundVaults);
setIsLoading(false);
};
Expand Down
10 changes: 9 additions & 1 deletion packages/web/src/languages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,11 @@
"roles": "Roles",
"competitionsRewards": "Competitions rewards",
"earnedFees": "Earned fees",
"loadingGithubIssues": "Loading GitHub issues",
"submittedAs": "Submitted as",
"labeledAs": "Labeled as",
"onlyShowLabeledIssues": "Show only labeled issues",
"ghIssue": "GH issue",
"MyWallet": {
"overview": "Overview",
"pointValue": "Point value",
Expand Down Expand Up @@ -1125,7 +1130,10 @@
"notAllowedToCreatePayout": "You are not allowed to create a payout for this vault. Please verify you are connected with a committee wallet.",
"filterBySeverity": "Filter by severity",
"filterByTitle": "Filter by title",
"title": "Title"
"filterByVault": "Filter by vault",
"title": "Title",
"showAllVaults": "Show all vaults",
"goToVaultGithubIssues": "Go to vault GitHub issues"
},
"Leaderboard": {
"ifYouAreAWinner": "If you are one of the winners and you want to claim your rewards, please execute the claim by clicking on the button:",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {
DefaultIndexArray,
GithubIssue,
IPayoutData,
ISinglePayoutData,
IVulnerabilitySeverityV1,
IVulnerabilitySeverityV2,
} from "@hats.finance/shared";
import { FormInput, FormSelectInput, Spinner } from "components";
import { FormInput, FormSelectInput, Loading, Spinner } from "components";
import { getCustomIsDirty, useEnhancedFormContext } from "hooks/form";
import { useOnChange } from "hooks/usePrevious";
import { hasSubmissionData } from "pages/CommitteeTools/PayoutsTool/utils/hasSubmissionData";
import { SubmissionCard } from "pages/CommitteeTools/SubmissionsTool/SubmissionsListPage/SubmissionCard";
import { getGhIssueFromSubmission, getGithubIssuesFromVault } from "pages/CommitteeTools/SubmissionsTool/submissionsService.api";
import { useVaultSubmissionsByKeystore } from "pages/CommitteeTools/SubmissionsTool/submissionsService.hooks";
import { useContext } from "react";
import { useContext, useEffect, useState } from "react";
import { Controller, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { SinglePayoutAllocation } from "../../../components";
Expand Down Expand Up @@ -81,6 +83,23 @@ export const SinglePayoutForm = () => {
}
});

const [vaultGithubIssues, setVaultGithubIssues] = useState<GithubIssue[] | undefined>(undefined);
const [isLoadingGH, setIsLoadingGH] = useState<boolean>(false);

// Get information from github
useEffect(() => {
if (!beneficiarySubmission || !vault) return;
if (vaultGithubIssues !== undefined) return;

const loadGhIssues = async () => {
setIsLoadingGH(true);
const ghIssues = await getGithubIssuesFromVault(vault);
setVaultGithubIssues(ghIssues);
setIsLoadingGH(false);
};
loadGhIssues();
}, [vault, vaultGithubIssues, beneficiarySubmission]);

return (
<StyledPayoutForm>
<div className="form-container">
Expand All @@ -100,6 +119,10 @@ export const SinglePayoutForm = () => {
<SubmissionCard
inPayout
submission={isPayoutCreated ? decryptedSubmission ?? beneficiarySubmission! : beneficiarySubmission!}
ghIssue={getGhIssueFromSubmission(
isPayoutCreated ? decryptedSubmission ?? beneficiarySubmission! : beneficiarySubmission!,
vaultGithubIssues
)}
/>
</div>
) : (
Expand Down Expand Up @@ -203,6 +226,7 @@ export const SinglePayoutForm = () => {
colorable
/>
</div> */}
{isLoadingGH && <Loading fixed extraText={`${t("loadingGithubIssues")}...`} />}
</StyledPayoutForm>
);
};
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { IPayoutResponse, ISplitPayoutData, IVault } from "@hats.finance/shared";
import { GithubIssue, IPayoutResponse, ISplitPayoutData, IVault } from "@hats.finance/shared";
import DeleteIcon from "@mui/icons-material/DeleteOutlineOutlined";
import InfoIcon from "@mui/icons-material/InfoOutlined";
import MoreIcon from "@mui/icons-material/MoreVertOutlined";
import { DropdownSelector, FormInput, FormSelectInput, FormSelectInputOption, Modal, Spinner } from "components";
import { DropdownSelector, FormInput, FormSelectInput, FormSelectInputOption, Loading, Modal, Spinner } from "components";
import { getCustomIsDirty, useEnhancedFormContext } from "hooks/form";
import useModal from "hooks/useModal";
import { useOnChange } from "hooks/usePrevious";
import { hasSubmissionData } from "pages/CommitteeTools/PayoutsTool/utils/hasSubmissionData";
import { SubmissionCard } from "pages/CommitteeTools/SubmissionsTool/SubmissionsListPage/SubmissionCard";
import { getGhIssueFromSubmission, getGithubIssuesFromVault } from "pages/CommitteeTools/SubmissionsTool/submissionsService.api";
import { useVaultSubmissionsByKeystore } from "pages/CommitteeTools/SubmissionsTool/submissionsService.hooks";
import { useState } from "react";
import { useEffect, useState } from "react";
import { Controller, UseFieldArrayRemove, useWatch } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { SinglePayoutAllocation } from "../../SinglePayoutAllocation/SinglePayoutAllocation";
Expand Down Expand Up @@ -97,6 +98,23 @@ export const SplitPayoutBeneficiaryForm = ({
setValue<any>(`beneficiaries.${index}.percentageOfPayout` as any, defaultPoints, { shouldValidate: true });
});

const [vaultGithubIssues, setVaultGithubIssues] = useState<GithubIssue[] | undefined>(undefined);
const [isLoadingGH, setIsLoadingGH] = useState<boolean>(false);

// Get information from github
useEffect(() => {
if (!beneficiarySubmission || !vault) return;
if (vaultGithubIssues !== undefined) return;

const loadGhIssues = async () => {
setIsLoadingGH(true);
const ghIssues = await getGithubIssuesFromVault(vault);
setVaultGithubIssues(ghIssues);
setIsLoadingGH(false);
};
loadGhIssues();
}, [vault, vaultGithubIssues, beneficiarySubmission]);

const getMoreOptions = () => {
if (beneficiariesCount === undefined) return [];
if (beneficiariesCount > 1 && !readOnly && !isPayoutCreated) {
Expand Down Expand Up @@ -148,6 +166,10 @@ export const SplitPayoutBeneficiaryForm = ({
submission={
isPayoutCreated ? beneficiaries[index]?.decryptedSubmission ?? beneficiarySubmission! : beneficiarySubmission!
}
ghIssue={getGhIssueFromSubmission(
isPayoutCreated ? beneficiaries[index]?.decryptedSubmission ?? beneficiarySubmission! : beneficiarySubmission!,
vaultGithubIssues
)}
/>
</div>
) : (
Expand Down Expand Up @@ -241,6 +263,8 @@ export const SplitPayoutBeneficiaryForm = ({
/>
</StyledSplitPayoutBeneficiaryAllocationModal>
</Modal>

{isLoadingGH && <Loading fixed extraText={`${t("loadingGithubIssues")}...`} />}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import { allowedElementsMarkdown } from "@hats.finance/shared";
import { GithubIssue, allowedElementsMarkdown } from "@hats.finance/shared";
import ArrowBackIcon from "@mui/icons-material/ArrowBackIosNewOutlined";
import LinkIcon from "@mui/icons-material/InsertLinkOutlined";
import MDEditor from "@uiw/react-md-editor";
import { Alert, Button, HatSpinner, WalletButton } from "components";
import { Alert, Button, HatSpinner, Loading, WalletButton } from "components";
import { useKeystore } from "components/Keystore";
import { IPFS_PREFIX } from "constants/constants";
import { useVaults } from "hooks/subgraph/vaults/useVaults";
import { RoutePaths } from "navigation";
import { useEffect } from "react";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { appChains } from "settings";
import { useAccount } from "wagmi";
import { SubmissionCard } from "../SubmissionsListPage/SubmissionCard";
import { getGhIssueFromSubmission, getGithubIssuesFromVault } from "../submissionsService.api";
import { useVaultSubmissionsByKeystore } from "../submissionsService.hooks";
import { StyledSubmissionDetailsPage } from "./styles";

Expand All @@ -20,15 +22,36 @@ export const SubmissionDetailsPage = () => {
const navigate = useNavigate();
const { address } = useAccount();
const { keystore, initKeystore } = useKeystore();
const { allVaults, vaultsReadyAllChains } = useVaults();

const { subId } = useParams();
const { data: committeeSubmissions, isLoading } = useVaultSubmissionsByKeystore();
const submission = committeeSubmissions?.find((sub) => sub.subId === subId);

const [vaultGithubIssues, setVaultGithubIssues] = useState<GithubIssue[] | undefined>(undefined);
const [isLoadingGH, setIsLoadingGH] = useState<boolean>(false);

useEffect(() => {
if (!keystore) setTimeout(() => initKeystore(), 600);
}, [keystore, initKeystore]);

// Get information from github
useEffect(() => {
if (!submission) return;

const vault = allVaults?.find((vault) => vault.id.toLowerCase() === submission.linkedVault?.id.toLowerCase());
if (!vault) return;
if (vaultGithubIssues !== undefined) return;

const loadGhIssues = async () => {
setIsLoadingGH(true);
const ghIssues = await getGithubIssuesFromVault(vault);
setVaultGithubIssues(ghIssues);
setIsLoadingGH(false);
};
loadGhIssues();
}, [allVaults, vaultGithubIssues, submission]);

const openSubmissionData = () => {
window.open(`${IPFS_PREFIX}/${submission?.submissionHash}`, "_blank");
};
Expand Down Expand Up @@ -82,7 +105,11 @@ export const SubmissionDetailsPage = () => {
</p>
</div>
</div>
<SubmissionCard submission={submission} noActions />
<SubmissionCard
submission={submission}
noActions
ghIssue={getGhIssueFromSubmission(submission, vaultGithubIssues)}
/>

<MDEditor.Markdown
allowedElements={allowedElementsMarkdown}
Expand All @@ -109,6 +136,9 @@ export const SubmissionDetailsPage = () => {
)}
</>
)}

{!vaultsReadyAllChains && <Loading fixed extraText={`${t("loadingVaults")}...`} />}
{isLoadingGH && <Loading fixed extraText={`${t("loadingGithubIssues")}...`} />}
</StyledSubmissionDetailsPage>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ISubmittedSubmission, IVulnerabilitySeverity, parseSeverityName } from "@hats.finance/shared";
import { GithubIssue, ISubmittedSubmission, IVulnerabilitySeverity, parseSeverityName } from "@hats.finance/shared";
import ArrowIcon from "@mui/icons-material/ArrowForwardOutlined";
import BoxUnselected from "@mui/icons-material/CheckBoxOutlineBlankOutlined";
import BoxSelected from "@mui/icons-material/CheckBoxOutlined";
Expand All @@ -18,11 +18,13 @@ type SubmissionCardProps = {
inPayout?: boolean;
isChecked?: boolean;
onCheckChange?: (submission: ISubmittedSubmission) => void;
ghIssue?: GithubIssue;
};

export const SubmissionCard = ({
submission,
onCheckChange,
ghIssue,
noActions = false,
isChecked = false,
inPayout = false,
Expand Down Expand Up @@ -65,11 +67,27 @@ export const SubmissionCard = ({
<div className="content">
{submissionData?.severity && (
<span className="severity">
{ghIssue && (
<span>
{t("ghIssue")} #{ghIssue.number} -
</span>
)}
<span>{t("submittedAs")}:</span>
<Pill
textColor={severityColors[severityIndex ?? 0]}
isSeverity
isSeverity={!ghIssue}
text={parseSeverityName(submissionData?.severity) ?? t("noSeverity")}
/>
{ghIssue && ghIssue?.validLabels.length > 0 && (
<>
<span>{t("labeledAs")}:</span>
<Pill
textColor={severityColors[severityIndex ?? 0]}
isSeverity
text={parseSeverityName(ghIssue.validLabels[0])}
/>
</>
)}
</span>
)}
<p className="submission-title">{submissionData?.title}</p>
Expand Down
Loading

0 comments on commit 4d28e87

Please sign in to comment.