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()} + - app-logo - - {screenWidth >= 1145 ? renderDesktopNav() : renderMobileNav()} - - + isConnectButton || disconnectWallet()} + > + app-logo + + {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.", + "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); });