diff --git a/CHANGELOG.md b/CHANGELOG.md
index 08ab94281..fee94d409 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -22,6 +22,7 @@ changes.
### Added
+- added pagination to `drep/list` [Issue 756](https://github.com/IntersectMBO/govtool/issues/756)
- added search query param to the `drep/getVotes` [Issue 640](https://github.com/IntersectMBO/govtool/issues/640)
- added filtering and sorting capabilities to the `drep/list` [Issue 722](https://github.com/IntersectMBO/govtool/issues/722)
- added drepView and txHash to the `ada-holder/get-current-delegation` [Issue 689](https://github.com/IntersectMBO/govtool/issues/689)
@@ -55,9 +56,11 @@ changes.
- Add frontend test workflow on github actions [Issue 500](https://github.com/IntersectMBO/govtool/issues/500)
- Add type check & lint to github actions [Issue 512](https://github.com/IntersectMBO/govtool/issues/512)
- Add eslint & prettier to frontend package [Issue 166](https://github.com/IntersectMBO/govtool/issues/166)
+- Add DRep list pagination [Issue 740](https://github.com/IntersectMBO/govtool/issues/740)
### Fixed
+- drep/list sql fix (now the latest tx date is correct) [Issue 826](https://github.com/IntersectMBO/govtool/issues/826)
- drep/info no longer returns null values [Issue 720](https://github.com/IntersectMBO/govtool/issues/720)
- drep/getVotes no longer returns 500 [Issue 685](https://github.com/IntersectMBO/govtool/issues/685)
- drep/info no longer returns 500 [Issue 676](https://github.com/IntersectMBO/govtool/issues/676)
@@ -109,6 +112,7 @@ changes.
- Update frontend package readme to reflect recent changes [Issue 543](https://github.com/IntersectMBO/govtool/issues/543)
- Change input selection strategy to 3 (random) [Issue 575](https://github.com/IntersectMBO/govtool/issues/575)
- Changed documents to prepare for open source [Issue 737](https://github.com/IntersectMBO/govtool/issues/737)
+- Changed copy on maintenance page [Issue 753](https://github.com/IntersectMBO/govtool/issues/753)
### Removed
diff --git a/govtool/backend/sql/list-dreps.sql b/govtool/backend/sql/list-dreps.sql
index 6a7351ec9..74def83c8 100644
--- a/govtool/backend/sql/list-dreps.sql
+++ b/govtool/backend/sql/list-dreps.sql
@@ -69,7 +69,7 @@ FROM
LEFT JOIN voting_procedure AS voting_procedure ON voting_procedure.drep_voter = dh.id
LEFT JOIN tx AS tx ON tx.id = voting_procedure.tx_id
LEFT JOIN block AS block ON block.id = tx.block_id
- JOIN (
+ LEFT JOIN (
SELECT
block.time,
dr.drep_hash_id,
@@ -79,9 +79,9 @@ FROM
JOIN tx ON tx.id = dr.tx_id
JOIN block ON block.id = tx.block_id
WHERE
- NOT (dr.deposit > 0)) AS newestRegister ON newestRegister.drep_hash_id = dh.id
+ NOT (dr.deposit < 0)) AS newestRegister ON newestRegister.drep_hash_id = dh.id
AND newestRegister.rn = 1
- JOIN (
+ LEFT JOIN (
SELECT
dr.tx_id,
dr.drep_hash_id,
@@ -89,8 +89,8 @@ FROM
FROM
drep_registration dr) AS dr_first_register ON dr_first_register.drep_hash_id = dh.id
AND dr_first_register.rn = 1
- JOIN tx AS tx_first_register ON tx_first_register.id = dr_first_register.tx_id
- JOIN block AS block_first_register ON block_first_register.id = tx_first_register.block_id
+ LEFT JOIN tx AS tx_first_register ON tx_first_register.id = dr_first_register.tx_id
+ LEFT JOIN block AS block_first_register ON block_first_register.id = tx_first_register.block_id
GROUP BY
dh.raw,
second_to_newest_drep_registration.voting_anchor_id,
diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs
index 08789bbbf..d982c61dd 100644
--- a/govtool/backend/src/VVA/API.hs
+++ b/govtool/backend/src/VVA/API.hs
@@ -47,7 +47,9 @@ type VVAApi =
:> QueryParam "search" Text
:> QueryParams "status" DRepStatus
:> QueryParam "sort" DRepSortMode
- :> Get '[JSON] [DRep]
+ :> QueryParam "page" Natural
+ :> QueryParam "pageSize" Natural
+ :> Get '[JSON] ListDRepsResponse
:<|> "drep" :> "get-voting-power" :> Capture "drepId" HexText :> Get '[JSON] Integer
:<|> "drep" :> "getVotes"
:> Capture "drepId" HexText
@@ -120,8 +122,8 @@ delegationToResponse Types.Delegation {..} =
}
-drepList :: App m => Maybe Text -> [DRepStatus] -> Maybe DRepSortMode -> m [DRep]
-drepList mSearchQuery statuses mSortMode = do
+drepList :: App m => Maybe Text -> [DRepStatus] -> Maybe DRepSortMode -> Maybe Natural -> Maybe Natural -> m ListDRepsResponse
+drepList mSearchQuery statuses mSortMode mPage mPageSize = do
CacheEnv {dRepListCache} <- asks vvaCache
dreps <- cacheRequest dRepListCache () DRep.listDReps
@@ -148,7 +150,23 @@ drepList mSearchQuery statuses mSortMode = do
dRepRegistrationStatus
- return $ map drepRegistrationToDrep $ sortDReps $ filterDRepsByQuery $ filterDRepsByStatus dreps
+ let allValidDReps = map drepRegistrationToDrep $ sortDReps $ filterDRepsByQuery $ filterDRepsByStatus dreps
+
+
+ let page = (fromIntegral $ fromMaybe 0 mPage) :: Int
+ pageSize = (fromIntegral $ fromMaybe 10 mPageSize) :: Int
+
+ total = length allValidDReps :: Int
+
+ let elements = take pageSize $ drop (page * pageSize) allValidDReps
+
+ return $ ListDRepsResponse
+ { listDRepsResponsePage = fromIntegral page
+ , listDRepsResponsePageSize = fromIntegral pageSize
+ , listDRepsResponseTotal = fromIntegral total
+ , listDRepsResponseElements = elements
+ }
+
getVotingPower :: App m => HexText -> m Integer
getVotingPower (unHexText -> dRepId) = do
diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs
index 4462f5a82..e911afa35 100644
--- a/govtool/backend/src/VVA/API/Types.hs
+++ b/govtool/backend/src/VVA/API/Types.hs
@@ -750,6 +750,42 @@ instance ToSchema DRep where
& example
?~ toJSON exampleDrep
+
+exampleListDRepsResponse :: Text
+exampleListDRepsResponse =
+ "{ \"page\": 0,"
+ <> "\"pageSize\": 1,"
+ <> "\"total\": 1000,"
+ <> "\"elements\": ["
+ <> exampleDrep <> "]}"
+
+data ListDRepsResponse
+ = ListDRepsResponse
+ { listDRepsResponsePage :: Integer
+ , listDRepsResponsePageSize :: Integer
+ , listDRepsResponseTotal :: Integer
+ , listDRepsResponseElements :: [DRep]
+ }
+ deriving (Generic, Show)
+
+deriveJSON (jsonOptions "listDRepsResponse") ''ListDRepsResponse
+
+instance ToSchema ListDRepsResponse where
+ declareNamedSchema proxy = do
+ NamedSchema name_ schema_ <-
+ genericDeclareNamedSchema
+ ( fromAesonOptions $
+ jsonOptions "listDRepsResponse"
+ )
+ proxy
+ return $
+ NamedSchema name_ $
+ schema_
+ & description ?~ "ListProposalsResponse"
+ & example
+ ?~ toJSON exampleListDRepsResponse
+
+
data DelegationResponse
= DelegationResponse
{ delegationResponseDRepHash :: Maybe HexText
diff --git a/govtool/frontend/maintenance-page/index.html b/govtool/frontend/maintenance-page/index.html
index f0438c102..47643b5ce 100644
--- a/govtool/frontend/maintenance-page/index.html
+++ b/govtool/frontend/maintenance-page/index.html
@@ -153,8 +153,12 @@
GovTool is down
- The SanchoNet GovTool is currently down for maintenance. Please try
- again later.
+ The SanchoNet GovTool Beta is currently down for maintenance.
+ SanchoNet Govtool is connected to SanchoNet testnet, which
+ means that if the network gets upgraded with new features
+ Govtool needs to be updated too.
+
+ Please try again later.
diff --git a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx
index 5a9e0ba3d..7734f9f30 100644
--- a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx
+++ b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx
@@ -4,7 +4,7 @@ import { Button, LoadingButton, Typography } from "@atoms";
import { primaryBlue } from "@consts";
import { useModal } from "@context";
import { useScreenDimension, useTranslation } from "@hooks";
-import { openInNewTab } from "@utils";
+import { openInNewTab, testIdFromLabel } from "@utils";
import { Card } from "./Card";
import { AutomatedVotingCardProps } from "./types";
@@ -24,6 +24,7 @@ export const AutomatedVotingCard = ({
const { isMobile, screenWidth } = useScreenDimension();
const { openModal } = useModal();
const { t } = useTranslation();
+ const testIdLabel = testIdFromLabel(title);
const onClickShowTransaction = () =>
openInNewTab(`https://sancho.cexplorer.io/tx/${transactionId}`);
@@ -121,7 +122,7 @@ export const AutomatedVotingCard = ({
}}
>
{!isConnected ? (
openModal({ type: "chooseWallet" })}
size={isMobile ? "medium" : "large"}
sx={{ flex: screenWidth < 768 ? 1 : undefined }}
@@ -140,6 +142,7 @@ export const AutomatedVotingCard = ({
) : (
!isSelected && (
> = ({
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx b/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx
index 1770a6806..0b0427483 100644
--- a/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx
+++ b/govtool/frontend/src/components/molecules/DataMissingInfoBox.tsx
@@ -2,8 +2,8 @@ import { Box, Link } from "@mui/material";
import { Typography } from "@atoms";
import { useTranslation } from "@hooks";
+import { MetadataValidationStatus } from "@models";
import { openInNewTab } from "@utils";
-import { MetadataValidationStatus } from "@/models";
export const DataMissingInfoBox = ({
isDataMissing,
@@ -26,6 +26,9 @@ export const DataMissingInfoBox = ({
[MetadataValidationStatus.INVALID_HASH]: t(
"errors.gAMetadata.message.notVerifiable",
),
+ [MetadataValidationStatus.INCORRECT_FORMAT]: t(
+ "errors.gAMetadata.message.incorrectFormat",
+ ),
}[isDataMissing as MetadataValidationStatus];
const gaMetadataErrorDescription = {
@@ -38,6 +41,9 @@ export const DataMissingInfoBox = ({
[MetadataValidationStatus.INVALID_HASH]: t(
"errors.gAMetadata.description.notVerifiable",
),
+ [MetadataValidationStatus.INCORRECT_FORMAT]: t(
+ "errors.gAMetadata.description.incorrectFormat",
+ ),
}[isDataMissing as MetadataValidationStatus];
return isDataMissing && !isSubmitted && !isInProgress ? (
diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx
index 6d08028a6..9dcd48dfe 100644
--- a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx
+++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx
@@ -1,66 +1,64 @@
import { Box } from "@mui/material";
import { Typography } from "@atoms";
+import { ICONS } from "@consts";
+import { useModal } from "@context";
import { useScreenDimension, useTranslation } from "@hooks";
import { LinkWithIcon } from "@molecules";
-import { ICONS } from "@/consts";
-import { useModal } from "@/context";
-// TODO: When BE is ready, pass links as props
-const LINKS = [
- "https://docs.sanchogov.tools/support/get-help-in-discord",
- "https://docs.sanchogov.tools/how-to-use-the-govtool/prerequsites",
- "https://docs.sanchogov.tools/faqs",
- "https://docs.sanchogov.tools/",
-];
-
-export const GovernanceActionDetailsCardLinks = () => {
+export const GovernanceActionDetailsCardLinks = ({
+ links,
+}: {
+ links?: string[];
+}) => {
const { isMobile } = useScreenDimension();
const { t } = useTranslation();
const { openModal } = useModal();
return (
- <>
-
- {t("govActions.supportingLinks")}
-
-
- {LINKS.map((link) => (
- {
- openModal({
- type: "externalLink",
- state: {
- externalLink: link,
- },
- });
- }}
- icon={ }
- cutWithEllipsis
- />
- ))}
-
- >
+ links && (
+ <>
+
+ {t("govActions.supportingLinks")}
+
+
+ {links.map((link) => (
+ {
+ openModal({
+ type: "externalLink",
+ state: {
+ externalLink: link,
+ },
+ });
+ }}
+ icon={ }
+ cutWithEllipsis
+ />
+ ))}
+
+ >
+ )
);
};
diff --git a/govtool/frontend/src/components/molecules/Share.tsx b/govtool/frontend/src/components/molecules/Share.tsx
index 39a712842..e1c6f1122 100644
--- a/govtool/frontend/src/components/molecules/Share.tsx
+++ b/govtool/frontend/src/components/molecules/Share.tsx
@@ -33,6 +33,7 @@ export const Share = ({ link }: { link: string }) => {
return (
<>
({
@@ -44,11 +45,11 @@ export const Share = ({ link }: { link: string }) => {
display: "flex",
justifyContent: "center",
padding: 1.5,
- transition: 'all 0.3s',
- '&:hover': {
+ transition: "all 0.3s",
+ "&:hover": {
boxShadow: theme.shadows[1],
bgcolor: "#F7F9FB",
- }
+ },
})}
>
@@ -82,6 +83,7 @@ export const Share = ({ link }: { link: string }) => {
>
{t("share")}
{
>
- {isActive ? t("clickToCopyLink") : t("linkCopied")}
+
+ {isActive ? t("clickToCopyLink") : t("linkCopied")}
+
>
diff --git a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx
index b2d119a8a..52772f98d 100644
--- a/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx
+++ b/govtool/frontend/src/components/organisms/AutomatedVotingOptions.tsx
@@ -42,6 +42,7 @@ export const AutomatedVotingOptions = ({
return (
setIsOpen(isExpanded)}
diff --git a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx
index b7da3c62b..7b9e0c1f0 100644
--- a/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx
+++ b/govtool/frontend/src/components/organisms/CreateGovernanceActionSteps/ChooseGovernanceActionType.tsx
@@ -43,6 +43,7 @@ export const ChooseGovernanceActionType = ({
{type}
{
navigator.clipboard.writeText(view);
addSuccessAlert(t("alerts.copiedToClipboard"));
@@ -145,6 +146,7 @@ export const DRepCard = ({
}}
>
navigate(
@@ -162,10 +164,18 @@ export const DRepCard = ({
onDelegate &&
!isMe &&
!isInProgress && (
- {t("delegate")}
+
+ {t("delegate")}
+
)}
{status === "Active" && !isConnected && (
-
+
{t("connectToDelegate")}
)}
diff --git a/govtool/frontend/src/components/organisms/DashboardCards.tsx b/govtool/frontend/src/components/organisms/DashboardCards.tsx
index dcb2d48cc..779cb2276 100644
--- a/govtool/frontend/src/components/organisms/DashboardCards.tsx
+++ b/govtool/frontend/src/components/organisms/DashboardCards.tsx
@@ -42,46 +42,48 @@ export const DashboardCards = () => {
}
return (
- = 1728
- ? "repeat(3, minmax(300px, 570px))"
- : "repeat(2, minmax(300px, 530px))",
- justifyContent: screenWidth < 1024 ? "center" : "flex-start",
- px: screenWidth < 640 ? 2 : 5,
- py: 3,
- rowGap: 3,
- }}
- >
-
+
+ = 1728
+ ? "repeat(3, minmax(300px, 570px))"
+ : "repeat(2, minmax(300px, 530px))",
+ justifyContent: screenWidth < 1024 ? "center" : "flex-start",
+ px: screenWidth < 640 ? 2 : 5,
+ py: 3,
+ rowGap: 3,
+ }}
+ >
+
-
+
-
+
-
+
-
+
+
);
};
diff --git a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx
index 0af605808..6aabe7efd 100644
--- a/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx
+++ b/govtool/frontend/src/components/organisms/DashboardCards/DelegateDashboardCard.tsx
@@ -112,7 +112,7 @@ export const DelegateDashboardCard = ({
{displayedDelegationId && (
diff --git a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx
index 426ad82b4..ee9729745 100644
--- a/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx
+++ b/govtool/frontend/src/components/organisms/DashboardGovernanceActionDetails.tsx
@@ -132,6 +132,7 @@ export const DashboardGovernanceActionDetails = () => {
details={state ? state.details : data.proposal.details}
url={state ? state.url : data.proposal.url}
title={state ? state.title : data.proposal.title}
+ links={state ? state.references : data.proposal.references}
about={state ? state.about : data.proposal.about}
motivation={state ? state.motivation : data.proposal.motivation}
rationale={state ? state.rationale : data.proposal.rationale}
diff --git a/govtool/frontend/src/components/organisms/DashboardTopNav.tsx b/govtool/frontend/src/components/organisms/DashboardTopNav.tsx
index fdf0a29c9..369483e7a 100644
--- a/govtool/frontend/src/components/organisms/DashboardTopNav.tsx
+++ b/govtool/frontend/src/components/organisms/DashboardTopNav.tsx
@@ -40,6 +40,7 @@ export const DashboardTopNav = ({
return (
<>
(
{
const onClickFeedback = () => openFeedbackWindow();
return (
-
-
- {t("footer.copyright")}
-
+ <>
-
-
-
-
- }
- sx={{ color: "#26252D" }}
- variant="text"
+
+ {t("footer.copyright")}
+
+
+
+
+
+
- {t("menu.help")}
-
-
- {t("feedback")}
-
+ }
+ sx={{ color: "#26252D" }}
+ variant="text"
+ >
+ {t("menu.help")}
+
+
+ {t("feedback")}
+
+
-
+ >
);
};
diff --git a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx
index d66a05a1c..7cc4656ee 100644
--- a/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx
+++ b/govtool/frontend/src/components/organisms/GovernanceActionDetailsCard.tsx
@@ -24,6 +24,7 @@ type GovernanceActionDetailsCardProps = {
motivation?: string;
rationale?: string;
yesVotes: number;
+ links?: string[];
govActionId: string;
isDataMissing: boolean | MetadataValidationStatus;
isDashboard?: boolean;
@@ -46,6 +47,7 @@ export const GovernanceActionDetailsCard = ({
details,
url,
title,
+ links,
about,
motivation,
rationale,
@@ -91,23 +93,24 @@ export const GovernanceActionDetailsCard = ({
/>
)}
{
const { t } = useTranslation();
const { screenWidth } = useScreenDimension();
@@ -121,7 +123,7 @@ export const GovernanceActionDetailsCardData = ({
{details && Object.keys(details).length !== 0 && (
)}
-
+
);
};
diff --git a/govtool/frontend/src/components/organisms/Modal/StatusModal.tsx b/govtool/frontend/src/components/organisms/Modal/StatusModal.tsx
index 95270c78c..ca5adb5d1 100644
--- a/govtool/frontend/src/components/organisms/Modal/StatusModal.tsx
+++ b/govtool/frontend/src/components/organisms/Modal/StatusModal.tsx
@@ -52,7 +52,15 @@ export const StatusModal = () => {
{state?.message}{" "}
{state?.link && (
diff --git a/govtool/frontend/src/consts/externalDataModalConfig.ts b/govtool/frontend/src/consts/externalDataModalConfig.ts
index 132b20103..36dd10836 100644
--- a/govtool/frontend/src/consts/externalDataModalConfig.ts
+++ b/govtool/frontend/src/consts/externalDataModalConfig.ts
@@ -36,6 +36,7 @@ export const storageInformationErrorModals: Record<
>["state"]
> = {
[MetadataValidationStatus.URL_NOT_FOUND]: urlCannotBeFound,
+ [MetadataValidationStatus.INCORRECT_FORMAT]: externalDataDoesntMatchModal,
[MetadataValidationStatus.INVALID_JSONLD]: externalDataDoesntMatchModal,
[MetadataValidationStatus.INVALID_HASH]: externalDataDoesntMatchModal,
};
diff --git a/govtool/frontend/src/consts/queryKeys.ts b/govtool/frontend/src/consts/queryKeys.ts
index 9a1e3c887..ccfdffe3f 100644
--- a/govtool/frontend/src/consts/queryKeys.ts
+++ b/govtool/frontend/src/consts/queryKeys.ts
@@ -1,7 +1,7 @@
export const QUERY_KEYS = {
getAdaHolderCurrentDelegationKey: "getAdaHolderCurrentDelegationKey",
getAdaHolderVotingPowerKey: "getAdaHolderVotingPowerKey",
- useGetDRepListKey: "useGetDRepListKey",
+ useGetDRepListInfiniteKey: "useGetDRepListInfiniteKey",
useGetDRepVotesKey: "useGetDRepVotesKey",
useGetDRepVotingPowerKey: "useGetDRepVotingPowerKey",
useGetProposalKey: "useGetProposalKey",
diff --git a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx
index 12794fd79..7de68755c 100644
--- a/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx
+++ b/govtool/frontend/src/hooks/forms/useDelegateTodRepForm.tsx
@@ -4,7 +4,7 @@ import { useForm, useWatch } from "react-hook-form";
import { PATHS } from "@consts";
import { useCardano, useModal } from "@context";
-import { useGetDRepListQuery, useTranslation } from "@hooks";
+import { useGetDRepListInfiniteQuery, useTranslation } from "@hooks";
import { formHexToBech32 } from "@utils";
export interface DelegateTodrepFormValues {
@@ -13,7 +13,9 @@ export interface DelegateTodrepFormValues {
export const useDelegateTodRepForm = () => {
const { buildSignSubmitConwayCertTx, buildVoteDelegationCert } = useCardano();
- const { data: drepList } = useGetDRepListQuery();
+ const { dRepData: drepList } = useGetDRepListInfiniteQuery({
+ page: 0,
+ });
const { openModal, closeModal, modal } = useModal();
const [isLoading, setIsLoading] = useState(false);
const navigate = useNavigate();
diff --git a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts
index 47161bcc4..4ae33f960 100644
--- a/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts
+++ b/govtool/frontend/src/hooks/queries/useGetDRepListQuery.ts
@@ -1,35 +1,73 @@
-import { UseQueryOptions, useQuery } from "react-query";
+import { UseInfiniteQueryOptions, useInfiniteQuery } from "react-query";
import { QUERY_KEYS } from "@consts";
import { useCardano } from "@context";
-import { GetDRepListParams, getDRepList } from "@services";
-import { DRepData } from "@/models";
+import { GetDRepListArguments, getDRepList } from "@services";
+import { InfinityDRepData } from "@/models";
-export const useGetDRepListQuery = (
- params?: GetDRepListParams,
- options?: UseQueryOptions
+export const useGetDRepListInfiniteQuery = (
+ {
+ filters = [],
+ pageSize = 10,
+ searchPhrase,
+ sorting,
+ status,
+ }: GetDRepListArguments,
+ options?: UseInfiniteQueryOptions,
) => {
- const { search, sort, status } = params || {};
const { pendingTransaction } = useCardano();
- const { data, isLoading, isPreviousData } = useQuery({
- queryKey: [
- QUERY_KEYS.useGetDRepListKey,
- (pendingTransaction.registerAsSoleVoter ||
+ const {
+ data,
+ isLoading,
+ fetchNextPage,
+ hasNextPage,
+ isFetching,
+ isFetchingNextPage,
+ isPreviousData,
+ } = useInfiniteQuery(
+ [
+ QUERY_KEYS.useGetDRepListInfiniteKey,
+ (
+ pendingTransaction.registerAsSoleVoter ||
pendingTransaction.registerAsDrep ||
pendingTransaction.retireAsSoleVoter ||
- pendingTransaction.retireAsDrep)?.transactionHash,
- search,
- sort,
+ pendingTransaction.retireAsDrep
+ )?.transactionHash,
+ filters,
+ searchPhrase,
+ sorting,
status,
],
- queryFn: () => getDRepList({
- ...(search && { search }),
- ...(sort && { sort }),
- ...(status && { status }),
- }),
- ...options
- });
+ async ({ pageParam = 0 }) =>
+ getDRepList({
+ page: pageParam,
+ pageSize,
+ filters,
+ searchPhrase,
+ sorting,
+ status,
+ }),
+ {
+ getNextPageParam: (lastPage) => {
+ if (lastPage.elements.length === 0) {
+ return undefined;
+ }
- return { data, isLoading, isPreviousData };
+ return lastPage.page + 1;
+ },
+ enabled: options?.enabled,
+ keepPreviousData: options?.keepPreviousData,
+ },
+ );
+
+ return {
+ dRepListFetchNextPage: fetchNextPage,
+ dRepListHasNextPage: hasNextPage,
+ isDRepListFetching: isFetching,
+ isDRepListFetchingNextPage: isFetchingNextPage,
+ isDRepListLoading: isLoading,
+ dRepData: data?.pages.flatMap((page) => page.elements),
+ isPreviousData,
+ };
};
diff --git a/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts b/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts
index 7bde94cee..3bae61e10 100644
--- a/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts
+++ b/govtool/frontend/src/hooks/queries/useGetProposalsInfiniteQuery.ts
@@ -24,11 +24,16 @@ export const useGetProposalsInfiniteQuery = ({
});
const mappedElements = await Promise.all(
data.elements.map(async (proposal: ActionType) => {
- const isDataMissing = await checkIsMissingGAMetadata({
+ const { metadata, status } = await checkIsMissingGAMetadata({
hash: proposal?.metadataHash ?? "",
url: proposal?.url ?? "",
});
- return { ...proposal, isDataMissing };
+ // workaround for the missing data in db-sync
+ return {
+ ...proposal,
+ ...metadata,
+ isDataMissing: status || false,
+ };
}),
);
diff --git a/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts b/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts
index 2bcb4f3c5..e6b0d6a38 100644
--- a/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts
+++ b/govtool/frontend/src/hooks/queries/useGetProposalsQuery.ts
@@ -33,11 +33,12 @@ export const useGetProposalsQuery = ({
allProposals
.flatMap((proposal) => proposal.elements)
.map(async (proposal) => {
- const isDataMissing = await checkIsMissingGAMetadata({
+ const { metadata, status } = await checkIsMissingGAMetadata({
hash: proposal?.metadataHash ?? "",
url: proposal?.url ?? "",
});
- return { ...proposal, isDataMissing };
+ // workaround for the missing data in db-sync
+ return { ...proposal, ...metadata, isDataMissing: status || false };
}),
);
diff --git a/govtool/frontend/src/i18n/locales/en.ts b/govtool/frontend/src/i18n/locales/en.ts
index 979d0f6ca..e59acaa2a 100644
--- a/govtool/frontend/src/i18n/locales/en.ts
+++ b/govtool/frontend/src/i18n/locales/en.ts
@@ -751,7 +751,7 @@ export const en = {
dataMissingErrors: {
dataMissing: "Data Missing",
notVerifiable: "Not Verifiable",
- incorrectFormat: "Incorrect Format",
+ incorrectFormat: "Data Formatted Incorrectly",
},
about: "About",
abstain: "Abstain",
diff --git a/govtool/frontend/src/models/api.ts b/govtool/frontend/src/models/api.ts
index 5b72cff60..ba1b2eb28 100644
--- a/govtool/frontend/src/models/api.ts
+++ b/govtool/frontend/src/models/api.ts
@@ -18,6 +18,12 @@ export enum DRepStatus {
Retired = "Retired",
}
+export enum DRepListSort {
+ VotingPower = "VotingPower",
+ RegistrationDate = "RegistrationDate",
+ Status = "Status",
+}
+
export interface DRepData {
drepId: string;
view: string;
@@ -28,6 +34,12 @@ export interface DRepData {
status: DRepStatus;
type: "DRep" | "SoleVoter";
}
+export type InfinityDRepData = {
+ elements: DRepData[];
+ page: number;
+ pageSize: number;
+ total: number;
+};
export type Vote = "yes" | "no" | "abstain";
diff --git a/govtool/frontend/src/models/metadataValidation.ts b/govtool/frontend/src/models/metadataValidation.ts
index e36e7c226..bb4a4edcc 100644
--- a/govtool/frontend/src/models/metadataValidation.ts
+++ b/govtool/frontend/src/models/metadataValidation.ts
@@ -3,11 +3,14 @@ export enum MetadataValidationStatus {
URL_NOT_FOUND = "URL_NOT_FOUND",
INVALID_JSONLD = "INVALID_JSONLD",
INVALID_HASH = "INVALID_HASH",
+ INCORRECT_FORMAT = "INCORRECT_FORMAT",
}
export type ValidateMetadataResult = {
status?: MetadataValidationStatus;
valid: boolean;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ metadata?: any;
};
export enum MetadataStandard {
diff --git a/govtool/frontend/src/pages/ChooseStakeKey.tsx b/govtool/frontend/src/pages/ChooseStakeKey.tsx
index f8f4ae739..cdd2eac10 100644
--- a/govtool/frontend/src/pages/ChooseStakeKey.tsx
+++ b/govtool/frontend/src/pages/ChooseStakeKey.tsx
@@ -11,6 +11,8 @@ export const ChooseStakeKey = () => (
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/CreateGovernanceAction.tsx b/govtool/frontend/src/pages/CreateGovernanceAction.tsx
index 658494a16..3c507874b 100644
--- a/govtool/frontend/src/pages/CreateGovernanceAction.tsx
+++ b/govtool/frontend/src/pages/CreateGovernanceAction.tsx
@@ -88,6 +88,8 @@ export const CreateGovernanceAction = () => {
{step === 5 && }
{step === 6 && }
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/DRepDetails.tsx b/govtool/frontend/src/pages/DRepDetails.tsx
index a5b9f7f0e..1734fb7a7 100644
--- a/govtool/frontend/src/pages/DRepDetails.tsx
+++ b/govtool/frontend/src/pages/DRepDetails.tsx
@@ -7,12 +7,17 @@ import { ICONS, PATHS } from "@consts";
import { useCardano, useModal } from "@context";
import {
useDelegateTodRep,
- useGetDRepListQuery,
+ useGetDRepListInfiniteQuery,
useScreenDimension,
useTranslation,
} from "@hooks";
import { Card, LinkWithIcon, Share } from "@molecules";
-import { correctAdaFormat, isSameDRep, openInNewTab } from "@utils";
+import {
+ correctAdaFormat,
+ isSameDRep,
+ openInNewTab,
+ testIdFromLabel,
+} from "@utils";
const LINKS = [
"darlenelonglink1.DRepwebsiteorwhatever.com",
@@ -36,10 +41,12 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => {
const { delegate, isDelegating } = useDelegateTodRep();
- const { data, isLoading } = useGetDRepListQuery({ search: dRepParam });
- const dRep = data?.[0];
+ const { dRepData, isDRepListLoading } = useGetDRepListInfiniteQuery({
+ searchPhrase: dRepParam,
+ });
+ const dRep = dRepData?.[0];
- if (data === undefined || isLoading)
+ if (dRep === undefined || isDRepListLoading)
return (
{
return (
<>
navigate(
@@ -126,6 +134,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => {
{isMe && (
navigate(PATHS.editDrepMetadata)}
variant="outlined"
>
@@ -190,6 +199,7 @@ export const DRepDetails = ({ isConnected }: DRepDetailsProps) => {
)}
{!isConnected && status === "Active" && (
openModal({
type: "chooseWallet",
@@ -253,6 +263,7 @@ const DRepDetailsInfoItem = ({ children, label }: DrepDetailsInfoItemProps) => (
const DRepId = ({ children }: PropsWithChildren) => (
{
return (
{
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/DRepDirectoryContent.tsx b/govtool/frontend/src/pages/DRepDirectoryContent.tsx
index 7a1a27b9f..6192fa9a3 100644
--- a/govtool/frontend/src/pages/DRepDirectoryContent.tsx
+++ b/govtool/frontend/src/pages/DRepDirectoryContent.tsx
@@ -2,18 +2,19 @@ import { FC } from "react";
import { Trans, useTranslation } from "react-i18next";
import { Box, CircularProgress } from "@mui/material";
-import { Typography } from "@atoms";
+import { Button, Typography } from "@atoms";
import { DREP_DIRECTORY_FILTERS, DREP_DIRECTORY_SORTING } from "@consts";
import { useCardano, useDataActionsBar } from "@context";
import {
useDelegateTodRep,
useGetAdaHolderCurrentDelegationQuery,
useGetAdaHolderVotingPowerQuery,
- useGetDRepListQuery,
+ useGetDRepListInfiniteQuery,
} from "@hooks";
import { Card, DataActionsBar } from "@molecules";
import { AutomatedVotingOptions, DRepCard } from "@organisms";
import { correctAdaFormat, formHexToBech32, isSameDRep } from "@utils";
+import { DRepListSort, DRepStatus } from "@models";
interface DRepDirectoryContentProps {
isConnected?: boolean;
@@ -47,20 +48,26 @@ export const DRepDirectoryContent: FC = ({
const { currentDelegation } = useGetAdaHolderCurrentDelegationQuery(stakeKey);
const inProgressDelegation = pendingTransaction.delegate?.resourceId;
- const { data: myDRepList } = useGetDRepListQuery(
+ const { dRepData: myDRepList } = useGetDRepListInfiniteQuery(
{
- search: currentDelegation?.dRepView?.startsWith("drep")
+ searchPhrase: currentDelegation?.dRepView?.startsWith("drep")
? currentDelegation.dRepView
: formHexToBech32(currentDelegation?.dRepHash ?? ""),
},
{ enabled: !!inProgressDelegation || !!currentDelegation },
);
const myDrep = myDRepList?.[0];
- const { data: dRepList, isPreviousData } = useGetDRepListQuery(
+
+ const {
+ dRepData: dRepList,
+ isPreviousData,
+ dRepListHasNextPage,
+ dRepListFetchNextPage,
+ } = useGetDRepListInfiniteQuery(
{
- search: debouncedSearchText,
- sort: chosenSorting,
- status: chosenFilters,
+ searchPhrase: debouncedSearchText,
+ sorting: chosenSorting as DRepListSort,
+ status: chosenFilters as DRepStatus[],
},
{
keepPreviousData: true,
@@ -186,6 +193,17 @@ export const DRepDirectoryContent: FC = ({
})}
>
+ {dRepListHasNextPage && (
+
+ dRepListFetchNextPage()}
+ >
+ {t("showMore")}
+
+
+ )}
);
};
diff --git a/govtool/frontend/src/pages/Dashboard.tsx b/govtool/frontend/src/pages/Dashboard.tsx
index d8997ce91..b6852bc8e 100644
--- a/govtool/frontend/src/pages/Dashboard.tsx
+++ b/govtool/frontend/src/pages/Dashboard.tsx
@@ -60,6 +60,8 @@ export const Dashboard = () => {
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx b/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx
index 70975a860..c1982d563 100644
--- a/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx
+++ b/govtool/frontend/src/pages/DashboardGovernanceActionsCategory.tsx
@@ -68,118 +68,121 @@ export const DashboardGovernanceActionsCategory = () => {
return (
-
-
- navigate(PATHS.dashboardGovernanceActions)}
- >
-
-
- {t("govActions.backToGovActions")}
-
-
-
-
- {getProposalTypeLabel(category ?? "")}
-
- {!mappedData || isEnableLoading || isProposalsLoading ? (
-
-
+ navigate(PATHS.dashboardGovernanceActions)}
+ >
+
+
+ {t("govActions.backToGovActions")}
+
+
+
+
+ {getProposalTypeLabel(category ?? "")}
+
+ {!mappedData || isEnableLoading || isProposalsLoading ? (
+
+
+
+ ) : !mappedData?.length ? (
+
+ ) : (
+
+ {mappedData.map((item) => (
+
+ {
+ saveScrollPosition();
+
+ if (
+ pendingTransaction.vote?.resourceId ===
+ item.txHash + item.index
+ ) {
+ openInNewTab(
+ `https://sancho.cexplorer.io/tx/${pendingTransaction.vote.transactionHash}`,
+ );
+ } else {
+ navigate(
+ generatePath(PATHS.dashboardGovernanceActionsAction, {
+ proposalId: getFullGovActionId(
+ item.txHash,
+ item.index,
+ ),
+ }),
+ {
+ state: {
+ ...item,
+ openedFromCategoryPage: true,
+ },
+ },
+ );
+ }
+ }}
+ txHash={item.txHash}
+ />
- ) : !mappedData?.length ? (
-
- ) : (
+ ))}
+ {proposalsHaveNextPage && isProposalsFetchingNextPage && (
- {mappedData.map((item) => (
-
- {
- saveScrollPosition();
-
- if (
- pendingTransaction.vote?.resourceId ===
- item.txHash + item.index
- ) {
- openInNewTab(
- `https://sancho.cexplorer.io/tx/${pendingTransaction.vote.transactionHash}`,
- );
- } else {
- navigate(
- generatePath(
- PATHS.dashboardGovernanceActionsAction,
- {
- proposalId: getFullGovActionId(
- item.txHash,
- item.index,
- ),
- },
- ),
- {
- state: {
- ...item,
- openedFromCategoryPage: true,
- },
- },
- );
- }
- }}
- txHash={item.txHash}
- />
-
- ))}
- {proposalsHaveNextPage && isProposalsFetchingNextPage && (
-
-
-
- )}
+
)}
-
+ )}
);
diff --git a/govtool/frontend/src/pages/EditDRepMetadata.tsx b/govtool/frontend/src/pages/EditDRepMetadata.tsx
index 41fd70d2d..d7dbaf10d 100644
--- a/govtool/frontend/src/pages/EditDRepMetadata.tsx
+++ b/govtool/frontend/src/pages/EditDRepMetadata.tsx
@@ -82,6 +82,8 @@ export const EditDRepMetadata = () => {
{step === 2 && }
{step === 3 && }
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/GovernanceActionDetails.tsx b/govtool/frontend/src/pages/GovernanceActionDetails.tsx
index 984e69fe5..0dcf03949 100644
--- a/govtool/frontend/src/pages/GovernanceActionDetails.tsx
+++ b/govtool/frontend/src/pages/GovernanceActionDetails.tsx
@@ -181,6 +181,8 @@ export const GovernanceActionDetails = () => {
)}
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/GovernanceActions.tsx b/govtool/frontend/src/pages/GovernanceActions.tsx
index c62279494..95a6f114a 100644
--- a/govtool/frontend/src/pages/GovernanceActions.tsx
+++ b/govtool/frontend/src/pages/GovernanceActions.tsx
@@ -49,13 +49,23 @@ export const GovernanceActions = () => {
-
+
{
}}
/>
)}
-
+
-
+
{!proposals || isProposalsLoading ? (
) : (
-
+ <>
+
+
+ >
)}
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/GovernanceActionsCategory.tsx b/govtool/frontend/src/pages/GovernanceActionsCategory.tsx
index cb5a7d0b4..b78b013cd 100644
--- a/govtool/frontend/src/pages/GovernanceActionsCategory.tsx
+++ b/govtool/frontend/src/pages/GovernanceActionsCategory.tsx
@@ -83,8 +83,14 @@ export const GovernanceActionsCategory = () => {
minHeight="100vh"
>
-
-
+
+
{
)
) : (
-
+
)}
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/Home.tsx b/govtool/frontend/src/pages/Home.tsx
index 25942471c..8c3615a1a 100644
--- a/govtool/frontend/src/pages/Home.tsx
+++ b/govtool/frontend/src/pages/Home.tsx
@@ -24,6 +24,8 @@ export const Home = () => {
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/RegisterAsSoleVoter.tsx b/govtool/frontend/src/pages/RegisterAsSoleVoter.tsx
index 822d809ce..ad387b4da 100644
--- a/govtool/frontend/src/pages/RegisterAsSoleVoter.tsx
+++ b/govtool/frontend/src/pages/RegisterAsSoleVoter.tsx
@@ -1,15 +1,89 @@
-import { useEffect } from "react";
+import { useCallback, useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
+import { Trans } from "react-i18next";
+import { Box, Link } from "@mui/material";
+import { Background, Typography } from "@atoms";
import { PATHS } from "@consts";
-import { RegisterAsSoleVoterBox } from "@organisms";
-import { useTranslation } from "@hooks";
-import { CenteredBoxPageWrapper } from "@molecules";
-import { checkIsWalletConnected } from "@/utils";
+import { useCardano, useModal } from "@context";
+import {
+ useGetVoterInfo,
+ useScreenDimension,
+ useTranslation,
+ useWalletErrorModal,
+} from "@hooks";
+import { LinkWithIcon } from "@molecules";
+import { BgCard, DashboardTopNav, Footer } from "@organisms";
+import {
+ PROTOCOL_PARAMS_KEY,
+ checkIsWalletConnected,
+ correctAdaFormat,
+ getItemFromLocalStorage,
+ openInNewTab,
+} from "@utils";
export const RegisterAsSoleVoter = () => {
+ const epochParams = getItemFromLocalStorage(PROTOCOL_PARAMS_KEY);
const navigate = useNavigate();
+ const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
+ const { isMobile } = useScreenDimension();
+ const { voter } = useGetVoterInfo();
+ const openWalletErrorModal = useWalletErrorModal();
+ const { buildSignSubmitConwayCertTx, buildDRepRegCert, buildDRepUpdateCert } =
+ useCardano();
+ const { openModal, closeModal } = useModal();
+
+ const onRegister = useCallback(async () => {
+ setIsLoading(true);
+
+ try {
+ const certBuilder = voter?.isRegisteredAsDRep
+ ? await buildDRepUpdateCert()
+ : await buildDRepRegCert();
+ const result = await buildSignSubmitConwayCertTx({
+ certBuilder,
+ type: "registerAsSoleVoter",
+ });
+ if (result) {
+ openModal({
+ type: "statusModal",
+ state: {
+ status: "success",
+ title: t("modals.registration.title"),
+ message: t("modals.registration.message"),
+ link: `https://sancho.cexplorer.io/tx/${result}`,
+ buttonText: t("modals.common.goToDashboard"),
+ onSubmit: () => {
+ navigate(PATHS.dashboard);
+ closeModal();
+ },
+ dataTestId: "registration-transaction-submitted-modal",
+ },
+ });
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ openWalletErrorModal({
+ error,
+ buttonText: t("modals.common.goToDashboard"),
+ onSumbit: () => navigate(PATHS.dashboard),
+ dataTestId: "registration-transaction-error-modal",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }, [
+ buildSignSubmitConwayCertTx,
+ buildDRepRegCert,
+ openModal,
+ voter?.isRegisteredAsDRep,
+ ]);
+
+ const navigateToDashboard = useCallback(
+ () => navigate(PATHS.dashboard),
+ [navigate],
+ );
useEffect(() => {
if (checkIsWalletConnected()) {
@@ -18,13 +92,57 @@ export const RegisterAsSoleVoter = () => {
}, []);
return (
-
-
-
+
+
+
+
+
+
+ {t("soleVoter.registerHeading")}
+
+
+ openInNewTab("https://sancho.network/")}
+ sx={{ cursor: "pointer" }}
+ key="0"
+ />,
+ ]}
+ />
+
+
+
+
+
);
};
diff --git a/govtool/frontend/src/pages/RegisterAsdRep.tsx b/govtool/frontend/src/pages/RegisterAsdRep.tsx
index 88f2b1d9e..f5f76d56c 100644
--- a/govtool/frontend/src/pages/RegisterAsdRep.tsx
+++ b/govtool/frontend/src/pages/RegisterAsdRep.tsx
@@ -115,6 +115,8 @@ export const RegisterAsdRep = () => {
>
)}
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/RetireAsDrep.tsx b/govtool/frontend/src/pages/RetireAsDrep.tsx
index 920dd5895..925b86dcb 100644
--- a/govtool/frontend/src/pages/RetireAsDrep.tsx
+++ b/govtool/frontend/src/pages/RetireAsDrep.tsx
@@ -41,6 +41,8 @@ export const RetireAsDrep = () => {
}}
/>
+ {/* FIXME: Footer should be on top of the layout.
+ Should not be rerendered across the pages */}
diff --git a/govtool/frontend/src/pages/RetireAsSoleVoter.tsx b/govtool/frontend/src/pages/RetireAsSoleVoter.tsx
index 7b8fde1cc..369575572 100644
--- a/govtool/frontend/src/pages/RetireAsSoleVoter.tsx
+++ b/govtool/frontend/src/pages/RetireAsSoleVoter.tsx
@@ -1,15 +1,84 @@
-import { useEffect } from "react";
+import { useCallback, useEffect, useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { Trans } from "react-i18next";
+import { Box, Link } from "@mui/material";
+import { Background, Typography } from "@atoms";
import { PATHS } from "@consts";
-import { RetireAsSoleVoterBox } from "@organisms";
-import { useTranslation } from "@hooks";
-import { useNavigate } from "react-router-dom";
-import { CenteredBoxPageWrapper } from "@molecules";
-import { checkIsWalletConnected } from "@/utils";
+import { useCardano, useModal } from "@context";
+import {
+ useGetVoterInfo,
+ useScreenDimension,
+ useTranslation,
+ useWalletErrorModal,
+} from "@hooks";
+import { LinkWithIcon } from "@molecules";
+import { BgCard, DashboardTopNav, Footer } from "@organisms";
+import { checkIsWalletConnected, correctAdaFormat, openInNewTab } from "@utils";
export const RetireAsSoleVoter = () => {
const navigate = useNavigate();
+ const [isLoading, setIsLoading] = useState(false);
const { t } = useTranslation();
+ const { isMobile } = useScreenDimension();
+ const { voter } = useGetVoterInfo();
+ const openWalletErrorModal = useWalletErrorModal();
+ const { buildSignSubmitConwayCertTx, buildDRepRetirementCert } = useCardano();
+ const { openModal, closeModal } = useModal();
+
+ const onRetire = useCallback(async () => {
+ try {
+ setIsLoading(true);
+ if (!voter?.deposit) {
+ throw new Error(t("errors.appCannotGetDeposit"));
+ }
+ const certBuilder = await buildDRepRetirementCert(
+ voter?.deposit?.toString(),
+ );
+ const result = await buildSignSubmitConwayCertTx({
+ certBuilder,
+ type: "retireAsSoleVoter",
+ voterDeposit: voter?.deposit?.toString(),
+ });
+ if (result) {
+ openModal({
+ type: "statusModal",
+ state: {
+ status: "success",
+ title: t("modals.retirement.title"),
+ message: t("modals.retirement.message"),
+ link: `https://sancho.cexplorer.io/tx/${result}`,
+ buttonText: t("modals.common.goToDashboard"),
+ dataTestId: "retirement-transaction-submitted-modal",
+ onSubmit: () => {
+ navigate(PATHS.dashboard);
+ closeModal();
+ },
+ },
+ });
+ }
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ } catch (error: any) {
+ openWalletErrorModal({
+ error,
+ buttonText: t("modals.common.goToDashboard"),
+ onSumbit: () => navigate(PATHS.dashboard),
+ dataTestId: "retirement-transaction-error-modal",
+ });
+ } finally {
+ setIsLoading(false);
+ }
+ }, [
+ buildDRepRetirementCert,
+ buildSignSubmitConwayCertTx,
+ openModal,
+ voter?.deposit,
+ ]);
+
+ const navigateToDashboard = useCallback(
+ () => navigate(PATHS.dashboard),
+ [navigate],
+ );
useEffect(() => {
if (checkIsWalletConnected()) {
@@ -18,12 +87,54 @@ export const RetireAsSoleVoter = () => {
}, []);
return (
-
-
-
+
+
+
+
+
+
+ {t("soleVoter.retirementHeading")}
+
+
+ openInNewTab("https://sancho.network/")}
+ sx={{ cursor: "pointer", textDecoration: "none" }}
+ key="0"
+ />,
+ ]}
+ />
+
+
+
+
+
);
};
diff --git a/govtool/frontend/src/services/API.ts b/govtool/frontend/src/services/API.ts
index 2e084a29b..54c09aa7f 100644
--- a/govtool/frontend/src/services/API.ts
+++ b/govtool/frontend/src/services/API.ts
@@ -6,11 +6,14 @@ import { PATHS } from "@consts";
const TIMEOUT_IN_SECONDS = 30 * 1000; // 1000 ms is 1 s then its 10 s
const BASE_URL = import.meta.env.VITE_BASE_URL;
+// Validation should be performed directly on the server
+// than no metadata service is needed and `/api` might be removed
export const API = axios.create({
baseURL: `${BASE_URL}/api`,
timeout: TIMEOUT_IN_SECONDS,
});
+// TODO: Remove this service and use the API service
export const METADATA_VALIDATION_API = axios.create({
baseURL: `${BASE_URL}/metadata-validation`,
timeout: TIMEOUT_IN_SECONDS,
diff --git a/govtool/frontend/src/services/requests/getDRepList.ts b/govtool/frontend/src/services/requests/getDRepList.ts
index 43430bf68..7d18ee43a 100644
--- a/govtool/frontend/src/services/requests/getDRepList.ts
+++ b/govtool/frontend/src/services/requests/getDRepList.ts
@@ -1,13 +1,32 @@
-import type { DRepData } from "@models";
+import type { InfinityDRepData, DRepStatus, DRepListSort } from "@models";
import { API } from "../API";
-export type GetDRepListParams = {
- search?: string;
- sort?: string;
- status?: string[];
+export type GetDRepListArguments = {
+ filters?: string[];
+ page?: number;
+ pageSize?: number;
+ sorting?: DRepListSort;
+ status?: DRepStatus[];
+ searchPhrase?: string;
};
-export const getDRepList = async (params: GetDRepListParams) => {
- const response = await API.get("/drep/list", { params });
+export const getDRepList = async ({
+ sorting,
+ filters = [],
+ page = 0,
+ pageSize = 10,
+ searchPhrase = "",
+ status = [],
+}: GetDRepListArguments): Promise => {
+ const response = await API.get("/drep/list", {
+ params: {
+ page,
+ pageSize,
+ ...(searchPhrase && { search: searchPhrase }),
+ ...(filters.length && { type: filters }),
+ ...(sorting && { sort: sorting }),
+ ...(status.length && { status }),
+ },
+ });
return response.data;
};
diff --git a/govtool/frontend/src/services/requests/getDRepVotes.ts b/govtool/frontend/src/services/requests/getDRepVotes.ts
index c01f421c1..2b7efd682 100644
--- a/govtool/frontend/src/services/requests/getDRepVotes.ts
+++ b/govtool/frontend/src/services/requests/getDRepVotes.ts
@@ -21,14 +21,19 @@ export const getDRepVotes = async ({
const mappedData = (await Promise.all(
data.map(async (proposal) => {
- const isDataMissing = await checkIsMissingGAMetadata({
+ const { metadata, status } = await checkIsMissingGAMetadata({
hash: proposal?.proposal?.metadataHash,
url: proposal?.proposal?.url,
});
return {
vote: proposal.vote,
- proposal: { ...proposal.proposal, isDataMissing },
+ proposal: {
+ ...proposal.proposal,
+ // workaround for the missing data in db-sync
+ ...metadata,
+ isDataMissing: status || false,
+ },
};
}),
)) as VotedProposalToDisplay[];
diff --git a/govtool/frontend/src/services/requests/getProposal.ts b/govtool/frontend/src/services/requests/getProposal.ts
index faa1d89ab..d366476bb 100644
--- a/govtool/frontend/src/services/requests/getProposal.ts
+++ b/govtool/frontend/src/services/requests/getProposal.ts
@@ -8,10 +8,14 @@ export const getProposal = async (proposalId: string, drepId?: string) => {
`/proposal/get/${encodedHash}?drepId=${drepId}`,
);
- const isDataMissing = await checkIsMissingGAMetadata({
+ const { metadata, status } = await checkIsMissingGAMetadata({
hash: data?.proposal.metadataHash,
url: data?.proposal.url,
});
-
- return { ...data, isDataMissing };
+ // workaround for the missing data in db-sync
+ return {
+ ...data,
+ proposal: { ...data.proposal, ...metadata },
+ isDataMissing: status || false,
+ };
};
diff --git a/govtool/frontend/src/services/requests/getProposals.ts b/govtool/frontend/src/services/requests/getProposals.ts
index ed8fa0cfb..e5cd14b1f 100644
--- a/govtool/frontend/src/services/requests/getProposals.ts
+++ b/govtool/frontend/src/services/requests/getProposals.ts
@@ -18,7 +18,7 @@ export const getProposals = async ({
pageSize = 7,
searchPhrase = "",
sorting = "",
-}: GetProposalsArguments) => {
+}: GetProposalsArguments): Promise => {
const response = await API.get("/proposal/list", {
params: {
page,
@@ -29,5 +29,5 @@ export const getProposals = async ({
...(dRepID && { drepId: dRepID }),
},
});
- return response.data as InfinityProposals;
+ return response.data;
};
diff --git a/govtool/frontend/src/utils/dRep.ts b/govtool/frontend/src/utils/dRep.ts
index 2135677a1..7743a017e 100644
--- a/govtool/frontend/src/utils/dRep.ts
+++ b/govtool/frontend/src/utils/dRep.ts
@@ -1,4 +1,4 @@
-import { DRepData } from '@/models';
+import { DRepData } from "@/models";
export const isSameDRep = (
{ drepId, view }: DRepData,
diff --git a/govtool/frontend/src/utils/getMetadataDataMissingStatusTranslation.ts b/govtool/frontend/src/utils/getMetadataDataMissingStatusTranslation.ts
index 0a91531a6..c7d388e57 100644
--- a/govtool/frontend/src/utils/getMetadataDataMissingStatusTranslation.ts
+++ b/govtool/frontend/src/utils/getMetadataDataMissingStatusTranslation.ts
@@ -13,6 +13,7 @@ export const getMetadataDataMissingStatusTranslation = (
const errorKey = {
[MetadataValidationStatus.URL_NOT_FOUND]: "dataMissing",
[MetadataValidationStatus.INVALID_JSONLD]: "incorrectFormat",
+ [MetadataValidationStatus.INCORRECT_FORMAT]: "incorrectFormat",
[MetadataValidationStatus.INVALID_HASH]: "notVerifiable",
}[status] as "dataMissing" | "incorrectFormat" | "notVerifiable";
return i18n.t(`dataMissingErrors.${errorKey || "dataMissing"}`);
diff --git a/govtool/frontend/src/utils/index.ts b/govtool/frontend/src/utils/index.ts
index 2a0eb135e..c708bd587 100644
--- a/govtool/frontend/src/utils/index.ts
+++ b/govtool/frontend/src/utils/index.ts
@@ -11,6 +11,7 @@ export * from "./ellipsizeText";
export * from "./formatDate";
export * from "./generateAnchor";
export * from "./generateJsonld";
+export * from "./generateMetadataBody";
export * from "./getDRepID";
export * from "./getGovActionId";
export * from "./getLengthInBytes";
@@ -22,6 +23,6 @@ export * from "./localStorage";
export * from "./numberValidation";
export * from "./openInNewTab";
export * from "./removeDuplicatedProposals";
+export * from "./testIdFromLabel";
export * from "./validateMetadataHash";
export * from "./wait";
-export * from "./generateMetadataBody";
diff --git a/govtool/frontend/src/utils/testIdFromLabel.ts b/govtool/frontend/src/utils/testIdFromLabel.ts
new file mode 100644
index 000000000..5c8c9cf4a
--- /dev/null
+++ b/govtool/frontend/src/utils/testIdFromLabel.ts
@@ -0,0 +1,2 @@
+export const testIdFromLabel = (label: string) =>
+ label.trim().replace(/ /g, "-").toLocaleLowerCase();
diff --git a/govtool/frontend/src/utils/tests/canonizeJSON.test.ts b/govtool/frontend/src/utils/tests/canonizeJSON.test.ts
new file mode 100644
index 000000000..7115cc430
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/canonizeJSON.test.ts
@@ -0,0 +1,106 @@
+import { describe, it, expect } from "vitest";
+import { canonizeJSON } from "..";
+
+const exampleJson = {
+ "@context": {
+ "@language": "en-us",
+ CIP100:
+ "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0100/README.md#",
+ CIP108:
+ "https://github.com/cardano-foundation/CIPs/blob/master/CIP-0108/README.md#",
+ hashAlgorithm: "CIP100:hashAlgorithm",
+ body: {
+ "@id": "CIP108:body",
+ "@context": {
+ references: {
+ "@id": "CIP108:references",
+ "@container": "@set",
+ "@context": {
+ GovernanceMetadata: "CIP100:GovernanceMetadataReference",
+ Other: "CIP100:OtherReference",
+ label: "CIP100:reference-label",
+ uri: "CIP100:reference-uri",
+ referenceHash: {
+ "@id": "CIP108:referenceHash",
+ "@context": {
+ hashDigest: "CIP108:hashDigest",
+ hashAlgorithm: "CIP100:hashAlgorithm",
+ },
+ },
+ },
+ },
+ title: "CIP108:title",
+ abstract: "CIP108:abstract",
+ motivation: "CIP108:motivation",
+ rationale: "CIP108:rationale",
+ },
+ },
+ authors: {
+ "@id": "CIP100:authors",
+ "@container": "@set",
+ "@context": {
+ name: "http://xmlns.com/foaf/0.1/name",
+ witness: {
+ "@id": "CIP100:witness",
+ "@context": {
+ witnessAlgorithm: "CIP100:witnessAlgorithm",
+ publicKey: "CIP100:publicKey",
+ signature: "CIP100:signature",
+ },
+ },
+ },
+ },
+ },
+ authors: [],
+ hashAlgorithm: {
+ "@value": "blake2b-256",
+ },
+ body: {
+ abstract: {
+ "@value": "Test abstract",
+ },
+ motivation: {
+ "@value": "Test motivation",
+ },
+ rationale: {
+ "@value": "Test rationale",
+ },
+ references: [
+ {
+ "@type": "Other",
+ "CIP108:reference-label": {
+ "@value": "Label",
+ },
+ "CIP108:reference-uri": {
+ "@value": "https://www.google.com/",
+ },
+ },
+ ],
+ title: {
+ "@value": "Test title",
+ },
+ },
+};
+
+const expectedOutput = `
+_:c14n0 "blake2b-256" .
+_:c14n0 _:c14n2 .
+_:c14n1 .
+_:c14n1 "Label" .
+_:c14n1 "https://www.google.com/" .
+_:c14n2 "Test abstract" .
+_:c14n2 "Test motivation" .
+_:c14n2 "Test rationale" .
+_:c14n2 _:c14n1 .
+_:c14n2 "Test title" .
+`
+ .trim()
+ .replace(/\s+\n/g, "\n");
+
+describe("canonizeJSON", () => {
+ it("should correctly canonize a jsonld object to the expected output", async () => {
+ const result = await canonizeJSON(exampleJson);
+ const normalizedResult = result.trim().replace(/\s+\n/g, "\n");
+ expect(normalizedResult).toBe(expectedOutput);
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/checkIsMaintenanceOn.test.ts b/govtool/frontend/src/utils/tests/checkIsMaintenanceOn.test.ts
new file mode 100644
index 000000000..93aff7b68
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/checkIsMaintenanceOn.test.ts
@@ -0,0 +1,46 @@
+import { describe, it, expect, vi, afterEach } from "vitest";
+import axios from "axios";
+import { checkIsMaintenanceOn } from "..";
+
+vi.stubGlobal("location", {
+ ...window.location,
+ reload: vi.fn(),
+});
+
+const axiosGetSpy = vi.spyOn(axios, "get");
+
+describe("checkIsMaintenanceOn function", () => {
+ afterEach(() => {
+ axiosGetSpy.mockClear();
+ vi.resetAllMocks();
+ });
+
+ it("does nothing in development mode", async () => {
+ vi.stubEnv("VITE_IS_DEV", "true");
+ await checkIsMaintenanceOn();
+ expect(axiosGetSpy).not.toHaveBeenCalled();
+ expect(window.location.reload).not.toHaveBeenCalled();
+ });
+
+ it("reloads the page if maintenance mode is active", async () => {
+ vi.stubEnv("VITE_IS_DEV", "");
+ axiosGetSpy.mockResolvedValue({ data: true });
+ await checkIsMaintenanceOn();
+ expect(window.location.reload).toHaveBeenCalled();
+ });
+
+ it("does not reload the page if maintenance mode is not active", async () => {
+ vi.stubEnv("VITE_IS_DEV", "");
+ axiosGetSpy.mockResolvedValue({ data: false });
+ await checkIsMaintenanceOn();
+ expect(window.location.reload).not.toHaveBeenCalled();
+ });
+
+ it("throws an error if the request fails", async () => {
+ vi.stubEnv("VITE_IS_DEV", "");
+ axiosGetSpy.mockRejectedValue(new Error("Network Error"));
+ await expect(checkIsMaintenanceOn()).rejects.toThrow(
+ "Action canceled due to maintenance mode.",
+ );
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/checkIsWalletConnected.test.ts b/govtool/frontend/src/utils/tests/checkIsWalletConnected.test.ts
new file mode 100644
index 000000000..54bb44a4f
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/checkIsWalletConnected.test.ts
@@ -0,0 +1,25 @@
+import { checkIsWalletConnected } from "..";
+import {
+ WALLET_LS_KEY,
+ setItemToLocalStorage,
+ removeItemFromLocalStorage,
+} from "@/utils/localStorage";
+
+describe("checkIsWalletConnected function", () => {
+ it("returns false when wallet information is present in local storage", () => {
+ setItemToLocalStorage(`${WALLET_LS_KEY}_name`, "Nami");
+ setItemToLocalStorage(`${WALLET_LS_KEY}_stake_key`, "teststakekey");
+ const isConnected = checkIsWalletConnected();
+
+ expect(isConnected).toBe(false);
+ });
+
+ it("returns true when wallet information is missing in local storage", () => {
+ removeItemFromLocalStorage(`${WALLET_LS_KEY}_name`);
+ removeItemFromLocalStorage(`${WALLET_LS_KEY}_stake_key`);
+
+ const isConnected = checkIsWalletConnected();
+
+ expect(isConnected).toBe(true);
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/dRep.test.ts b/govtool/frontend/src/utils/tests/dRep.test.ts
new file mode 100644
index 000000000..26558791d
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/dRep.test.ts
@@ -0,0 +1,38 @@
+import { isSameDRep } from "..";
+
+import { DRepStatus } from "@/models";
+
+type TDRepType = "DRep" | "SoleVoter";
+
+const EXAMPLE_DREP = {
+ drepId: "drep123",
+ view: "view123",
+ url: "url",
+ metadataHash: "hash",
+ deposit: 10000,
+ votingPower: 10000,
+ status: DRepStatus.Active,
+ type: "DRep" as TDRepType,
+};
+
+describe("isSameDRep function", () => {
+ it("returns false if dRepIdOrView is undefined", () => {
+ const dRepIdOrView = undefined;
+ expect(isSameDRep(EXAMPLE_DREP, dRepIdOrView)).toBe(false);
+ });
+
+ it("returns true if drepId matches dRepIdOrView", () => {
+ const dRepIdOrView = "drep123";
+ expect(isSameDRep(EXAMPLE_DREP, dRepIdOrView)).toBe(true);
+ });
+
+ it("returns true if view matches dRepIdOrView", () => {
+ const dRepIdOrView = "view123";
+ expect(isSameDRep(EXAMPLE_DREP, dRepIdOrView)).toBe(true);
+ });
+
+ it("returns false if neither drepId nor view matches dRepIdOrView", () => {
+ const dRepIdOrView = "otherId";
+ expect(isSameDRep(EXAMPLE_DREP, dRepIdOrView)).toBe(false);
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/generateAnchor.test.ts b/govtool/frontend/src/utils/tests/generateAnchor.test.ts
new file mode 100644
index 000000000..a0f563112
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/generateAnchor.test.ts
@@ -0,0 +1,28 @@
+import { vi } from "vitest";
+import {
+ Anchor,
+ AnchorDataHash,
+ URL,
+} from "@emurgo/cardano-serialization-lib-asmjs";
+import { generateAnchor } from "..";
+
+describe("generateAnchor function", () => {
+ it("generates an anchor with the provided URL and hash", () => {
+ const url = "https://example.com";
+ const hash = "aabbccddeeff";
+
+ URL.new = vi.fn().mockReturnValueOnce({});
+ AnchorDataHash.from_hex = vi.fn().mockReturnValueOnce({});
+ Anchor.new = vi.fn().mockReturnValueOnce({});
+
+ const spyForAnchor = vi.spyOn(Anchor, "new").mockReturnValue(new Anchor());
+ const anchor = generateAnchor(url, hash);
+
+ expect(URL.new).toHaveBeenCalledWith(url);
+ expect(AnchorDataHash.from_hex).toHaveBeenCalledWith(hash);
+ expect(spyForAnchor).toHaveBeenCalledWith({}, {});
+ expect(anchor).toBeInstanceOf(Anchor);
+
+ spyForAnchor.mockRestore();
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/getDRepID.test.ts b/govtool/frontend/src/utils/tests/getDRepID.test.ts
new file mode 100644
index 000000000..61bc5470c
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/getDRepID.test.ts
@@ -0,0 +1,57 @@
+import { vi } from "vitest";
+import { formHexToBech32, getPubDRepID } from "../getDRepID";
+import { CardanoApiWallet } from "@/models";
+
+const dRepIdHex = "99f2c9a961ff53099796643a514a0640379b706ad310bc751c2997c9";
+const dRepIdBech32 = "drep1n8evn2tplafsn9ukvsa9zjsxgqmekur26vgtcagu9xtujzv2yv8";
+
+describe("formHexToBech32 function", () => {
+ it("returns correct dRep bech32 format", () => {
+ const bech32Format = formHexToBech32(dRepIdHex);
+ expect(bech32Format).toBe(dRepIdBech32);
+ });
+
+ it("expected undefined when no argument", () => {
+ const bech32Format = formHexToBech32();
+ expect(bech32Format).toBe(undefined);
+ });
+});
+
+const mockGetPubDRepKey = vi.fn();
+
+const mockWalletApi = {
+ cip95: {
+ getPubDRepKey: mockGetPubDRepKey,
+ },
+} as unknown as CardanoApiWallet;
+
+describe("getPubDRepID function", () => {
+ beforeEach(() => {
+ vi.clearAllMocks();
+ });
+
+ it("returns the dRepKey, dRepID, and dRepIDBech32 when walletApi returns a valid response", async () => {
+ const dRepKey = "dRepKey123";
+ mockGetPubDRepKey.mockResolvedValueOnce(dRepKey);
+ const result = await getPubDRepID(mockWalletApi);
+ expect(result).toEqual({
+ dRepKey,
+ dRepID: expect.any(String),
+ dRepIDBech32: expect.any(String),
+ });
+ expect(mockGetPubDRepKey).toHaveBeenCalled();
+ });
+
+ it("returns undefined values for dRepKey, dRepID, and dRepIDBech32 when walletApi throws an error", async () => {
+ mockGetPubDRepKey.mockRejectedValueOnce(
+ new Error("Failed to get PubDRepKey"),
+ );
+ const result = await getPubDRepID(mockWalletApi);
+ expect(result).toEqual({
+ dRepKey: undefined,
+ dRepID: undefined,
+ dRepIDBech32: undefined,
+ });
+ expect(mockGetPubDRepKey).toHaveBeenCalled();
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/getMetadataDataMissingStatusTranslation.test.ts b/govtool/frontend/src/utils/tests/getMetadataDataMissingStatusTranslation.test.ts
index 230dedf48..2cfec8d28 100644
--- a/govtool/frontend/src/utils/tests/getMetadataDataMissingStatusTranslation.test.ts
+++ b/govtool/frontend/src/utils/tests/getMetadataDataMissingStatusTranslation.test.ts
@@ -13,7 +13,7 @@ describe("getMetadataDataMissingStatusTranslation", () => {
const translation = getMetadataDataMissingStatusTranslation(
MetadataValidationStatus.INVALID_JSONLD,
);
- expect(translation).toBe("Incorrect Format");
+ expect(translation).toBe("Data Formatted Incorrectly");
});
it("should return the correct translation for INVALID_HASH status", () => {
diff --git a/govtool/frontend/src/utils/tests/localStorage.test.ts b/govtool/frontend/src/utils/tests/localStorage.test.ts
new file mode 100644
index 000000000..8cf7bc8b3
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/localStorage.test.ts
@@ -0,0 +1,27 @@
+import {
+ getItemFromLocalStorage,
+ setItemToLocalStorage,
+ removeItemFromLocalStorage,
+} from "..";
+
+const EXAMPLE_KEY = "example_key";
+const VALUE = "exampleValue";
+
+describe("localStorage util", () => {
+ it("returns correctly value after set item to localstorage", () => {
+ setItemToLocalStorage(EXAMPLE_KEY, VALUE);
+
+ const itemFromStorage = getItemFromLocalStorage(EXAMPLE_KEY);
+
+ expect(itemFromStorage).toBe(VALUE);
+ });
+
+ it("returns null after remove item from localstorage", () => {
+ setItemToLocalStorage(EXAMPLE_KEY, VALUE);
+ removeItemFromLocalStorage(EXAMPLE_KEY);
+
+ const itemFromStorage = getItemFromLocalStorage(EXAMPLE_KEY);
+
+ expect(itemFromStorage).toBe(null);
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/numberValidation.test.ts b/govtool/frontend/src/utils/tests/numberValidation.test.ts
new file mode 100644
index 000000000..4a7e332ec
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/numberValidation.test.ts
@@ -0,0 +1,36 @@
+import i18n from "@/i18n";
+import { numberValidation } from "..";
+
+const positiveResponse = i18n.t(
+ "createGovernanceAction.fields.validations.positive",
+);
+
+const numberResponse = i18n.t(
+ "createGovernanceAction.fields.validations.number",
+);
+
+describe("numberValidation function", () => {
+ it("returns an error message when the input is not a valid number", () => {
+ const invalidInputs = ["abc", "1.2.3", "10,000.50abc", "/"];
+
+ invalidInputs.forEach((input) => {
+ expect(numberValidation(input)).toEqual(numberResponse);
+ });
+ });
+
+ it("returns an error message when the input is negative", () => {
+ const negativeInputs = ["-10", "-1.5", "-5000"];
+
+ negativeInputs.forEach((input) => {
+ expect(numberValidation(input)).toEqual(positiveResponse);
+ });
+ });
+
+ it("returns true when the input is a valid positive number", () => {
+ const validInputs = ["10", "1.5", "5000", "10,5"];
+
+ validInputs.forEach((input) => {
+ expect(numberValidation(input)).toEqual(true);
+ });
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/openInNewTab.test.ts b/govtool/frontend/src/utils/tests/openInNewTab.test.ts
new file mode 100644
index 000000000..ebecfa62c
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/openInNewTab.test.ts
@@ -0,0 +1,33 @@
+import { vi } from "vitest";
+import { openInNewTab } from "..";
+
+describe("openInNewTab function", () => {
+ it("opens a new tab with the provided URL", () => {
+ const originalOpen = window.open;
+ const mockOpen = vi.fn();
+ window.open = mockOpen;
+
+ const url = "https://example.com";
+ openInNewTab(url);
+
+ expect(mockOpen).toHaveBeenCalledWith(url, "_blank", "noopener,noreferrer");
+
+ window.open = originalOpen;
+ });
+
+ it("sets opener to null if new window is opened", () => {
+ const originalOpen = window.open;
+ const mockNewWindow = {
+ opener: "someOpener",
+ };
+ const mockOpen = vi.fn().mockReturnValue(mockNewWindow);
+ window.open = mockOpen;
+
+ const url = "https://example.com";
+ openInNewTab(url);
+
+ expect(mockNewWindow.opener).toBeNull();
+
+ window.open = originalOpen;
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/testIdFromLabel.test.ts b/govtool/frontend/src/utils/tests/testIdFromLabel.test.ts
new file mode 100644
index 000000000..f47e0fa51
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/testIdFromLabel.test.ts
@@ -0,0 +1,57 @@
+import { testIdFromLabel } from "..";
+
+describe("testIdFromLabel function", () => {
+ it("replaces spaces with dashes and converts the label to lowercase", () => {
+ const label = "Hello World";
+ const expectedTestId = "hello-world";
+
+ const result = testIdFromLabel(label);
+
+ expect(result).toEqual(expectedTestId);
+ });
+
+ it("handles labels with multiple spaces", () => {
+ const label = "Multiple Spaces";
+ const expectedTestId = "multiple-----spaces";
+
+ const result = testIdFromLabel(label);
+
+ expect(result).toEqual(expectedTestId);
+ });
+
+ it("handles labels with leading and trailing spaces", () => {
+ const label = " Leading and Trailing Spaces ";
+ const expectedTestId = "leading-and-trailing-spaces";
+
+ const result = testIdFromLabel(label);
+
+ expect(result).toEqual(expectedTestId);
+ });
+
+ it("handles labels with special characters", () => {
+ const label = "!@#$%^&*()";
+ const expectedTestId = "!@#$%^&*()";
+
+ const result = testIdFromLabel(label);
+
+ expect(result).toEqual(expectedTestId);
+ });
+
+ it("handles empty labels", () => {
+ const label = "";
+ const expectedTestId = "";
+
+ const result = testIdFromLabel(label);
+
+ expect(result).toEqual(expectedTestId);
+ });
+
+ it("handles labels with all spaces", () => {
+ const label = " ";
+ const expectedTestId = "";
+
+ const result = testIdFromLabel(label);
+
+ expect(result).toEqual(expectedTestId);
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/validateMetadataHash.test.ts b/govtool/frontend/src/utils/tests/validateMetadataHash.test.ts
new file mode 100644
index 000000000..ea9b1e78e
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/validateMetadataHash.test.ts
@@ -0,0 +1,79 @@
+import { vi } from "vitest";
+import { postValidate } from "@services";
+import { checkIsMissingGAMetadata } from "..";
+import { MetadataStandard, MetadataValidationStatus } from "@/models";
+
+const url = "https://example.com";
+const hash = "abcdefg";
+
+vi.mock("@services");
+
+const mockPostValidate = postValidate as jest.MockedFunction<
+ typeof postValidate
+>;
+
+describe("checkIsMissingGAMetadata", () => {
+ it("returns metadata when there are no issues with the validation", async () => {
+ mockPostValidate.mockResolvedValueOnce({
+ valid: true,
+ metadata: { some: "metadata" },
+ });
+
+ const result = await checkIsMissingGAMetadata({ url, hash });
+
+ expect(result).toStrictEqual({
+ valid: true,
+ metadata: { some: "metadata" },
+ });
+ expect(mockPostValidate).toHaveBeenCalledWith({
+ url,
+ hash,
+ standard: MetadataStandard.CIP108,
+ });
+ });
+
+ it("returns MetadataValidationStatus.INVALID_HASH when postValidate resolves with INVALID_HASH", async () => {
+ mockPostValidate.mockResolvedValueOnce({
+ valid: false,
+ status: MetadataValidationStatus.INVALID_HASH,
+ });
+
+ const result = await checkIsMissingGAMetadata({ url, hash });
+
+ expect(result.status).toBe(MetadataValidationStatus.INVALID_HASH);
+ expect(mockPostValidate).toHaveBeenCalledWith({
+ url,
+ hash,
+ standard: MetadataStandard.CIP108,
+ });
+ });
+
+ it("returns MetadataValidationStatus.INVALID_JSONLD when postValidate resolves with INVALID_JSONLD", async () => {
+ mockPostValidate.mockResolvedValueOnce({
+ valid: false,
+ status: MetadataValidationStatus.INVALID_JSONLD,
+ });
+
+ const result = await checkIsMissingGAMetadata({ url, hash });
+
+ expect(result.status).toBe(MetadataValidationStatus.INVALID_JSONLD);
+ expect(mockPostValidate).toHaveBeenCalledWith({
+ url,
+ hash,
+ standard: MetadataStandard.CIP108,
+ });
+ });
+
+ it("returns MetadataValidationStatus.URL_NOT_FOUND when postValidate throws an error", async () => {
+ mockPostValidate.mockRejectedValueOnce(new Error("404 Not Found"));
+
+ const result = await checkIsMissingGAMetadata({ url, hash });
+
+ expect(result.status).toBe(MetadataValidationStatus.URL_NOT_FOUND);
+ expect(mockPostValidate).toHaveBeenCalledWith({
+ url,
+ hash,
+ standard: MetadataStandard.CIP108,
+ });
+ });
+});
diff --git a/govtool/frontend/src/utils/tests/wait.test.ts b/govtool/frontend/src/utils/tests/wait.test.ts
new file mode 100644
index 000000000..1949f11c1
--- /dev/null
+++ b/govtool/frontend/src/utils/tests/wait.test.ts
@@ -0,0 +1,28 @@
+import { wait } from "..";
+
+describe("wait function", () => {
+ it("resolves after the specified time", async () => {
+ const startTime = Date.now();
+ const waitTime = 2000;
+
+ await wait(waitTime);
+
+ const endTime = Date.now();
+ const elapsedTime = endTime - startTime;
+
+ expect(elapsedTime).toBeGreaterThanOrEqual(waitTime - 100);
+ expect(elapsedTime).toBeLessThanOrEqual(waitTime + 100);
+ });
+
+ it("resolves after the default time if no time is specified", async () => {
+ const startTime = Date.now();
+
+ await wait();
+
+ const endTime = Date.now();
+ const elapsedTime = endTime - startTime;
+
+ expect(elapsedTime).toBeGreaterThanOrEqual(4900);
+ expect(elapsedTime).toBeLessThanOrEqual(5100);
+ });
+});
diff --git a/govtool/frontend/src/utils/validateMetadataHash.ts b/govtool/frontend/src/utils/validateMetadataHash.ts
index d9eb0e1e8..6868cc3d6 100644
--- a/govtool/frontend/src/utils/validateMetadataHash.ts
+++ b/govtool/frontend/src/utils/validateMetadataHash.ts
@@ -2,24 +2,31 @@ import { postValidate } from "@services";
import { MetadataStandard, MetadataValidationStatus } from "@/models";
+type CheckIsMissingGAMetadataResponse = {
+ status?: MetadataValidationStatus;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ metadata?: any;
+ valid: boolean;
+};
+
export const checkIsMissingGAMetadata = async ({
url,
hash,
}: {
url: string;
hash: string;
-}): Promise => {
+}): Promise => {
try {
- const { status } = await postValidate({
+ const { status, metadata, valid } = await postValidate({
url,
hash,
standard: MetadataStandard.CIP108,
});
if (status) {
- return status;
+ return { status, valid };
}
- return false;
+ return { metadata, valid };
} catch (error) {
- return MetadataValidationStatus.URL_NOT_FOUND;
+ return { status: MetadataValidationStatus.URL_NOT_FOUND, valid: false };
}
};
diff --git a/govtool/frontend/yarn.lock b/govtool/frontend/yarn.lock
index bc05cc41f..d32bcde1a 100644
--- a/govtool/frontend/yarn.lock
+++ b/govtool/frontend/yarn.lock
@@ -7415,11 +7415,6 @@ isobject@^3.0.1:
resolved "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz"
integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
-istanbul-badges-readme@^1.8.5:
- version "1.8.5"
- resolved "https://registry.npmjs.org/istanbul-badges-readme/-/istanbul-badges-readme-1.8.5.tgz"
- integrity sha512-VEh90ofuufPZIbLeDF2g14wpe0sebhirG0xHooYKpNPYOkGXm6y+HJFotQqIzCg0NmbCnlKnOPL1B2oxG7/piA==
-
istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0, istanbul-lib-coverage@^3.2.2:
version "3.2.2"
resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz"
@@ -10358,7 +10353,16 @@ string-length@^5.0.1:
char-regex "^2.0.0"
strip-ansi "^7.0.1"
-"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
+"string-width-cjs@npm:string-width@^4.2.0":
+ version "4.2.3"
+ resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
+ integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+ dependencies:
+ emoji-regex "^8.0.0"
+ is-fullwidth-code-point "^3.0.0"
+ strip-ansi "^6.0.1"
+
+string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz"
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
@@ -10436,7 +10440,14 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
-"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
+ version "6.0.1"
+ resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
+ integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+ dependencies:
+ ansi-regex "^5.0.1"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
@@ -11349,7 +11360,7 @@ wordwrap@^1.0.0:
resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==
-"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0:
+"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0":
version "7.0.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
@@ -11367,6 +11378,15 @@ wrap-ansi@^6.2.0:
string-width "^4.1.0"
strip-ansi "^6.0.0"
+wrap-ansi@^7.0.0:
+ version "7.0.0"
+ resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz"
+ integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrap-ansi@^8.1.0:
version "8.1.0"
resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"
diff --git a/govtool/metadata-validation/Makefile b/govtool/metadata-validation/Makefile
new file mode 100644
index 000000000..10b0379db
--- /dev/null
+++ b/govtool/metadata-validation/Makefile
@@ -0,0 +1,21 @@
+common_mk := ../../scripts/govtool/common.mk
+ifeq ($(origin $(common_mk)), undefined)
+ $(eval $(common_mk) := included)
+ include $(common_mk)
+endif
+
+.DEFAULT_GOAL := push-metadata-validation
+
+# image tags
+metadata_validation_image_tag := $(shell git log -n 1 --format="%H" -- $(root_dir)/govtool/metadata-validation)
+
+.PHONY: build-metadata-validation
+build-metadata-validation: docker-login
+ $(call check_image_on_ecr,metadata-validation,$(metadata_validation_image_tag)) || \
+ $(docker) build --tag "$(repo_url)/metadata-validation:$(metadata_validation_image_tag)" \
+ $(root_dir)/govtool/metadata-validation
+
+.PHONY: push-metadata-validation
+push-metadata-validation: build-metadata-validation
+ $(call check_image_on_ecr,metadata-validation,$(metadata_validation_image_tag)) || \
+ $(docker) push $(repo_url)/metadata-validation:$(metadata_validation_image_tag)
diff --git a/govtool/metadata-validation/src/app.service.ts b/govtool/metadata-validation/src/app.service.ts
index c29cf16fa..fb31dd4fd 100644
--- a/govtool/metadata-validation/src/app.service.ts
+++ b/govtool/metadata-validation/src/app.service.ts
@@ -5,7 +5,7 @@ import * as blake from 'blakejs';
import { ValidateMetadataDTO } from '@dto';
import { MetadataValidationStatus } from '@enums';
-import { canonizeJSON, validateMetadataStandard } from '@utils';
+import { canonizeJSON, validateMetadataStandard, parseMetadata } from '@utils';
import { ValidateMetadataResult } from '@types';
@Injectable()
@@ -18,6 +18,7 @@ export class AppService {
standard,
}: ValidateMetadataDTO): Promise {
let status: MetadataValidationStatus;
+ let metadata: any;
try {
const { data } = await firstValueFrom(
this.httpService.get(url).pipe(
@@ -31,6 +32,8 @@ export class AppService {
await validateMetadataStandard(data, standard);
}
+ metadata = parseMetadata(data.body, standard);
+
let canonizedMetadata;
try {
canonizedMetadata = await canonizeJSON(data);
@@ -48,6 +51,6 @@ export class AppService {
}
}
- return { status, valid: !Boolean(status) };
+ return { status, valid: !Boolean(status), metadata };
}
}
diff --git a/govtool/metadata-validation/src/schemas/cipStandardSchema.ts b/govtool/metadata-validation/src/schemas/cipStandardSchema.ts
index 913b9e952..0551ad172 100644
--- a/govtool/metadata-validation/src/schemas/cipStandardSchema.ts
+++ b/govtool/metadata-validation/src/schemas/cipStandardSchema.ts
@@ -32,9 +32,13 @@ export const cipStandardSchema: StandardSpecification = {
references: Joi.array().items(
Joi.object({
'@type': Joi.string(),
- label: Joi.object({ '@value': Joi.string().required() }),
- uri: Joi.object({ '@value': Joi.string().uri().required() }),
- referenceHash: Joi.object({
+ 'CIP108:reference-label': Joi.object({
+ '@value': Joi.string().required(),
+ }),
+ 'CIP108:reference-uri': Joi.object({
+ '@value': Joi.string().uri().required(),
+ }),
+ 'CIP108:reference-hash': Joi.object({
hashDigest: Joi.string().required(),
hashAlgorithm: Joi.string().required(),
}),
diff --git a/govtool/metadata-validation/src/types/validateMetadata.ts b/govtool/metadata-validation/src/types/validateMetadata.ts
index dcdf28363..fa20f24fe 100644
--- a/govtool/metadata-validation/src/types/validateMetadata.ts
+++ b/govtool/metadata-validation/src/types/validateMetadata.ts
@@ -7,4 +7,5 @@ export enum MetadataStandard {
export type ValidateMetadataResult = {
status?: MetadataValidationStatus;
valid: boolean;
+ metadata: any;
};
diff --git a/govtool/metadata-validation/src/utils/index.ts b/govtool/metadata-validation/src/utils/index.ts
index 648150db2..a074ea4fe 100644
--- a/govtool/metadata-validation/src/utils/index.ts
+++ b/govtool/metadata-validation/src/utils/index.ts
@@ -1,2 +1,3 @@
export * from './canonizeJSON';
export * from './validateMetadataStandard';
+export * from './parseMetadata';
diff --git a/govtool/metadata-validation/src/utils/parseMetadata.ts b/govtool/metadata-validation/src/utils/parseMetadata.ts
new file mode 100644
index 000000000..ee6c62fd0
--- /dev/null
+++ b/govtool/metadata-validation/src/utils/parseMetadata.ts
@@ -0,0 +1,23 @@
+import { MetadataStandard } from '@/types';
+
+const CIP_108_VALUE_KEYS = ['abstract', 'motivation', 'rationale', 'title'];
+export const parseMetadata = (metadata: any, standard: MetadataStandard) => {
+ const parsedMetadata = {};
+ switch (standard) {
+ case MetadataStandard.CIP108:
+ for (const [key, value] of Object.entries(metadata)) {
+ if (CIP_108_VALUE_KEYS.includes(key)) {
+ parsedMetadata[key] = value['@value'];
+ }
+
+ if (key === 'references') {
+ parsedMetadata[key] = (Array.isArray(value) ? value : [])?.map(
+ (reference) => reference['CIP108:reference-uri']['@value'],
+ );
+ }
+ }
+ return parsedMetadata;
+ default:
+ return;
+ }
+};
diff --git a/govtool/status-service/Makefile b/govtool/status-service/Makefile
new file mode 100644
index 000000000..07d975981
--- /dev/null
+++ b/govtool/status-service/Makefile
@@ -0,0 +1,21 @@
+common_mk := ../../scripts/govtool/common.mk
+ifeq ($(origin $(common_mk)), undefined)
+ $(eval $(common_mk) := included)
+ include $(common_mk)
+endif
+
+.DEFAULT_GOAL := push-status-service
+
+# image tags
+status_service_image_tag := $(shell git log -n 1 --format="%H" -- $(root_dir)/govtool/status-service)
+
+.PHONY: build-status-service
+build-status-service: docker-login
+ $(call check_image_on_ecr,status-service,$(status_service_image_tag)) || \
+ $(docker) build --tag "$(repo_url)/status-service:$(status_service_image_tag)" \
+ $(root_dir)/govtool/status-service
+
+.PHONY: push-status-service
+push-status-service: build-status-service
+ $(call check_image_on_ecr,status-service,$(status_service_image_tag)) || \
+ $(docker) push $(repo_url)/status-service:$(status_service_image_tag)
diff --git a/infra/terraform/main.tf b/infra/terraform/main.tf
index 27000cffd..18e24437e 100644
--- a/infra/terraform/main.tf
+++ b/infra/terraform/main.tf
@@ -26,6 +26,16 @@ module "govtool-ecr-frontend" {
repo_name = "frontend"
}
+module "govtool-ecr-status-service" {
+ source = "./modules/ecr"
+ repo_name = "status-service"
+}
+
+module "govtool-ecr-metadata-validation" {
+ source = "./modules/ecr"
+ repo_name = "metadata-validation"
+}
+
resource "aws_iam_policy" "cicd_ecr" {
name = "CICD_ECR"
policy = jsonencode({
@@ -39,7 +49,9 @@ resource "aws_iam_policy" "cicd_ecr" {
Resource = [
module.govtool-ecr-backend.repo_arn,
module.govtool-ecr-backend-base.repo_arn,
- module.govtool-ecr-frontend.repo_arn
+ module.govtool-ecr-frontend.repo_arn,
+ module.govtool-ecr-status-service.repo_arn,
+ module.govtool-ecr-metadata-validation.repo_arn
]
},
{
@@ -105,6 +117,14 @@ output "govtool-ecr-frontend-url" {
value = module.govtool-ecr-frontend.repo_url
}
+output "govtool-ecr-status-service-url" {
+ value = module.govtool-ecr-status-service.repo_url
+}
+
+output "govtool-ecr-metadata-validation-url" {
+ value = module.govtool-ecr-metadata-validation.repo_url
+}
+
output "govtool-dev-sanchonet-frontend-domain" {
value = module.govtool-dev-sanchonet.frontend_domain
}
diff --git a/infra/terraform/modules/govtool-ec2/main.tf b/infra/terraform/modules/govtool-ec2/main.tf
index 40ad6f6cd..bddf37ce7 100644
--- a/infra/terraform/modules/govtool-ec2/main.tf
+++ b/infra/terraform/modules/govtool-ec2/main.tf
@@ -193,3 +193,12 @@ resource "aws_route53_record" "frontend" {
ttl = 180
records = [aws_eip.govtool.public_ip]
}
+
+resource "aws_route53_record" "participation" {
+ count = var.app_env == "beta" ? 0 : 1
+ zone_id = var.dns_zone_id
+ name = "${var.custom_subdomain != "" ? "participation.${var.custom_subdomain}" : "participation.${var.app_env}-${var.cardano_network}"}"
+ type = "A"
+ ttl = 180
+ records = [aws_eip.govtool.public_ip]
+}
\ No newline at end of file
diff --git a/scripts/govtool/Makefile b/scripts/govtool/Makefile
index b2f849b05..281de5f36 100644
--- a/scripts/govtool/Makefile
+++ b/scripts/govtool/Makefile
@@ -1,5 +1,7 @@
include ../../govtool/backend/Makefile
include ../../govtool/frontend/Makefile
+include ../../govtool/status-service/Makefile
+include ../../govtool/metadata-validation/Makefile
include utils.mk
include info.mk
include config.mk
@@ -14,7 +16,7 @@ cardano_db_sync_image_tag := sancho-4-2-1
all: deploy-stack notify
.PHONY: deploy-stack
-deploy-stack: upload-config push-backend push-frontend
+deploy-stack: upload-config push-backend push-frontend push-status-service push-metadata-validation
@:$(call check_defined, cardano_network)
@:$(call check_defined, env)
export CARDANO_NETWORK=$(cardano_network); \
@@ -23,6 +25,8 @@ deploy-stack: upload-config push-backend push-frontend
export GRAFANA_ADMIN_PASSWORD=$${GRAFANA_ADMIN_PASSWORD}; \
export BACKEND_TAG=$(backend_image_tag); \
export FRONTEND_TAG=$(frontend_image_tag); \
+ export STATUS_SERVICE_TAG=$(status_service_image_tag); \
+ export METADATA_VALIDATION_TAG=$(metadata_validation_image_tag); \
export CARDANO_NODE_TAG=$(cardano_node_image_tag); \
export CARDANO_DB_SYNC_TAG=$(cardano_db_sync_image_tag); \
$(ssh-keyscan) $(docker_host) 2>/dev/null >> ~/.ssh/known_hosts; \
@@ -37,6 +41,8 @@ destroy-cardano-node-and-dbsync: prepare-config
export ENVIRONMENT=$(env); \
export BACKEND_TAG=$(backend_image_tag); \
export FRONTEND_TAG=$(frontend_image_tag); \
+ export STATUS_SERVICE_TAG=$(status_service_image_tag); \
+ export METADATA_VALIDATION_TAG=$(metadata_validation_image_tag); \
export CARDANO_NODE_TAG=$(cardano_node_image_tag); \
export CARDANO_DB_SYNC_TAG=$(cardano_db_sync_image_tag); \
$(ssh-keyscan) $(docker_host) 2>/dev/null >> ~/.ssh/known_hosts; \
@@ -56,6 +62,8 @@ toggle-maintenance: docker-login prepare-config
export DOCKER_HOST=ssh://$(ssh_url); \
export BACKEND_TAG=$(backend_image_tag); \
export FRONTEND_TAG=$(frontend_image_tag); \
+ export STATUS_SERVICE_TAG=$(status_service_image_tag); \
+ export METADATA_VALIDATION_TAG=$(metadata_validation_image_tag); \
export CARDANO_NODE_TAG=$(cardano_node_image_tag); \
export CARDANO_DB_SYNC_TAG=$(cardano_db_sync_image_tag); \
$(ssh-keyscan) $(docker_host) 2>/dev/null >> ~/.ssh/known_hosts; \
diff --git a/scripts/govtool/config/templates/docker-compose.yml.tpl b/scripts/govtool/config/templates/docker-compose.yml.tpl
index 789c66101..742d0f970 100644
--- a/scripts/govtool/config/templates/docker-compose.yml.tpl
+++ b/scripts/govtool/config/templates/docker-compose.yml.tpl
@@ -168,8 +168,7 @@ services:
logging: *logging
status-service:
- build:
- context: ../../govtool/status-service
+ image: /status-service:${STATUS_SERVICE_TAG}
environment:
- GRAFANA_USERNAME=admin
- GRAFANA_PASSWORD=${GRAFANA_ADMIN_PASSWORD}
@@ -183,8 +182,7 @@ services:
- "traefik.http.services.status-service.loadbalancer.server.port=8000"
metadata-validation:
- build:
- context: ../../govtool/metadata-validation
+ image: /metadata-validation:${METADATA_VALIDATION_TAG}
environment:
- PORT=3000
logging: *logging
diff --git a/scripts/govtool/info.mk b/scripts/govtool/info.mk
index 128567307..fdf8ed83e 100644
--- a/scripts/govtool/info.mk
+++ b/scripts/govtool/info.mk
@@ -22,10 +22,12 @@ info:
@echo "| TIME $(shell date +'%Y-%m-%d %H:%M:%S%z')"
@echo "| BRANCH $(branch) [$(commit)]"
@echo "| ENV $(env)"
- @echo "I NETWORK $(cardano_network)"
- @echo "N BACKEND $(repo_url)/backend:$(backend_image_tag)"
- @echo "F FRONTEND $(repo_url)/frontend:$(frontend_image_tag)"
- @echo "O NODE ghcr.io/intersectmbo/cardano-node:$(cardano_node_image_tag)"
+ @echo "| NETWORK $(cardano_network)"
+ @echo "| BACKEND $(repo_url)/backend:$(backend_image_tag)"
+ @echo "| FRONTEND $(repo_url)/frontend:$(frontend_image_tag)"
+ @echo "| STATUS $(repo_url)/status-service:$(status_service_image_tag)"
+ @echo "| METADATA $(repo_url)/metadata-validation:$(metadata_validation_image_tag)"
+ @echo "| NODE ghcr.io/intersectmbo/cardano-node:$(cardano_node_image_tag)"
@echo "| DBSYNC ghcr.io/intersectmbo/cardano-db-sync:$(cardano_db_sync_image_tag)"
@echo "| SSH $(ssh_url)"
@echo "| URL https://$(docker_host)"
@@ -38,4 +40,4 @@ notify: info log-deployment
$(curl) -X POST https://slack.com/api/chat.postMessage\
-H "Authorization: Bearer $${GRAFANA_SLACK_OAUTH_TOKEN}" \
-H "Content-Type: application/json; charset=utf-8" \
- --data "{ \"channel\":\"$${GRAFANA_SLACK_RECIPIENT}\", \"text\":\":rocket: *Deploy performed on \`$(env)\`*\n- from *branch* \`$(branch)\` (\`$(commit)\`),\n- using *Cardano Node* version \`$(cardano_node_image_tag)\`,\n- using *Cardano DB Sync* version \`$(cardano_db_sync_image_tag)\`,\n- using *GovTool backend* version \`$(backend_image_tag)\`,\n- using *Govtool frontend* version \`$(frontend_image_tag)\`.\n$(pipeline_info)\" }"
+ --data "{ \"channel\":\"$${GRAFANA_SLACK_RECIPIENT}\", \"text\":\":rocket: *Deploy performed on \`$(env)\`*\n- from *branch* \`$(branch)\` (\`$(commit)\`),\n- using *Cardano Node* version \`$(cardano_node_image_tag)\`,\n- using *Cardano DB Sync* version \`$(cardano_db_sync_image_tag)\`,\n- using *GovTool backend* version \`$(backend_image_tag)\`,\n- using *Govtool frontend* version \`$(frontend_image_tag)\`,\n- using *Govtool status-service* version \`$(status_service_image_tag)\`,\n- using *Govtool metadata-validation* version \`$(metadata_validation_image_tag)\`.\n$(pipeline_info)\" }"
diff --git a/scripts/govtool/utils.mk b/scripts/govtool/utils.mk
index b69671472..45d4e0041 100644
--- a/scripts/govtool/utils.mk
+++ b/scripts/govtool/utils.mk
@@ -14,6 +14,8 @@ docker-compose:
export GRAFANA_ADMIN_PASSWORD=$${GRAFANA_ADMIN_PASSWORD}; \
export BACKEND_TAG=$(backend_image_tag); \
export FRONTEND_TAG=$(frontend_image_tag); \
+ export STATUS_SERVICE_TAG=$(status_service_image_tag); \
+ export METADATA_VALIDATION_TAG=$(metadata_validation_image_tag); \
export CARDANO_NODE_TAG=$(cardano_node_image_tag); \
export CARDANO_DB_SYNC_TAG=$(cardano_db_sync_image_tag); \
$(ssh-keyscan) $(docker_host) 2>/dev/null >> ~/.ssh/known_hosts; \