diff --git a/.github/scripts/register_report.sh b/.github/scripts/register_report.sh
index f5e5756f9..959368afb 100644
--- a/.github/scripts/register_report.sh
+++ b/.github/scripts/register_report.sh
@@ -10,6 +10,6 @@ if grep -q "$REPORT_NAME" "$PROJECT_DIR/$PROJECT_FILE"; then
echo "Project already exists"
echo "project_exists=true">> $GITHUB_OUTPUT
else
- echo "$REPORT_NAME" >> "$PROJECT_DIR/$PROJECT_FILE"
+ echo "\n$REPORT_NAME" >> "$PROJECT_DIR/$PROJECT_FILE"
echo "project_exists=false">> $GITHUB_OUTPUT
fi
diff --git a/.github/scripts/remove_oldest_report.sh b/.github/scripts/remove_oldest_report.sh
index 566aed1c7..e0a809604 100644
--- a/.github/scripts/remove_oldest_report.sh
+++ b/.github/scripts/remove_oldest_report.sh
@@ -2,14 +2,21 @@
if [ -d "gh-pages/$REPORT_NAME" ]; then
cd gh-pages/$REPORT_NAME
- # Find the oldest numerical directory
- oldest_dir=$(find . -maxdepth 1 -type d -regex './[0-9]+' | sort -n | head -1)
- if [ -n "$oldest_dir" ]; then
- echo "Removing oldest report directory: $oldest_dir"
- rm -rf "$oldest_dir"
+
+ # Count the number of numerical directories
+ dir_count=$(find . -maxdepth 1 -type d -regex './[0-9]+' | wc -l)
+
+ if [ "$dir_count" -gt 10 ]; then
+ # Find the oldest numerical directory
+ oldest_dir=$(find . -maxdepth 1 -type d -regex './[0-9]+' | sort -V | head -1)
+ if [ -n "$oldest_dir" ]; then
+ echo "More than 10 report directories exist. Removing oldest: $oldest_dir"
+ rm -rf "$oldest_dir"
+ fi
else
- echo "No report directories found to remove"
+ echo "Only $dir_count report directories exist (threshold: 10). Nothing to remove."
fi
+
cd ../../
else
echo "Report directory does not exist yet"
diff --git a/.github/workflows/test_backend.yml b/.github/workflows/test_backend.yml
index accc4f99f..74b9ca195 100644
--- a/.github/workflows/test_backend.yml
+++ b/.github/workflows/test_backend.yml
@@ -16,6 +16,7 @@ on:
- "sanchogov.tools/api"
- "staging.govtool.byron.network/api"
- "govtool.cardanoapi.io/api"
+ - "be.preview.gov.tools"
- "z6b8d2f7a-zca4a4c45-gtw.z937eb260.rustrocks.fr"
- "z78acf3c2-z5575152b-gtw.z937eb260.rustrocks.fr"
- "be.gov.tools"
@@ -28,6 +29,7 @@ on:
- "preview"
- "mainnet"
- "preprod"
+
workflow_run:
workflows: ["Build and deploy GovTool test stack"]
types: [completed]
@@ -35,6 +37,9 @@ on:
- test
- infra/test-chores
+ schedule:
+ - cron: "0 0 * * *" # 12AM UTC
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
@@ -42,7 +47,7 @@ concurrency:
jobs:
backend-tests:
runs-on: ubuntu-latest
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' || github.event.schedule }}
outputs:
start_time: ${{ steps.set-pending-status.outputs.timestamp }}
status: ${{ steps.run-tests.outcome }}
@@ -115,7 +120,7 @@ jobs:
path: gh-pages
repository: ${{vars.GH_PAGES}}
ssh-key: ${{ secrets.DEPLOY_KEY }}
-
+
- name: Remove oldest report to save space
if: ${{success()}}
run: |
@@ -128,6 +133,7 @@ jobs:
run: |
chmod +x .github/scripts/register_report.sh
.github/scripts/register_report.sh
+
- if: steps.register-project.outputs.project_exists != 'true'
uses: JamesIves/github-pages-deploy-action@v4
with:
@@ -191,7 +197,7 @@ jobs:
GITHUB_TOKEN: ${{ github.token }}
env:
- BASE_URL: https://${{inputs.deployment || 'govtool.cardanoapi.io/api' }}
- REPORT_NAME: govtool-backend
+ BASE_URL: https://${{github.event.schedule && 'be.preview.gov.tools' || inputs.deployment || 'govtool.cardanoapi.io/api' }}
+ REPORT_NAME: ${{ github.event.schedule && 'nightly-'}}govtool-backend
GH_PAGES: ${{vars.GH_PAGES}}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
diff --git a/.github/workflows/test_integration_playwright.yml b/.github/workflows/test_integration_playwright.yml
index 030c25225..841357529 100644
--- a/.github/workflows/test_integration_playwright.yml
+++ b/.github/workflows/test_integration_playwright.yml
@@ -37,6 +37,9 @@ on:
- test
- infra/test-chores
+ schedule:
+ - cron: "0 0 * * *" # 12AM UTC
+
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: false
@@ -44,7 +47,7 @@ concurrency:
jobs:
integration-tests:
runs-on: ubuntu-latest
- if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' }}
+ if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'success' || github.event.schedule }}
outputs:
start_time: ${{ steps.set-pending-status.outputs.timestamp }}
status: ${{ steps.run-test.outcome }}
@@ -103,6 +106,11 @@ jobs:
export BLOCKFROST_API_KEY="${{ secrets.BLOCKFROST_API_KEY_MAINNET }}"
fi
+ # Set schedule workflow variable
+ if [[ "$GITHUB_EVENT_NAME" == "schedule" ]]; then
+ export SCHEDULED_WORKFLOW="true"
+ fi
+
npm run test:headless
- name: Upload report
@@ -165,7 +173,7 @@ jobs:
run: |
chmod +x .github/scripts/register_report.sh
.github/scripts/register_report.sh
-
+
- if: steps.register-project.outputs.project_exists != 'true'
uses: JamesIves/github-pages-deploy-action@v4
with:
@@ -229,7 +237,7 @@ jobs:
REPORT_NUMBER: ${{ needs.publish-report.outputs.report_number }}
GITHUB_TOKEN: ${{ github.token }}
env:
- HOST_URL: https://${{inputs.deployment || 'govtool.cardanoapi.io' }}
- REPORT_NAME: govtool-frontend
+ HOST_URL: https://${{ github.event.schedule && 'preview.gov.tools' || (inputs.deployment || 'govtool.cardanoapi.io') }}
+ REPORT_NAME: ${{ github.event.schedule && 'nightly-'}}govtool-frontend
GH_PAGES: ${{vars.GH_PAGES}}
COMMIT_SHA: ${{ github.event.workflow_run.head_sha || github.sha }}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2515fbe34..145af887e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,8 @@ changes.
### Added
+- Add maintenance ending banner [Issue 3647](https://github.com/IntersectMBO/govtool/issues/3647)
+
### Fixed
### Changed
@@ -20,7 +22,6 @@ changes.
## [v2.0.22](https://github.com/IntersectMBO/govtool/releases/tag/v2.0.22) 2025-05-15
-
### Added
### Fixed
diff --git a/govtool/frontend/src/components/organisms/MaintenanceEndingBanner.tsx b/govtool/frontend/src/components/organisms/MaintenanceEndingBanner.tsx
new file mode 100644
index 000000000..37d207e72
--- /dev/null
+++ b/govtool/frontend/src/components/organisms/MaintenanceEndingBanner.tsx
@@ -0,0 +1,106 @@
+import { Box, Typography, IconButton } from "@mui/material";
+import { useState } from "react";
+import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
+import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
+import { Trans, useTranslation } from "react-i18next";
+
+export const MaintenanceEndingBanner = () => {
+ const [isExpanded, setIsExpanded] = useState(true);
+ const { t } = useTranslation();
+
+ const handleToggle = () => {
+ setIsExpanded((prev) => !prev);
+ };
+
+ return (
+
+ {/* Banner Header */}
+
+
+
+ {t("system.maintenanceEnding.title")}
+
+
+
+
+ {isExpanded ? : }
+
+
+
+
+ {/* Expandable Content */}
+
+
+
+ {t("system.maintenanceEnding.description1")}
+
+
+ ,
+ ]}
+ />
+
+
+ {t("system.maintenanceEnding.description3")}
+
+
+
+
+ );
+};
diff --git a/govtool/frontend/src/components/organisms/TopBanners.tsx b/govtool/frontend/src/components/organisms/TopBanners.tsx
index e27b8138d..f4cd741b8 100644
--- a/govtool/frontend/src/components/organisms/TopBanners.tsx
+++ b/govtool/frontend/src/components/organisms/TopBanners.tsx
@@ -1,11 +1,13 @@
import { Box, Link, Typography } from "@mui/material";
import { Trans, useTranslation } from "react-i18next";
-import { useAppContext } from "@/context";
+import { useAppContext, useCardano } from "@/context";
import { LINKS } from "@/consts/links";
+import { MaintenanceEndingBanner } from "./MaintenanceEndingBanner";
export const TopBanners = () => {
const { isMainnet, networkName, isInBootstrapPhase, isAppInitializing } =
useAppContext();
+ const { isEnabled } = useCardano();
const { t } = useTranslation();
if (isAppInitializing) {
@@ -13,65 +15,83 @@ export const TopBanners = () => {
}
return (
-
- {/* NETWORK BANNER */}
- {!isMainnet && (
-
-
- {`${t("network")}: ${networkName}`}
-
-
- |
-
-
+
+ {/* NETWORK BANNER */}
+ {!isMainnet && (
+
- {t("goToMainnet")}
-
-
- )}
+
+ {`${t("network")}: ${networkName}`}
+
+
+ |
+
+
+ {t("goToMainnet")}
+
+
+ )}
+
- {/* BOOTSTRAPPING BANNER */}
- {isInBootstrapPhase && (
+ {/* GOVTOOL MAINTENANCE ENDING SOON BANNER */}
+ {isEnabled && (
-
-
- ),
- }}
- />
-
+
)}
-
+
+ {/* BOOTSTRAPPING BANNER */}
+
+ {isInBootstrapPhase && (
+
+
+
+ ),
+ }}
+ />
+
+
+ )}
+
+ >
);
};
diff --git a/govtool/frontend/src/components/organisms/TopNav.tsx b/govtool/frontend/src/components/organisms/TopNav.tsx
index 5a85776cf..5eb2654c5 100644
--- a/govtool/frontend/src/components/organisms/TopNav.tsx
+++ b/govtool/frontend/src/components/organisms/TopNav.tsx
@@ -8,8 +8,9 @@ import { useCardano, useFeatureFlag, useModal } from "@context";
import { useScreenDimension, useTranslation } from "@hooks";
import { openInNewTab } from "@utils";
import { DrawerMobile } from "./DrawerMobile";
+import { MaintenanceEndingBanner } from "./MaintenanceEndingBanner";
-const POSITION_TO_BLUR = 50;
+const POSITION_TO_BLUR = 80;
export const TopNav = ({ isConnectButton = true }) => {
const {
@@ -136,51 +137,54 @@ export const TopNav = ({ isConnectButton = true }) => {
);
return (
-
-
+
+
+
- isConnectButton || disconnectWallet()}
+
-
-
- {screenWidth >= 1145 ? renderDesktopNav() : renderMobileNav()}
-
-
+ isConnectButton || disconnectWallet()}
+ >
+
+
+ {screenWidth >= 1145 ? renderDesktopNav() : renderMobileNav()}
+
+
+
);
};
diff --git a/govtool/frontend/src/i18n/locales/en.json b/govtool/frontend/src/i18n/locales/en.json
index d1d56337d..d057b97cb 100644
--- a/govtool/frontend/src/i18n/locales/en.json
+++ b/govtool/frontend/src/i18n/locales/en.json
@@ -738,7 +738,13 @@
"system": {
"description": "The Cardano GovTool is a tool that allows you to participate in the governance of the Cardano network. You can propose, vote on, and delegate your voting power to other users.",
"title": "This tool is connected to {{networkName}}",
- "bootstrappingWarning": "Govtool is in the Bootstrapping phase. Some features are not available. Learn more"
+ "bootstrappingWarning": "Govtool is in the Bootstrapping phase. Some features are not available. Learn more",
+ "maintenanceEnding": {
+ "title": "ā ļø GovTool Maintenance Ending Soon",
+ "description1": "GovTool has not been included in the 2025 Cardano budget.",
+ "description2": "Unless alternative support is secured, <0>maintenance will end in June 2025.0>",
+ "description3": "We remain committed to transparency and will keep you updated."
+ }
},
"tooltips": {
"delegateTodRep": {
diff --git a/tests/govtool-frontend/playwright/.env.example b/tests/govtool-frontend/playwright/.env.example
index 18caf343a..c68929567 100644
--- a/tests/govtool-frontend/playwright/.env.example
+++ b/tests/govtool-frontend/playwright/.env.example
@@ -18,7 +18,7 @@ NETWORK=preview
FAUCET_ADDRESS=
FAUCET_PAYMENT_PRIVATE=
-FAUCET_STAKE_PKH=
+FAUCET_STAKE_PRIVATE=
CI=true
TEST_WORKERS=6 // Number of workers to run in parallel
\ No newline at end of file
diff --git a/tests/govtool-frontend/playwright/README.md b/tests/govtool-frontend/playwright/README.md
index deb18fae4..43e9515ab 100644
--- a/tests/govtool-frontend/playwright/README.md
+++ b/tests/govtool-frontend/playwright/README.md
@@ -106,13 +106,13 @@ The script will:
š Wallet generated successfully!
-----------------------------------
š Payment Private Key:
-š Stake Public Key Hash:
+š° Stake Private Key:
š Wallet Address:
-----------------------------------
š Please copy the following to your environment variables:
1. Set FAUCET_PAYMENT_PRIVATE=
-2. Set FAUCET_STAKE_PKH=
+2. Set FAUCET_STAKE_PRIVATE=
3. Set FAUCET_ADDRESS=
š All set! Please ensure this wallet is funded with a sufficient balance
diff --git a/tests/govtool-frontend/playwright/generate_faucet_wallet.ts b/tests/govtool-frontend/playwright/generate_faucet_wallet.ts
index c61b1137b..4d4fc2d9d 100644
--- a/tests/govtool-frontend/playwright/generate_faucet_wallet.ts
+++ b/tests/govtool-frontend/playwright/generate_faucet_wallet.ts
@@ -11,7 +11,7 @@ import { ShelleyWallet } from "./lib/helpers/crypto";
console.log("-----------------------------------");
console.log("š¼ Wallet:", walletJson);
console.log(`\nš Payment Private Key: ${walletJson.payment.private}`);
- console.log(`š Stake Public Key Hash: ${walletJson.stake.pkh}`);
+ console.log(`š° Stake Private Key: ${walletJson.stake.private}`);
console.log(`š Wallet Address: ${walletJson.address}`);
console.log("-----------------------------------");
@@ -20,7 +20,7 @@ import { ShelleyWallet } from "./lib/helpers/crypto";
"\nš Please copy the following to your environment variables:"
);
console.log(`1. Set FAUCET_PAYMENT_PRIVATE=${walletJson.payment.private}`);
- console.log(`2. Set FAUCET_STAKE_PKH=${walletJson.stake.pkh}`);
+ console.log(`2. Set FAUCET_STAKE_PRIVATE=${walletJson.stake.private}`);
console.log(`3. Set FAUCET_ADDRESS=${walletJson.address}`);
console.log(
diff --git a/tests/govtool-frontend/playwright/lib/constants/environments.ts b/tests/govtool-frontend/playwright/lib/constants/environments.ts
index 1882424c6..c382c358b 100644
--- a/tests/govtool-frontend/playwright/lib/constants/environments.ts
+++ b/tests/govtool-frontend/playwright/lib/constants/environments.ts
@@ -18,7 +18,9 @@ const environments = {
apiUrl: `https://faucet.${NETWORK}.world.dev.cardano.org`,
address: process.env.FAUCET_ADDRESS,
payment: { private: process.env.FAUCET_PAYMENT_PRIVATE },
- stake: { pkh: process.env.FAUCET_STAKE_PKH },
+ stake: {
+ private: process.env.FAUCET_STAKE_PRIVATE,
+ },
},
kuber: {
apiUrl: `https://${NETWORK}.kuber.cardanoapi.io`,
@@ -28,6 +30,10 @@ const environments = {
metadataBucketUrl: `${CARDANO_API_METADATA_HOST_URL}/data`,
lockInterceptorUrl: `${CARDANO_API_METADATA_HOST_URL}/lock`,
ci: process.env.CI,
+ isScheduled:
+ (process.env.SCHEDULED_WORKFLOW &&
+ process.env.SCHEDULED_WORKFLOW == "true") ||
+ false,
};
export default environments;
diff --git a/tests/govtool-frontend/playwright/lib/helpers/cardano.ts b/tests/govtool-frontend/playwright/lib/helpers/cardano.ts
index a2b47a3ff..e4ff03501 100644
--- a/tests/govtool-frontend/playwright/lib/helpers/cardano.ts
+++ b/tests/govtool-frontend/playwright/lib/helpers/cardano.ts
@@ -85,3 +85,10 @@ export async function getWalletBalance(address: string) {
return balance;
}
+
+export async function skipIfScheduledWorkflow() {
+ if (environments.isScheduled) {
+ await allure.description("This test is skipped in scheduled workflow.");
+ test.skip();
+ }
+}
diff --git a/tests/govtool-frontend/playwright/lib/helpers/crypto.ts b/tests/govtool-frontend/playwright/lib/helpers/crypto.ts
index c221c747e..62442ac9f 100644
--- a/tests/govtool-frontend/playwright/lib/helpers/crypto.ts
+++ b/tests/govtool-frontend/playwright/lib/helpers/crypto.ts
@@ -247,3 +247,9 @@ export class ShelleyWalletAddress implements Address {
return Buffer.from(this.toRawBytes()).toString("hex");
}
}
+
+export const createKeyFromPrivateKeyHex = async (
+ privateKeyHex: string
+): Promise => {
+ return await Ed25519Key.fromPrivateKeyHex(privateKeyHex);
+};
diff --git a/tests/govtool-frontend/playwright/lib/helpers/index.ts b/tests/govtool-frontend/playwright/lib/helpers/index.ts
index 3b5b658eb..86fc90cc3 100644
--- a/tests/govtool-frontend/playwright/lib/helpers/index.ts
+++ b/tests/govtool-frontend/playwright/lib/helpers/index.ts
@@ -22,7 +22,7 @@ export const getWalletConfigForFaucet = () => {
private: environments.faucet.payment.private || "",
},
stake: {
- pkh: environments.faucet.stake.pkh || "",
+ private: environments.faucet.stake.private || "",
},
address: environments.faucet.address || "",
};
diff --git a/tests/govtool-frontend/playwright/tests/7-proposal-submission/proposalSubmission.ga.spec.ts b/tests/govtool-frontend/playwright/tests/7-proposal-submission/proposalSubmission.ga.spec.ts
index 3826af310..dcc0fbaa3 100644
--- a/tests/govtool-frontend/playwright/tests/7-proposal-submission/proposalSubmission.ga.spec.ts
+++ b/tests/govtool-frontend/playwright/tests/7-proposal-submission/proposalSubmission.ga.spec.ts
@@ -9,6 +9,7 @@ import ProposalSubmissionPage from "@pages/proposalSubmissionPage";
import { expect } from "@playwright/test";
import {
skipIfMainnet,
+ skipIfScheduledWorkflow,
skipIfTemporyWalletIsNotAvailable,
} from "@helpers/cardano";
import { ProposalType } from "@types";
@@ -19,6 +20,7 @@ import { getWalletConfigForFaucet } from "@helpers/index";
import { faker } from "@faker-js/faker";
import { proposalSubmissionAuthFile } from "@constants/auth";
import ProposalDiscussionDetailsPage from "@pages/proposalDiscussionDetailsPage";
+import { createKeyFromPrivateKeyHex } from "@helpers/crypto";
test.beforeEach(async () => {
await setAllureEpic("7. Proposal submission");
@@ -31,9 +33,19 @@ Object.values(ProposalType).forEach((proposalType, index) => {
page,
browser,
}, testInfo) => {
+ await skipIfScheduledWorkflow();
test.setTimeout(testInfo.timeout + environments.txTimeOut);
const wallet = await walletManager.popWallet("proposalSubmission");
+
+ const stakeKeys = await createKeyFromPrivateKeyHex(
+ environments.faucet.stake.private || ""
+ );
+ const { pkh: stakePkh, public: stakePublic } = stakeKeys.json();
+ wallet.stake.pkh = stakePkh;
+ wallet.stake.private = getWalletConfigForFaucet().stake.private;
+ wallet.stake.public = stakePublic;
+
await logWalletDetails(wallet.address);
const tempUserAuth = await createTempUserAuth(page, wallet);
@@ -46,16 +58,22 @@ Object.values(ProposalType).forEach((proposalType, index) => {
const proposalDiscussionPage = new ProposalDiscussionPage(userPage);
await proposalDiscussionPage.goto();
await proposalDiscussionPage.verifyIdentityBtn.click();
- await proposalDiscussionPage.setUsername(mockValid.username());
+
+ try {
+ await expect(userPage.getByTestId("username-input")).toBeVisible({
+ timeout: 10_000,
+ });
+ await proposalDiscussionPage.setUsername(mockValid.username());
+ } catch (error) {
+ // Ignore error if username is already set
+ console.log("Username is already set");
+ }
const proposalSubmissionPage = new ProposalSubmissionPage(userPage);
await proposalSubmissionPage.proposalCreateBtn.click();
await proposalDiscussionPage.continueBtn.click();
- const rewardAddress = rewardAddressBech32(
- environments.networkId,
- getWalletConfigForFaucet().stake.pkh
- );
+ const rewardAddress = rewardAddressBech32(environments.networkId, stakePkh);
await proposalSubmissionPage.createProposal(rewardAddress, proposalType);
@@ -99,7 +117,7 @@ test.describe("Proposed as a governance action", async () => {
const rewardAddress = rewardAddressBech32(
environments.networkId,
- getWalletConfigForFaucet().stake.pkh
+ getWalletConfigForFaucet().address
);
proposalId = await proposalSubmissionPage.createProposal(rewardAddress);
diff --git a/tests/govtool-frontend/playwright/tests/proposal.setup.ts b/tests/govtool-frontend/playwright/tests/proposal.setup.ts
index 4a44b621c..5087fd136 100644
--- a/tests/govtool-frontend/playwright/tests/proposal.setup.ts
+++ b/tests/govtool-frontend/playwright/tests/proposal.setup.ts
@@ -9,7 +9,7 @@ import walletManager from "lib/walletManager";
import { functionWaitedAssert } from "@helpers/waitedLoop";
import { getWalletConfigForFaucet } from "@helpers/index";
-const PROPOSAL_WALLETS_COUNT = 5;
+const PROPOSAL_WALLETS_COUNT = environments.isScheduled ? 1 : 5;
let govActionDeposit: number;
@@ -28,8 +28,7 @@ setup.beforeEach(async () => {
await setAllureStory("Proposal");
await skipIfMainnet();
const totalRequiredBalanceForWallets =
- (govActionDeposit / 1000000) * PROPOSAL_WALLETS_COUNT +
- 22 * PROPOSAL_WALLETS_COUNT;
+ (govActionDeposit / 1000000 + 22) * PROPOSAL_WALLETS_COUNT;
await skipIfBalanceIsInsufficient(totalRequiredBalanceForWallets);
});