diff --git a/.github/images/cardano-govtool-header.png b/.github/images/cardano-govtool-header.png new file mode 100644 index 000000000..f7a7ded9c Binary files /dev/null and b/.github/images/cardano-govtool-header.png differ diff --git a/.github/workflows/add-issue-to-project.yml b/.github/workflows/add-issue-to-project.yml new file mode 100644 index 000000000..50f3c22de --- /dev/null +++ b/.github/workflows/add-issue-to-project.yml @@ -0,0 +1,24 @@ +name: Add all issues created to projects + +on: + issues: + types: [opened] + +jobs: + add-to-govtool-all-project: + name: Add issue to GovTool all project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/IntersectMBO/projects/30 + github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT_PAT }} + + add-to-community-backlog-project: + name: Add issue to governance tools community backlog project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/IntersectMBO/projects/34 + github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT_PAT }} \ No newline at end of file diff --git a/.github/workflows/build-and-deploy-test-stack.yml b/.github/workflows/build-and-deploy-test-stack.yml index a4d3c07a2..01802be3c 100644 --- a/.github/workflows/build-and-deploy-test-stack.yml +++ b/.github/workflows/build-and-deploy-test-stack.yml @@ -29,6 +29,7 @@ jobs: key: ${{ secrets.TEST_STACK_SSH_KEY }} command_timeout: 100m ## Haskell container build takes a lot of time. script: | + set -euo pipefail REPO_URL="https://github.com/${{ github.repository }}" DEST_DIR="$HOME/Documents/govtool" @@ -59,9 +60,8 @@ jobs: # Execute the build-and-deploy.sh script cd $DEST_DIR/tests/test-infrastructure ./build-and-deploy.sh update-images - docker system prune - (docker image ls -q | xargs docker image rm --force ) || echo "Images cleaned-up" - envs: GOVTOOL_TAG, GRAFANA_ADMIN_PASSWORD, GRAFANA_SLACK_RECIPIENT, GRAFANA_SLACK_OAUTH_TOKEN, SENTRY_DSN_BACKEND, GTM_ID, NPMRC_TOKEN, SENTRY_DSN_FRONTEND, PIPELINE_URL, USERSNAP_SPACE_API_KEY, APP_ENV, PDF_API_URL + yes | docker system prune -f || echo "Ignoring system prune eror" + envs: GOVTOOL_TAG, GRAFANA_ADMIN_PASSWORD, GRAFANA_SLACK_RECIPIENT, GRAFANA_SLACK_OAUTH_TOKEN, SENTRY_DSN_BACKEND, GTM_ID, NPMRC_TOKEN, SENTRY_DSN_FRONTEND, PIPELINE_URL, USERSNAP_SPACE_API_KEY, APP_ENV, PDF_API_URL, KUBER_API_KEY env: GOVTOOL_TAG: ${{ github.sha }} GRAFANA_ADMIN_PASSWORD: ${{ secrets.GRAFANA_ADMIN_PASSWORD }} @@ -75,4 +75,4 @@ jobs: USERSNAP_SPACE_API_KEY: ${{ secrets.USERSNAP_SPACE_API_KEY }} APP_ENV: test PDF_API_URL: ${{ secrets.PDF_API_URL }} - KUBER_API_KEY: ${{secrets.KUBER_API_KEY}} + KUBER_API_KEY: ${{ secrets.KUBER_API_KEY }} diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml index d89b4ee3f..a21f26b48 100644 --- a/.github/workflows/merge.yaml +++ b/.github/workflows/merge.yaml @@ -162,3 +162,11 @@ jobs: docker load -i '/tmp/image-${{ matrix.name }}-${{ env.ENVIRONMENT }}.tar' rm -rf '/tmp/image-${{ matrix.name }}-${{ env.ENVIRONMENT }}.tar' docker push ${{ steps.image_lowercase.outputs.lowercase }}:${{ env.TAG }} + + - name: Add tag as a PR comment + uses: ubie-oss/comment-to-merged-pr-action@v0.3.3 + id: comment-to-merged-pr + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + message: |- + This PR is in the tag: ${{ env.TAG }} , for ${{ matrix.name }} service diff --git a/.github/workflows/test_integration_playwright.yml b/.github/workflows/test_integration_playwright.yml index 95a5151a8..f1c149685 100644 --- a/.github/workflows/test_integration_playwright.yml +++ b/.github/workflows/test_integration_playwright.yml @@ -14,6 +14,7 @@ on: - "sanchogov.tools" - "staging.govtool.byron.network" - "govtool.cardanoapi.io" + - "preview.gov.tools" workflow_run: workflows: ["Build and deploy GovTool to TEST server"] @@ -79,6 +80,8 @@ jobs: KUBER_API_KEY: ${{secrets.KUBER_API_KEY}} TEST_WORKERS: ${{vars.TEST_WORKERS}} CI: ${{vars.CI}} + NETWORK: ${{vars.NETWORK}} + FAUCET_ADDRESS: ${{vars.FAUCET_ADDRESS}} CARDANOAPI_METADATA_URL: ${{vars.CARDANOAPI_METADATA_URL}} publish-report: diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ec1ed241..fee842e2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,20 +12,212 @@ changes. ### Added +- Handle displaying votes based on bootstrap phase, full governance and security groups [Issue 2316](https://github.com/IntersectMBO/govtool/issues/2316) +- Add support for displaying Motion of No Confidence Governance Action [Issue 1597](https://github.com/IntersectMBO/govtool/issues/1597) +- Add support for displaying Update committee/threshold Governance Action [Issue 1598](https://github.com/IntersectMBO/govtool/issues/1598) +- Add support for displaying New Constitution and/or Guardrails Script Governance Action [Issue 1599](https://github.com/IntersectMBO/govtool/issues/1598) + +### Fixed + +- Fix submitting treasury governance action [Issue 1845](https://github.com/IntersectMBO/govtool/issues/1845) +- Fix failing github action workflow [Issue 2277](https://github.com/IntersectMBO/govtool/issues/2277) + +### Changed + +- Bumped Cardano node version to `10.1.0-pre`. +- Bumped Cardano DB Sync version to `13.6.0.0-pre`. + +### Removed + +- + +## [v1.0.26](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.26) 2024-11-07 + +### Added + +- add support for CIP-129 governance identifiers [Issue 2183](https://github.com/IntersectMBO/govtool/issues/2183) +- Add label to supporting links in Governance Action details [Issue 2305](https://github.com/IntersectMBO/govtool/issues/2305) + +### Fixed + +- Fix certificates order where vote delegation cert is before the DRep registration cert [Issue 2333](https://github.com/IntersectMBO/govtool/issues/2333) + +### Changed + +- Change multilanguage support to use json file [Issue 2325](https://github.com/IntersectMBO/govtool/issues/2325) +- Display full Governance Action IDs on desktop +- Support space on given name [Issue 2276](https://github.com/IntersectMBO/govtool/issues/2276) +- Display ADA in 'en-US' format [Issue 2305](https://github.com/IntersectMBO/govtool/issues/2305) + +### Removed + +- + +## [v1.0.25](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.25) 2024-11-04 + +### Added + +- + +### Fixed + +- Fix searching by DRep Given name +- Fix displaying the wallet connected modal +- Fix navigating to DRep details [Issue 2307](https://github.com/IntersectMBO/govtool/issues/2307) + +### Changed + +- + +### Removed + +- + +## [v1.0.24](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.24) 2024-10-31 + +### Added + +- + +### Fixed + +- Fix infinite DRep list loading [Issue 2285](https://github.com/IntersectMBO/govtool/issues/2285) + +### Changed + +- + +### Removed + +- + +## [v1.0.23](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.23) 2024-10-29 + +### Added + +- Add searching for DRep and Proposal metadatas [Issue 1893](https://github.com/IntersectMBO/govtool/issues/1783) + +### Fixed + +- Fix validating metadata against the CIP standard [Issue 2233](https://github.com/IntersectMBO/govtool/issues/2233) +- Fix counting the CC votes [Issue 2247](https://github.com/IntersectMBO/govtool/issues/2247) + +### Changed + +- + +### Removed + +- + +## [v1.0.22](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.22) 2024-10-24 + +### Added + +- + +### Fixed + +- Fix unwanted horizontal page scroll on Governance Actions page [Issue 1897](https://github.com/IntersectMBO/govtool/issues/1897) +- Fix duplicate testIds for reference errors and hints in DRep metadata form [Issue 1965](https://github.com/IntersectMBO/govtool/issues/1965) +- Eliminate duplicate DReps in the DRep Directory [Issue 2171](https://github.com/IntersectMBO/govtool/issues/2171) +- Handle script based DReps [Issue 1951](https://github.com/IntersectMBO/govtool/issues/1951) +- Fix displaying protocol parameter cost models [Issue 2191](https://github.com/IntersectMBO/govtool/issues/2191) + +### Changed + +- Bump to use Cardano Node `10.0.0-pre` +- Changed copy for no DRep found via search + +### Removed + +- + +## [v1.0.21](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.21) 2024-10-15 + +### Added + - ### Fixed +- Fix counting epoch boundaries for Governance Actions [Issue 2125](https://github.com/IntersectMBO/govtool/issues/2125) +- Fix displaying the SPO Votes [Issue 2085](https://github.com/IntersectMBO/govtool/issues/2085) +- Fix counting ada holder voting power [Issue 2000](https://github.com/IntersectMBO/govtool/issues/2000) + +### Changed + +- + +### Removed + - +## [v1.0.20](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.20) 2024-10-03 + +### Added + +- Add useful external links to home page and dashboard [Issue 1995](https://github.com/IntersectMBO/govtool/issues/1995) + +### Fixed + +- Add missing testIds for submitted votes [Issue 1875](https://github.com/IntersectMBO/govtool/issues/1875) +- Provide workaround for iOS for downloading metadata on iOS [Issue 1989](https://github.com/IntersectMBO/govtool/issues/1989) +- Fix infinite loading in DRep Directory [Issue 2090](https://github.com/IntersectMBO/govtool/issues/2090) + ### Changed +- Change constitutional committee vote totals to be constitutional for yes and unconstitutional for no [Issue 2062](https://github.com/IntersectMBO/govtool/issues/2062) +- Bump @intersect.mbo/pdf-ui to v0.4.0 +- Include @language property in generated jsonld files [Issue 1856](https://github.com/IntersectMBO/govtool/issues/1856) + +### Removed + +- + +## [v1.0.19](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.19) 2024-09-19 + +### Added + - +### Fixed + +- Fix private policy link and get support link +- Fixed poor UTxO management when building transactions [Issue 2059](https://github.com/IntersectMBO/govtool/issues/2059) + +### Changed + +- Bump cardano-db-sync 13.5.0.2 [Issue 1945](https://github.com/IntersectMBO/govtool/issues/1945) +- Add Mainnet link to network banner [Issue 2002](https://github.com/IntersectMBO/govtool/issues/2002) +- Bump CSL version to 12.1.0 +- Add random sorting as default sorting for DRep list [Issue 2013](https://github.com/IntersectMBO/govtool/issues/2013) + ### Removed - +## [v1.0.18](https://github.com/IntersectMBO/govtool/releases/tag/v1.0.18) 2024-09-12 + +### Added + +- Add script for Matomo Analytics [Issue 1817](https://github.com/IntersectMBO/govtool/issues/1817) + +### Fixed + +- Correctly show all kinds of votes in the modal showing vote numbers [Issue 1941](https://github.com/IntersectMBO/govtool/issues/1941) +- Fixed terms and conditions link [Issue 1968](https://github.com/IntersectMBO/govtool/issues/1968) +- Hide Delegate button in DRep list and details if user has already delegated to this DRep [Issue 1982](https://github.com/IntersectMBO/govtool/issues/1982) +- Fix condition for disabling voting on different GA types [Issue 2008](https://github.com/IntersectMBO/govtool/issues/2008) +- Fix incorrect copy (ex. github) to (e.g. github) [Issue 1748](https://github.com/IntersectMBO/govtool/issues/1748) +- Fix broken translation in DRep Details [Issue 2036](https://github.com/IntersectMBO/govtool/issues/2036) + +### Changed + +- Bump @intersect.mbo/pdf-ui to v0.3.9 +- Changed misleading text for direct voter registration [Issue 1976](https://github.com/IntersectMBO/govtool/issues/1976) +- Change base repo README header image to have correct branding and reflect mainnet launch. + ## [sancho-v1.0.17](https://github.com/IntersectMBO/govtool/releases/tag/sancho-v1.0.17) 2024-09-05 ### Added diff --git a/README.md b/README.md index 8a8aea50b..db07e081c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

- +

@@ -24,7 +24,7 @@ The Cardano GovTool enables ada holders to experience the governance features de #### Mainnet -- _Coming soon_ +- [gov.tools](https://gov.tools/) #### SanchoNet diff --git a/docs/architecture/README.md b/docs/architecture/README.md index 9b8e3fafa..66689da9c 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -35,7 +35,7 @@ Frontend is a React application using Vite as a built tool to enhance developmen Direct voter uses following CSL services: - TransactionBuilder - to build the transaction - CertificatesBuilder - to build the delegation certificate - - DrepRegistration - to build the DRep registration certificate + - DRepRegistration - to build the DRep registration certificate - **DRep** - DRep is a Decentralized Representative which has a metadata and can delegate ADA to other DReps. DRep registration and delegation are separate processes. DReps are visible in the DRep directory. @@ -46,9 +46,9 @@ Frontend is a React application using Vite as a built tool to enhance developmen DRep uses following CSL services: - TransactionBuilder - to build the transaction - CertificatesBuilder - to build the DRep registration certificate - - DrepRegistration - to build the DRep registration certificate - - DrepDeregistration - to build the DRep deregistration certificate - - DrepUpdate - to build the DRep update certificate + - DRepRegistration - to build the DRep registration certificate + - DRepDeregistration - to build the DRep deregistration certificate + - DRepUpdate - to build the DRep update certificate **Note** diff --git a/gov-action-loader/backend/app/transaction.py b/gov-action-loader/backend/app/transaction.py index 0d9027960..fda74247c 100644 --- a/gov-action-loader/backend/app/transaction.py +++ b/gov-action-loader/backend/app/transaction.py @@ -39,8 +39,8 @@ def get_base_proposal(): def get_base_proposal_for_multiple(): base_proposal = get_base_proposal() base_proposal["anchor"] = { - "url": "http://bit.ly/3QFMhii", - "dataHash": "1111111111111111111111111111111111111111111111111111111111111112", + "url": "https://metadata-govtool.cardanoapi.io/data/gov-action-loader", + "dataHash": "bcb2fadedbe928519ff000051d8e78ffcabadba0f92b91334cee3e8786491462", } base_proposal["deposit"] = default_proposal_deposit_ada * 1000000 return base_proposal @@ -69,7 +69,7 @@ def generate_bytes(length): def generate_withdraw(number): - stake_addresses = [generate_raw_address() for _ in range(number)] + stake_addresses = ["e02fe0d8c1b1c600249e8b9663e18790425e9589ac17cb5ee952d54bee"] amounts = [ random.choice([10000000, 20000000, 30000000, 40000000, 50000000]) for _ in range(number) @@ -78,7 +78,7 @@ def generate_withdraw(number): def generate_update(number): - stake_addresses = [generate_raw_address() for _ in range(number)] + stake_addresses = ["e02fe0d8c1b1c600249e8b9663e18790425e9589ac17cb5ee952d54bee"] current_epoch = int((int(time.time()) - 1506635071) / (5 * 24 * 60 * 60)) maximum_epoch = 10000 epochs = [random.randint(current_epoch + 2, maximum_epoch) for _ in range(number)] diff --git a/gov-action-loader/frontend/src/views/SpecificLoad.vue b/gov-action-loader/frontend/src/views/SpecificLoad.vue index 05fd0507f..b9cc8744a 100644 --- a/gov-action-loader/frontend/src/views/SpecificLoad.vue +++ b/gov-action-loader/frontend/src/views/SpecificLoad.vue @@ -72,15 +72,17 @@ import config from '../config' - +

+ + +
+ -
- + -
+ @@ -915,6 +917,12 @@ export default { ...(this.DRepDeposit != null ? { DRepDeposit: parseInt(this.DRepDeposit) } : {}), ...(this.DRepActivity != null ? { DRepActivity: parseInt(this.DRepActivity) } : {}), } + proposal_data['script'] = + { + type: "PlutusScriptV3", + description: "", + cborHex: this.guardrailScript + } break } diff --git a/govtool/backend/Dockerfile b/govtool/backend/Dockerfile index 38c09931d..c601511c6 100644 --- a/govtool/backend/Dockerfile +++ b/govtool/backend/Dockerfile @@ -4,4 +4,4 @@ FROM $BASE_IMAGE_REPO:$BASE_IMAGE_TAG WORKDIR /src COPY . . RUN cabal build -RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-1.0.17/x/vva-be/build/vva-be/vva-be /usr/local/bin +RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-1.0.26/x/vva-be/build/vva-be/vva-be /usr/local/bin diff --git a/govtool/backend/Dockerfile.base b/govtool/backend/Dockerfile.base index 6dd3c75a3..fcdf388ab 100644 --- a/govtool/backend/Dockerfile.base +++ b/govtool/backend/Dockerfile.base @@ -3,16 +3,60 @@ # process by ensuring it only needs to compile against these dependencies. This # is a common practice in Haskell projects, as it can significantly reduce the # time it takes to build the project. +# +# The reason why we do not use the official haskell image is that the official +# image does not include the necessary dependencies for the project, which are +# unobtainable from the official image. -FROM haskell:9.2.7-buster +FROM ubuntu:24.04 + +# Set the working directory WORKDIR /src +# Set noninteractive mode +ENV DEBIAN_FRONTEND=noninteractive + +# Update package list and install dependencies RUN apt-get update && \ - apt-get install -y wget lsb-release && \ - sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \ + apt-get install -y \ + software-properties-common \ + wget \ + gnupg \ + curl \ + build-essential \ + libncurses-dev \ + libgmp-dev \ + liblzma-dev \ + pkg-config \ + zlib1g-dev \ + xz-utils + +# Install PostgreSQL 14 +RUN sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt/ $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' && \ wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - && \ apt-get update && \ apt-get install -y postgresql-14 libpq-dev +# Download and install GHC 9.2.7 +RUN wget https://downloads.haskell.org/~ghc/9.2.7/ghc-9.2.7-x86_64-deb10-linux.tar.xz && \ + tar -xf ghc-9.2.7-x86_64-deb10-linux.tar.xz && \ + cd ghc-9.2.7 && \ + ./configure && \ + make install && \ + cd .. && \ + rm -rf ghc-9.2.7 ghc-9.2.7-x86_64-deb10-linux.tar.xz + +# Install Cabal +RUN wget https://downloads.haskell.org/~cabal/cabal-install-3.6.2.0/cabal-install-3.6.2.0-x86_64-linux-deb10.tar.xz && \ + tar -xf cabal-install-3.6.2.0-x86_64-linux-deb10.tar.xz && \ + mv cabal /usr/local/bin/ && \ + rm cabal-install-3.6.2.0-x86_64-linux-deb10.tar.xz + +# Copy the project files into the container COPY . . -RUN cabal update && cabal configure && cabal install --only-dependencies && rm -rf /src/* + +# Install the project dependencies +RUN cabal update && \ + cabal configure && \ + cabal install --only-dependencies && \ + rm -rf /src/* diff --git a/govtool/backend/Dockerfile.qovery b/govtool/backend/Dockerfile.qovery index cdf1acc79..41cf5dfd4 100644 --- a/govtool/backend/Dockerfile.qovery +++ b/govtool/backend/Dockerfile.qovery @@ -4,7 +4,7 @@ FROM $BASE_IMAGE_REPO:$BASE_IMAGE_TAG WORKDIR /src COPY . . RUN cabal build -RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-1.0.17/x/vva-be/build/vva-be/vva-be /usr/local/bin +RUN cp dist-newstyle/build/x86_64-linux/ghc-9.2.7/vva-be-1.0.26/x/vva-be/build/vva-be/vva-be /usr/local/bin # Expose the necessary port EXPOSE 9876 diff --git a/govtool/backend/app/Main.hs b/govtool/backend/app/Main.hs index fb2f7b524..91a743025 100644 --- a/govtool/backend/app/Main.hs +++ b/govtool/backend/app/Main.hs @@ -82,7 +82,7 @@ startApp vvaConfig = do settings = setPort vvaPort $ setHost vvaHost - $ setTimeout 60 -- 60 seconds timeout + $ setTimeout 120 -- 120 seconds timeout $ setBeforeMainLoop ( Text.hPutStrLn stderr $ Text.pack @@ -117,7 +117,7 @@ startApp vvaConfig = do , dRepListCache , networkMetricsCache } - connectionPool <- createPool (connectPostgreSQL (encodeUtf8 (dbSyncConnectionString $ getter vvaConfig))) close 1 1 60 + connectionPool <- createPool (connectPostgreSQL (encodeUtf8 (dbSyncConnectionString $ getter vvaConfig))) close 10 10 120 let appEnv = AppEnv {vvaConfig=vvaConfig, vvaCache=cacheEnv, vvaConnectionPool=connectionPool } server' <- mkVVAServer appEnv runSettings settings server' diff --git a/govtool/backend/example-config.json b/govtool/backend/example-config.json index 463fdc7df..fe6a47420 100644 --- a/govtool/backend/example-config.json +++ b/govtool/backend/example-config.json @@ -1,14 +1,14 @@ { - "dbsyncconfig" : { - "host" : "localhost", - "dbname" : "cexplorer", - "user" : "postgres", - "password" : "postgres", - "port" : 5432 - }, - "port" : 9999, - "host" : "localhost", - "cachedurationseconds": 20, - "sentrydsn": "https://username:password@senty.host/id", - "sentryenv": "dev" + "dbsyncconfig" : { + "host" : "localhost", + "dbname" : "cexplorer", + "user" : "postgres", + "password" : "postgres", + "port" : 5432 + }, + "port" : 9999, + "host" : "localhost", + "cachedurationseconds": 20, + "sentrydsn": "https://username:password@senty.host/id", + "sentryenv": "dev" } diff --git a/govtool/backend/sql/get-current-delegation.sql b/govtool/backend/sql/get-current-delegation.sql index e4a92d392..365344eff 100644 --- a/govtool/backend/sql/get-current-delegation.sql +++ b/govtool/backend/sql/get-current-delegation.sql @@ -4,6 +4,13 @@ select else encode(drep_hash.raw,'hex') end as drep_raw, drep_hash.view as drep_view, + EXISTS ( + SELECT dh.has_script + FROM drep_hash as dh + WHERE drep_hash.raw = dh.raw + AND dh.has_script = true + LIMIT 1 + ) AS has_script, encode(tx.hash, 'hex') from delegation_vote join tx on tx.id = delegation_vote.tx_id diff --git a/govtool/backend/sql/get-current-epoch-params.sql b/govtool/backend/sql/get-current-epoch-params.sql index 9fd642903..2b591a0b4 100644 --- a/govtool/backend/sql/get-current-epoch-params.sql +++ b/govtool/backend/sql/get-current-epoch-params.sql @@ -1 +1,18 @@ -select ROW_TO_JSON(epoch_param) from epoch_param order by epoch_no desc limit 1; +SELECT + jsonb_set( + ROW_TO_JSON(epoch_param)::jsonb, + '{cost_model}', + CASE + WHEN cost_model.id IS NOT NULL THEN + ROW_TO_JSON(cost_model)::jsonb + ELSE + 'null'::jsonb + END + ) AS epoch_param +FROM + epoch_param +LEFT JOIN + cost_model ON epoch_param.cost_model_id = cost_model.id +ORDER BY + epoch_no DESC +LIMIT 1; diff --git a/govtool/backend/sql/get-drep-info.sql b/govtool/backend/sql/get-drep-info.sql index ca4505d6c..3b25285d2 100644 --- a/govtool/backend/sql/get-drep-info.sql +++ b/govtool/backend/sql/get-drep-info.sql @@ -34,6 +34,18 @@ LatestRegistrationEntry AS ( drep_registration.tx_id DESC LIMIT 1 ), +IsScriptHash AS ( + SELECT EXISTS( + SELECT + drep_hash.has_script + FROM + drep_hash + CROSS JOIN DRepId + WHERE + drep_hash.raw = DRepId.raw + AND + drep_hash.has_script = true +) AS has_script), IsRegisteredAsDRep AS ( SELECT (LatestRegistrationEntry.deposit IS NULL @@ -165,6 +177,7 @@ SoleVoterRetire AS ( LIMIT 1 ) SELECT + IsScriptHash.has_script, IsRegisteredAsDRep.value, WasRegisteredAsDRep.value, IsRegisteredAsSoleVoter.value, @@ -197,5 +210,6 @@ FROM CROSS JOIN SoleVoterRegister CROSS JOIN SoleVoterRetire CROSS JOIN LatestRegistrationEntry + CROSS JOIN IsScriptHash LEFT JOIN off_chain_vote_data ON off_chain_vote_data.voting_anchor_id = LatestRegistrationEntry.voting_anchor_id LEFT JOIN off_chain_vote_drep_data ON off_chain_vote_drep_data.off_chain_vote_data_id = off_chain_vote_data.id diff --git a/govtool/backend/sql/get-stake-key-voting-power.sql b/govtool/backend/sql/get-stake-key-voting-power.sql index 59aa5049b..b42da7ed9 100644 --- a/govtool/backend/sql/get-stake-key-voting-power.sql +++ b/govtool/backend/sql/get-stake-key-voting-power.sql @@ -1,6 +1,11 @@ -select coalesce(sum(utxo_view.value), 0), encode(stake_address.hash_raw, 'hex') -from stake_address -join utxo_view -on utxo_view.stake_address_id = stake_address.id -where stake_address.hash_raw = decode(?, 'hex') -group by stake_address.hash_raw +SELECT COALESCE(SUM(utxo_view.value::numeric), 0) + COALESCE(reward_sum.total_reward, 0) AS total_value, + encode(stake_address.hash_raw, 'hex') +FROM stake_address +JOIN utxo_view ON utxo_view.stake_address_id = stake_address.id +LEFT JOIN ( + SELECT addr_id, SUM(reward_rest.amount) AS total_reward + FROM reward_rest + GROUP BY addr_id +) AS reward_sum ON reward_sum.addr_id = stake_address.id +WHERE stake_address.hash_raw = decode(?, 'hex') +GROUP BY stake_address.hash_raw, reward_sum.total_reward; diff --git a/govtool/backend/sql/list-dreps.sql b/govtool/backend/sql/list-dreps.sql index 917f0641f..043b316d2 100644 --- a/govtool/backend/sql/list-dreps.sql +++ b/govtool/backend/sql/list-dreps.sql @@ -21,6 +21,7 @@ DRepActivity AS ( SELECT encode(dh.raw, 'hex'), dh.view, + dh.has_script, va.url, encode(va.data_hash, 'hex'), dr_deposit.deposit, @@ -30,6 +31,7 @@ SELECT newestRegister.time AS last_register_time, COALESCE(latestDeposit.deposit, 0), non_deregister_voting_anchor.url IS NOT NULL AS has_non_deregister_voting_anchor, + fetch_error.message, off_chain_vote_drep_data.payment_address, off_chain_vote_drep_data.given_name, off_chain_vote_drep_data.objectives, @@ -95,7 +97,15 @@ FROM LEFT JOIN DRepDistr ON DRepDistr.hash_id = dh.id AND DRepDistr.rn = 1 LEFT JOIN voting_anchor va ON va.id = dr_voting_anchor.voting_anchor_id - LEFT JOIN voting_anchor non_deregister_voting_anchor on non_deregister_voting_anchor.id = dr_non_deregister_voting_anchor.voting_anchor_id + LEFT JOIN voting_anchor non_deregister_voting_anchor ON non_deregister_voting_anchor.id = dr_non_deregister_voting_anchor.voting_anchor_id + LEFT JOIN ( + SELECT fetch_error as message, voting_anchor_id + FROM off_chain_vote_fetch_error + WHERE fetch_time = ( + SELECT max(fetch_time) + FROM off_chain_vote_fetch_error) + GROUP BY fetch_error, voting_anchor_id + ) AS fetch_error ON fetch_error.voting_anchor_id = va.id LEFT JOIN off_chain_vote_data ON off_chain_vote_data.voting_anchor_id = va.id LEFT JOIN off_chain_vote_drep_data on off_chain_vote_drep_data.off_chain_vote_data_id = off_chain_vote_data.id CROSS JOIN DRepActivity @@ -124,10 +134,18 @@ FROM AND dr_first_register.rn = 1 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 +WHERE + ( + COALESCE(?, '') = '' OR + (CASE WHEN LENGTH(?) % 2 = 0 AND ? ~ '^[0-9a-fA-F]+$' THEN dh.raw = decode(?, 'hex') ELSE false END) OR + dh.view ILIKE ? OR + off_chain_vote_drep_data.given_name ILIKE ? + ) GROUP BY dh.raw, second_to_newest_drep_registration.voting_anchor_id, dh.view, + dh.has_script, va.url, va.data_hash, dr_deposit.deposit, @@ -138,6 +156,7 @@ GROUP BY newestRegister.time, latestDeposit.deposit, non_deregister_voting_anchor.url, + fetch_error.message, off_chain_vote_drep_data.payment_address, off_chain_vote_drep_data.given_name, off_chain_vote_drep_data.objectives, diff --git a/govtool/backend/sql/list-proposals.sql b/govtool/backend/sql/list-proposals.sql index 7926fd6ba..7380dbcdf 100644 --- a/govtool/backend/sql/list-proposals.sql +++ b/govtool/backend/sql/list-proposals.sql @@ -5,13 +5,12 @@ WITH LatestDrepDistr AS ( FROM drep_distr ), -EpochUtils AS ( +LatestEpoch AS ( SELECT - (Max(end_time) - Min(end_time)) /(Max(NO) - Min(NO)) AS epoch_duration, - Max(NO) AS last_epoch_no, - Max(end_time) AS last_epoch_end_time + start_time, + no FROM - epoch + epoch ORDER BY no DESC LIMIT 1 ), always_no_confidence_voting_power AS ( SELECT @@ -32,6 +31,74 @@ always_abstain_voting_power AS ( LEFT JOIN drep_distr ON drep_hash.id = drep_distr.hash_id WHERE drep_hash.view = 'drep_always_abstain' ORDER BY epoch_no DESC LIMIT 1), 0) AS amount +), +committee_data AS ( + SELECT DISTINCT ON (ch.raw) + encode(ch.raw, 'hex') AS hash, + cm.expiration_epoch, + ch.has_script + FROM + committee_member cm + JOIN + committee_hash ch ON cm.committee_hash_id = ch.id + ORDER BY ch.raw, cm.expiration_epoch DESC +), +parsed_description AS ( + SELECT + gov_action_proposal.id, + description->'tag' AS tag, + description->'contents'->1 AS members_to_be_removed, + description->'contents'->2 AS members, + description->'contents'->3 AS threshold + FROM + gov_action_proposal + WHERE + gov_action_proposal.type = 'NewCommittee' +), +members_to_be_removed AS ( + SELECT + id, + json_agg(value->>'keyHash') AS members_to_be_removed + FROM + parsed_description, + json_array_elements(members_to_be_removed::json) AS value + GROUP BY + id +), +processed_current_members AS ( + SELECT + pd.id, + json_agg( + json_build_object( + 'hash', regexp_replace(kv.key, '^keyHash-', ''), + 'newExpirationEpoch', kv.value::int + ) + ) AS current_members + FROM + parsed_description pd, + jsonb_each_text(pd.members) AS kv(key, value) + GROUP BY + pd.id +), +enriched_current_members AS ( + SELECT + pcm.id, + json_agg( + json_build_object( + 'hash', cm.hash, + 'expirationEpoch', cm.expiration_epoch, + 'hasScript', cm.has_script, + 'newExpirationEpoch', (member->>'newExpirationEpoch')::int + ) + ) AS enriched_members + FROM + processed_current_members pcm + LEFT JOIN + json_array_elements(pcm.current_members) AS member ON true + LEFT JOIN + committee_data cm ON cm.hash = encode(decode(member->>'hash', 'hex'), 'hex') + GROUP BY + pcm.id ) SELECT gov_action_proposal.id, @@ -43,24 +110,67 @@ SELECT json_build_object('Reward Address', stake_address.view, 'Amount', treasury_withdrawal.amount) when gov_action_proposal.type::text = 'InfoAction' then - json_build_object() + json_build_object('data', gov_action_proposal.description) when gov_action_proposal.type::text = 'HardForkInitiation' then json_build_object( 'major', (gov_action_proposal.description->'contents'->1->>'major')::int, 'minor', (gov_action_proposal.description->'contents'->1->>'minor')::int ) + + when gov_action_proposal.type::text = 'NoConfidence' then + json_build_object('data', gov_action_proposal.description->'contents') + + when gov_action_proposal.type::text = 'ParameterChange' then + json_build_object('data', gov_action_proposal.description->'contents') + + when gov_action_proposal.type::text = 'NewConstitution' then + json_build_object( + 'anchor', gov_action_proposal.description->'contents'->1->'anchor' + ) + when gov_action_proposal.type::text = 'NewCommittee' then + ( + SELECT + json_build_object( + 'tag', pd.tag, + 'members', em.enriched_members, + 'membersToBeRemoved', mtr.members_to_be_removed, + 'threshold', pd.threshold::float + ) + FROM + parsed_description pd + JOIN + members_to_be_removed mtr ON pd.id = mtr.id + JOIN + enriched_current_members em ON pd.id = em.id + WHERE + pd.id = gov_action_proposal.id + ) else null end ) as description, - epoch_utils.last_epoch_end_time + epoch_utils.epoch_duration * (gov_action_proposal.expiration - epoch_utils.last_epoch_no), + CASE + WHEN meta.network_name::text = 'mainnet' OR meta.network_name::text = 'preprod' THEN + latest_epoch.start_time + (gov_action_proposal.expiration - latest_epoch.no)::bigint * INTERVAL '5 days' + ELSE + latest_epoch.start_time + (gov_action_proposal.expiration - latest_epoch.no)::bigint * INTERVAL '1 day' + END AS expiry_date, gov_action_proposal.expiration, creator_block.time, creator_block.epoch_no, voting_anchor.url, encode(voting_anchor.data_hash, 'hex'), - ROW_TO_JSON(proposal_params), + jsonb_set( + ROW_TO_JSON(proposal_params)::jsonb, + '{cost_model}', + CASE + WHEN cost_model.id IS NOT NULL THEN + ROW_TO_JSON(cost_model)::jsonb + ELSE + 'null'::jsonb + END + ) AS proposal_params, off_chain_vote_gov_action_data.title, off_chain_vote_gov_action_data.abstract, off_chain_vote_gov_action_data.motivation, @@ -78,9 +188,9 @@ SELECT always_no_confidence_voting_power.amount END) "no_votes", coalesce(Sum(ldd_drep.amount) FILTER (WHERE voting_procedure.vote::text = 'Abstain'), 0) + always_abstain_voting_power.amount "abstain_votes", - coalesce(vp_by_pool.poolYesVotes, 0), - coalesce(vp_by_pool.poolNoVotes, 0), - coalesce(vp_by_pool.poolAbstainVotes, 0), + coalesce(Sum(ldd_pool.amount) FILTER (WHERE voting_procedure.vote::text = 'Yes'), 0), + coalesce(Sum(ldd_pool.amount) FILTER (WHERE voting_procedure.vote::text = 'No'), 0), + coalesce(Sum(ldd_pool.amount) FILTER (WHERE voting_procedure.vote::text = 'Abstain'), 0), coalesce(vp_by_cc.ccYesVotes, 0), coalesce(vp_by_cc.ccNoVotes, 0), coalesce(vp_by_cc.ccAbstainVotes, 0), @@ -92,33 +202,22 @@ FROM on gov_action_proposal.id = treasury_withdrawal.gov_action_proposal_id LEFT JOIN stake_address on stake_address.id = treasury_withdrawal.stake_address_id - CROSS JOIN EpochUtils AS epoch_utils + CROSS JOIN LatestEpoch AS latest_epoch CROSS JOIN always_no_confidence_voting_power CROSS JOIN always_abstain_voting_power + CROSS JOIN meta JOIN tx AS creator_tx ON creator_tx.id = gov_action_proposal.tx_id JOIN block AS creator_block ON creator_block.id = creator_tx.block_id LEFT JOIN voting_anchor ON voting_anchor.id = gov_action_proposal.voting_anchor_id LEFT JOIN param_proposal as proposal_params ON gov_action_proposal.param_proposal = proposal_params.id + LEFT JOIN cost_model AS cost_model ON proposal_params.cost_model_id = cost_model.id LEFT JOIN off_chain_vote_data ON off_chain_vote_data.voting_anchor_id = voting_anchor.id LEFT JOIN off_chain_vote_gov_action_data ON off_chain_vote_gov_action_data.off_chain_vote_data_id = off_chain_vote_data.id LEFT JOIN voting_procedure ON voting_procedure.gov_action_proposal_id = gov_action_proposal.id LEFT JOIN LatestDrepDistr ldd_drep ON ldd_drep.hash_id = voting_procedure.drep_voter AND ldd_drep.rn = 1 - LEFT JOIN - ( - SELECT - gov_action_proposal_id, - SUM(CASE WHEN vote = 'Yes' THEN 1 ELSE 0 END) AS poolYesVotes, - SUM(CASE WHEN vote = 'No' THEN 1 ELSE 0 END) AS poolNoVotes, - SUM(CASE WHEN vote = 'Abstain' THEN 1 ELSE 0 END) AS poolAbstainVotes - FROM - voting_procedure - WHERE - pool_voter IS NOT NULL - GROUP BY - gov_action_proposal_id - ) vp_by_pool - ON gov_action_proposal.id = vp_by_pool.gov_action_proposal_id + LEFT JOIN LatestDrepDistr ldd_pool ON ldd_pool.hash_id = voting_procedure.pool_voter + AND ldd_pool.rn = 1 LEFT JOIN ( SELECT @@ -127,20 +226,30 @@ FROM SUM(CASE WHEN vote = 'No' THEN 1 ELSE 0 END) AS ccNoVotes, SUM(CASE WHEN vote = 'Abstain' THEN 1 ELSE 0 END) AS ccAbstainVotes FROM - voting_procedure + voting_procedure AS vp WHERE - committee_voter IS NOT NULL + vp.committee_voter IS NOT NULL + AND (vp.tx_id, vp.committee_voter, vp.gov_action_proposal_id) IN ( + SELECT MAX(tx_id), committee_voter, gov_action_proposal_id + FROM voting_procedure + WHERE committee_voter IS NOT NULL + GROUP BY committee_voter, gov_action_proposal_id + ) GROUP BY gov_action_proposal_id ) vp_by_cc ON gov_action_proposal.id = vp_by_cc.gov_action_proposal_id - LEFT JOIN LatestDrepDistr ldd_cc ON ldd_cc.hash_id = voting_procedure.committee_voter AND ldd_cc.rn = 1 LEFT JOIN gov_action_proposal AS prev_gov_action ON gov_action_proposal.prev_gov_action_proposal = prev_gov_action.id LEFT JOIN tx AS prev_gov_action_tx ON prev_gov_action.tx_id = prev_gov_action_tx.id -WHERE (NOT ? - OR (concat(encode(creator_tx.hash, 'hex'), '#', gov_action_proposal.index) IN ?)) +WHERE + (COALESCE(?, '') = '' OR + off_chain_vote_gov_action_data.title ILIKE ? OR + off_chain_vote_gov_action_data.abstract ILIKE ? OR + off_chain_vote_gov_action_data.motivation ILIKE ? OR + off_chain_vote_gov_action_data.rationale ILIKE ? OR + concat(encode(creator_tx.hash, 'hex'), '#', gov_action_proposal.index) ILIKE ?) AND gov_action_proposal.expiration >( SELECT Max(NO) @@ -159,22 +268,19 @@ GROUP BY off_chain_vote_gov_action_data.abstract, off_chain_vote_gov_action_data.motivation, off_chain_vote_gov_action_data.rationale, - vp_by_pool.poolYesVotes, - vp_by_pool.poolNoVotes, - vp_by_pool.poolAbstainVotes, vp_by_cc.ccYesVotes, vp_by_cc.ccNoVotes, vp_by_cc.ccAbstainVotes, gov_action_proposal.index, creator_tx.hash, creator_block.time, - epoch_utils.epoch_duration, - epoch_utils.last_epoch_no, - epoch_utils.last_epoch_end_time, + latest_epoch.start_time, + latest_epoch.no, proposal_params, voting_anchor.url, voting_anchor.data_hash, always_no_confidence_voting_power.amount, always_abstain_voting_power.amount, prev_gov_action.index, - prev_gov_action_tx.hash) \ No newline at end of file + prev_gov_action_tx.hash, + meta.network_name) \ No newline at end of file diff --git a/govtool/backend/src/VVA/API.hs b/govtool/backend/src/VVA/API.hs index 64f6236dd..d13150d44 100644 --- a/govtool/backend/src/VVA/API.hs +++ b/govtool/backend/src/VVA/API.hs @@ -29,6 +29,7 @@ import Numeric.Natural (Natural) import Servant.API import Servant.Server +import System.Random (randomRIO) import Text.Read (readMaybe) @@ -105,7 +106,8 @@ mapDRepStatus Types.Inactive = Inactive drepRegistrationToDrep :: Types.DRepRegistration -> DRep drepRegistrationToDrep Types.DRepRegistration {..} = DRep - { dRepDrepId = DRepHash dRepRegistrationDRepHash, + { dRepIsScriptBased = dRepRegistrationIsScriptBased, + dRepDrepId = DRepHash dRepRegistrationDRepHash, dRepView = dRepRegistrationView, dRepUrl = dRepRegistrationUrl, dRepMetadataHash = dRepRegistrationDataHash, @@ -115,6 +117,7 @@ drepRegistrationToDrep Types.DRepRegistration {..} = dRepType = mapDRepType dRepRegistrationType, dRepLatestTxHash = HexText <$> dRepRegistrationLatestTxHash, dRepLatestRegistrationDate = dRepRegistrationLatestRegistrationDate, + dRepMetadataError = dRepRegistrationMetadataError, dRepPaymentAddress = dRepRegistrationPaymentAddress, dRepGivenName = dRepRegistrationGivenName, dRepObjectives = dRepRegistrationObjectives, @@ -129,6 +132,7 @@ delegationToResponse Types.Delegation {..} = DelegationResponse { delegationResponseDRepHash = HexText <$> delegationDRepHash, delegationResponseDRepView = delegationDRepView, + delegationResponseIsDRepScriptBased = delegationIsDRepScriptBased, delegationResponseTxHash = HexText delegationTxHash } @@ -136,23 +140,32 @@ delegationToResponse Types.Delegation {..} = 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 + dreps <- cacheRequest dRepListCache (fromMaybe "" mSearchQuery) (DRep.listDReps mSearchQuery) let filterDRepsByQuery = case mSearchQuery of - Nothing -> filter $ \Types.DRepRegistration {..} -> dRepRegistrationType == Types.DRep + Nothing -> filter $ \Types.DRepRegistration {..} -> + dRepRegistrationType == Types.DRep Just query -> filter $ \Types.DRepRegistration {..} -> - case dRepRegistrationType of - Types.SoleVoter -> query == dRepRegistrationView || query == dRepRegistrationDRepHash - Types.DRep -> query `isInfixOf` dRepRegistrationView - || query `isInfixOf` dRepRegistrationDRepHash + let searchLower = Text.toLower query + viewLower = Text.toLower dRepRegistrationView + hashLower = Text.toLower dRepRegistrationDRepHash + nameLower = maybe "" Text.toLower dRepRegistrationGivenName + in case dRepRegistrationType of + Types.SoleVoter -> searchLower == viewLower || searchLower == hashLower + Types.DRep -> searchLower `isInfixOf` viewLower + || searchLower `isInfixOf` hashLower + || searchLower `isInfixOf` nameLower let filterDRepsByStatus = case statuses of [] -> id _ -> filter $ \Types.DRepRegistration {..} -> mapDRepStatus dRepRegistrationStatus `elem` statuses + randomizedOrderList <- mapM (\_ -> randomRIO (0, 1 :: Double)) dreps + let sortDReps = case mSortMode of Nothing -> id + Just Random -> fmap snd . sortOn fst . Prelude.zip randomizedOrderList Just VotingPower -> sortOn $ \Types.DRepRegistration {..} -> Down dRepRegistrationVotingPower Just RegistrationDate -> sortOn $ \Types.DRepRegistration {..} -> @@ -281,7 +294,8 @@ drepInfo (unHexText -> dRepId) = do CacheEnv {dRepInfoCache} <- asks vvaCache Types.DRepInfo {..} <- cacheRequest dRepInfoCache dRepId $ DRep.getDRepInfo dRepId return $ DRepInfoResponse - { dRepInfoResponseIsRegisteredAsDRep = dRepInfoIsRegisteredAsDRep + { dRepInfoResponseIsScriptBased = dRepInfoIsScriptBased + , dRepInfoResponseIsRegisteredAsDRep = dRepInfoIsRegisteredAsDRep , dRepInfoResponseWasRegisteredAsDRep = dRepInfoWasRegisteredAsDRep , dRepInfoResponseIsRegisteredAsSoleVoter = dRepInfoIsRegisteredAsSoleVoter , dRepInfoResponseWasRegisteredAsSoleVoter = dRepInfoWasRegisteredAsSoleVoter @@ -349,20 +363,20 @@ listProposals selectedTypes sortMode mPage mPageSize mDrepRaw mSearchQuery = do map (voteParamsProposalId . voteResponseVote) <$> getVotes drepId [] Nothing Nothing - CacheEnv {proposalListCache} <- asks vvaCache - mappedAndSortedProposals <- do - proposals <- cacheRequest proposalListCache () Proposal.listProposals - mappedSortedAndFilteredProposals <- mapSortAndFilterProposals selectedTypes sortMode proposals - return $ filter - ( \p@ProposalResponse {proposalResponseId} -> - proposalResponseId `notElem` proposalsToRemove - && isProposalSearchedFor mSearchQuery p - ) mappedSortedAndFilteredProposals - - let total = length mappedAndSortedProposals :: Int - - let elements = take pageSize $ drop (page * pageSize) mappedAndSortedProposals + + proposals <- cacheRequest proposalListCache () (Proposal.listProposals mSearchQuery) + + mappedSortedAndFilteredProposals <- mapSortAndFilterProposals selectedTypes sortMode proposals + let filteredProposals = filter + ( \p@ProposalResponse {proposalResponseId} -> + proposalResponseId `notElem` proposalsToRemove + && isProposalSearchedFor mSearchQuery p + ) mappedSortedAndFilteredProposals + + let total = length filteredProposals :: Int + + let elements = take pageSize $ drop (page * pageSize) filteredProposals return $ ListProposalsResponse { listProposalsResponsePage = fromIntegral page diff --git a/govtool/backend/src/VVA/API/Types.hs b/govtool/backend/src/VVA/API/Types.hs index 308c17669..79b8915a0 100644 --- a/govtool/backend/src/VVA/API/Types.hs +++ b/govtool/backend/src/VVA/API/Types.hs @@ -202,7 +202,7 @@ instance ToParamSchema GovernanceActionType where & enum_ ?~ map toJSON (enumFromTo minBound maxBound :: [GovernanceActionType]) -data DRepSortMode = VotingPower | RegistrationDate | Status deriving (Bounded, Enum, Eq, Generic, Read, Show) +data DRepSortMode = Random | VotingPower | RegistrationDate | Status deriving (Bounded, Enum, Eq, Generic, Read, Show) instance FromJSON DRepSortMode where parseJSON (Aeson.String dRepSortMode) = pure $ fromJust $ readMaybe (Text.unpack dRepSortMode) @@ -561,7 +561,8 @@ instance ToSchema VoteResponse where data DRepInfoResponse = DRepInfoResponse - { dRepInfoResponseIsRegisteredAsDRep :: Bool + { dRepInfoResponseIsScriptBased :: Bool + , dRepInfoResponseIsRegisteredAsDRep :: Bool , dRepInfoResponseWasRegisteredAsDRep :: Bool , dRepInfoResponseIsRegisteredAsSoleVoter :: Bool , dRepInfoResponseWasRegisteredAsSoleVoter :: Bool @@ -758,7 +759,8 @@ instance ToSchema DRepType where data DRep = DRep - { dRepDrepId :: DRepHash + { dRepIsScriptBased :: Bool + , dRepDrepId :: DRepHash , dRepView :: Text , dRepUrl :: Maybe Text , dRepMetadataHash :: Maybe Text @@ -768,6 +770,7 @@ data DRep , dRepType :: DRepType , dRepLatestTxHash :: Maybe HexText , dRepLatestRegistrationDate :: UTCTime + , dRepMetadataError :: Maybe Text , dRepPaymentAddress :: Maybe Text , dRepGivenName :: Maybe Text , dRepObjectives :: Maybe Text @@ -854,9 +857,10 @@ instance ToSchema ListDRepsResponse where data DelegationResponse = DelegationResponse - { delegationResponseDRepHash :: Maybe HexText - , delegationResponseDRepView :: Text - , delegationResponseTxHash :: HexText + { delegationResponseDRepHash :: Maybe HexText + , delegationResponseDRepView :: Text + , delegationResponseIsDRepScriptBased :: Bool + , delegationResponseTxHash :: HexText } deriveJSON (jsonOptions "delegationResponse") ''DelegationResponse diff --git a/govtool/backend/src/VVA/AdaHolder.hs b/govtool/backend/src/VVA/AdaHolder.hs index c12a531c7..6a2ac3c33 100644 --- a/govtool/backend/src/VVA/AdaHolder.hs +++ b/govtool/backend/src/VVA/AdaHolder.hs @@ -29,9 +29,6 @@ import VVA.Types sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs -listDRepsSql :: SQL.Query -listDRepsSql = sqlFrom $(embedFile "sql/list-dreps.sql") - getCurrentDelegationSql :: SQL.Query getCurrentDelegationSql = sqlFrom $(embedFile "sql/get-current-delegation.sql") @@ -42,9 +39,9 @@ getCurrentDelegation :: getCurrentDelegation stakeKey = withPool $ \conn -> do result <- liftIO $ SQL.query conn getCurrentDelegationSql (SQL.Only stakeKey) case result of - [] -> return Nothing - [(mDRepHash, dRepView, txHash)] -> return $ Just $ Delegation mDRepHash dRepView txHash - _ -> error ("multiple delegations for stake key: " <> unpack stakeKey) + [] -> return Nothing + [(mDRepHash, dRepView, isDRepScriptBased, txHash)] -> return $ Just $ Delegation mDRepHash dRepView isDRepScriptBased txHash + _ -> error ("multiple delegations for stake key: " <> unpack stakeKey) getVotingPowerSql :: SQL.Query getVotingPowerSql = sqlFrom $(embedFile "sql/get-stake-key-voting-power.sql") diff --git a/govtool/backend/src/VVA/DRep.hs b/govtool/backend/src/VVA/DRep.hs index fd262bc94..fd4034c12 100644 --- a/govtool/backend/src/VVA/DRep.hs +++ b/govtool/backend/src/VVA/DRep.hs @@ -26,7 +26,6 @@ import qualified Data.Text.Encoding as Text import Data.Time import qualified Database.PostgreSQL.Simple as SQL - import VVA.Config import VVA.Pool (ConnectionPool, withPool) import qualified VVA.Proposal as Proposal @@ -36,34 +35,28 @@ import VVA.Types (AppError, DRepInfo (..), DRepRegist sqlFrom :: ByteString -> SQL.Query sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs -getVotingPowerSql :: SQL.Query -getVotingPowerSql = sqlFrom $(embedFile "sql/get-voting-power.sql") - -getVotingPower :: - (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadFail m) => - Text -> - m Integer -getVotingPower drepId = withPool $ \conn -> do - result <- - liftIO - (SQL.query @_ @(SQL.Only Scientific) conn getVotingPowerSql $ SQL.Only drepId) - case result of - [SQL.Only votingPower] -> return $ floor votingPower - [] -> return 0 - listDRepsSql :: SQL.Query listDRepsSql = sqlFrom $(embedFile "sql/list-dreps.sql") listDReps :: (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m) => - m [DRepRegistration] -listDReps = withPool $ \conn -> do - results <- liftIO $ SQL.query_ conn listDRepsSql + Maybe Text -> m [DRepRegistration] +listDReps mSearchQuery = withPool $ \conn -> do + let searchParam = fromMaybe "" mSearchQuery + results <- liftIO $ SQL.query conn listDRepsSql + ( searchParam + , searchParam + , searchParam + , searchParam + , "%" <> searchParam <> "%" + , "%" <> searchParam <> "%" + ) timeZone <- liftIO getCurrentTimeZone return - [ DRepRegistration drepHash drepView url dataHash (floor @Scientific deposit) votingPower status drepType txHash (localTimeToUTC timeZone date) paymentAddress givenName objectives motivations qualifications imageUrl imageHash + [ DRepRegistration drepHash drepView isScriptBased url dataHash (floor @Scientific deposit) votingPower status drepType txHash (localTimeToUTC timeZone date) metadataError paymentAddress givenName objectives motivations qualifications imageUrl imageHash | ( drepHash , drepView + , isScriptBased , url , dataHash , deposit @@ -73,6 +66,7 @@ listDReps = withPool $ \conn -> do , date , latestDeposit , latestNonDeregisterVotingAnchorWasNotNull + , metadataError , paymentAddress , givenName , objectives @@ -93,6 +87,19 @@ listDReps = withPool $ \conn -> do | Data.Maybe.isJust url = DRep ] +getVotingPowerSql :: SQL.Query +getVotingPowerSql = sqlFrom $(embedFile "sql/get-voting-power.sql") + +getVotingPower :: + (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadFail m) => + Text -> + m Integer +getVotingPower drepId = withPool $ \conn -> do + result <- liftIO (SQL.query @_ @(SQL.Only Scientific) conn getVotingPowerSql $ SQL.Only drepId) + case result of + [SQL.Only votingPower] -> return $ floor votingPower + [] -> return 0 + getVotesSql :: SQL.Query getVotesSql = sqlFrom $(embedFile "sql/get-votes.sql") @@ -103,17 +110,23 @@ getVotes :: m ([Vote], [Proposal]) getVotes drepId selectedProposals = withPool $ \conn -> do results <- liftIO $ SQL.query conn getVotesSql (SQL.Only drepId) - let proposalsToSelect = if null selectedProposals - then [ govActionId | (_, govActionId, _, _, _, _, _, _, _) <- results] - else selectedProposals - proposals <- Proposal.getProposals (Just proposalsToSelect) - let proposalMap = M.fromList $ map (\x -> (proposalId x, x)) proposals - timeZone <- liftIO getCurrentTimeZone - return - ([ Vote proposalId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') voteTxHash' - | (proposalId', govActionId', drepId', vote', url', docHash', epochNo', date', voteTxHash') <- results - , govActionId' `elem` proposalsToSelect - ], proposals) + + if null results + then return ([], []) + else do + let proposalsToSelect = if null selectedProposals + then [ govActionId | (_, govActionId, _, _, _, _, _, _, _) <- results] + else selectedProposals + proposals <- if null proposalsToSelect + then return [] + else Proposal.getProposals (Just proposalsToSelect) + let proposalMap = M.fromList $ map (\x -> (proposalId x, x)) proposals + timeZone <- liftIO getCurrentTimeZone + let votes = [ Vote proposalId' drepId' vote' url' docHash' epochNo' (localTimeToUTC timeZone date') voteTxHash' + | (proposalId', govActionId', drepId', vote', url', docHash', epochNo', date', voteTxHash') <- results + , govActionId' `elem` proposalsToSelect + ] + return (votes, proposals) getDRepInfoSql :: SQL.Query getDRepInfoSql = sqlFrom $(embedFile "sql/get-drep-info.sql") @@ -131,7 +144,8 @@ getDRepInfo getDRepInfo drepId = withPool $ \conn -> do result <- liftIO $ SQL.query conn getDRepInfoSql (SQL.Only drepId) case result of - [ ( isRegisteredAsDRep + [ ( isScriptBased + , isRegisteredAsDRep , wasRegisteredAsDRep , isRegisteredAsSoleVoter , wasRegisteredAsSoleVoter @@ -152,7 +166,8 @@ getDRepInfo drepId = withPool $ \conn -> do , imageHash )] -> return $ DRepInfo - { dRepInfoIsRegisteredAsDRep = fromMaybe False isRegisteredAsDRep + { dRepInfoIsScriptBased = isScriptBased + , dRepInfoIsRegisteredAsDRep = fromMaybe False isRegisteredAsDRep , dRepInfoWasRegisteredAsDRep = fromMaybe False wasRegisteredAsDRep , dRepInfoIsRegisteredAsSoleVoter = fromMaybe False isRegisteredAsSoleVoter , dRepInfoWasRegisteredAsSoleVoter = fromMaybe False wasRegisteredAsSoleVoter @@ -172,4 +187,4 @@ getDRepInfo drepId = withPool $ \conn -> do , dRepInfoImageUrl = imageUrl , dRepInfoImageHash = imageHash } - [] -> return $ DRepInfo False False False False Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing + [] -> return $ DRepInfo False False False False False Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing diff --git a/govtool/backend/src/VVA/Proposal.hs b/govtool/backend/src/VVA/Proposal.hs index bc9fb6a2d..328b091eb 100644 --- a/govtool/backend/src/VVA/Proposal.hs +++ b/govtool/backend/src/VVA/Proposal.hs @@ -28,10 +28,9 @@ import qualified Data.Text.IO as Text import Data.Time import qualified Database.PostgreSQL.Simple as SQL - -import qualified GHC.Generics as SQL - -import Text.Read (readMaybe) +import qualified Database.PostgreSQL.Simple.Types as PG +import Database.PostgreSQL.Simple.ToField (ToField(..)) +import Database.PostgreSQL.Simple.ToRow (ToRow(..)) import VVA.Config import VVA.Pool (ConnectionPool, withPool) @@ -43,10 +42,15 @@ sqlFrom bs = fromString $ unpack $ Text.decodeUtf8 bs listProposalsSql :: SQL.Query listProposalsSql = sqlFrom $(embedFile "sql/list-proposals.sql") +newtype TextArray = TextArray [Text] + +instance ToRow TextArray where + toRow (TextArray texts) = map toField texts + listProposals :: (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadFail m, MonadError AppError m) => - m [Proposal] -listProposals = getProposals Nothing + Maybe Text -> m [Proposal] +listProposals mSearch = getProposals (fmap (:[]) mSearch) getProposal :: (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadFail m, MonadError AppError m) => @@ -54,17 +58,23 @@ getProposal :: Integer -> m Proposal getProposal txHash index = do - result <- getProposals (Just [txHash <> "#" <> pack (show index)]) + let proposalId = txHash <> "#" <> pack (show index) + result <- getProposals (Just [proposalId]) case result of - [] -> throwError $ NotFoundError ("Proposal with id: " <> txHash <> "#" <> pack (show index) <> " not found") + [] -> throwError $ NotFoundError ("Proposal with id: " <> proposalId <> " not found") [a] -> return a - _ -> throwError $ CriticalError ("Multiple proposal found for id: " <> txHash <> "#" <> pack (show index) <> ". This should never happen") + _ -> throwError $ CriticalError ("Multiple proposals found for id: " <> proposalId <> ". This should never happen") getProposals :: (Has ConnectionPool r, Has VVAConfig r, MonadReader r m, MonadIO m, MonadFail m, MonadError AppError m) => - Maybe [Text] -> - m [Proposal] -getProposals mProposalIds = withPool $ \conn -> - liftIO $ case mProposalIds of - Nothing -> SQL.query @(Bool, SQL.In [Text]) conn listProposalsSql (False, SQL.In []) - Just proposalIds -> SQL.query conn listProposalsSql (True, SQL.In proposalIds) + Maybe [Text] -> m [Proposal] +getProposals mSearchTerms = withPool $ \conn -> do + let searchParam = maybe "" head mSearchTerms + liftIO $ SQL.query conn listProposalsSql + ( searchParam + , "%" <> searchParam <> "%" + , "%" <> searchParam <> "%" + , "%" <> searchParam <> "%" + , "%" <> searchParam <> "%" + , "%" <> searchParam <> "%" + ) diff --git a/govtool/backend/src/VVA/Types.hs b/govtool/backend/src/VVA/Types.hs index d3965ff34..c67e70189 100644 --- a/govtool/backend/src/VVA/Types.hs +++ b/govtool/backend/src/VVA/Types.hs @@ -72,7 +72,8 @@ data Vote data DRepInfo = DRepInfo - { dRepInfoIsRegisteredAsDRep :: Bool + { dRepInfoIsScriptBased :: Bool + , dRepInfoIsRegisteredAsDRep :: Bool , dRepInfoWasRegisteredAsDRep :: Bool , dRepInfoIsRegisteredAsSoleVoter :: Bool , dRepInfoWasRegisteredAsSoleVoter :: Bool @@ -101,6 +102,7 @@ data DRepRegistration = DRepRegistration { dRepRegistrationDRepHash :: Text , dRepRegistrationView :: Text + , dRepRegistrationIsScriptBased :: Bool , dRepRegistrationUrl :: Maybe Text , dRepRegistrationDataHash :: Maybe Text , dRepRegistrationDeposit :: Integer @@ -109,6 +111,7 @@ data DRepRegistration , dRepRegistrationType :: DRepType , dRepRegistrationLatestTxHash :: Maybe Text , dRepRegistrationLatestRegistrationDate :: UTCTime + , dRepRegistrationMetadataError :: Maybe Text , dRepRegistrationPaymentAddress :: Maybe Text , dRepRegistrationGivenName :: Maybe Text , dRepRegistrationObjectives :: Maybe Text @@ -193,7 +196,7 @@ data CacheEnv , dRepGetVotesCache :: Cache.Cache Text ([Vote], [Proposal]) , dRepInfoCache :: Cache.Cache Text DRepInfo , dRepVotingPowerCache :: Cache.Cache Text Integer - , dRepListCache :: Cache.Cache () [DRepRegistration] + , dRepListCache :: Cache.Cache Text [DRepRegistration] , networkMetricsCache :: Cache.Cache () NetworkMetrics } @@ -214,7 +217,8 @@ data NetworkMetrics data Delegation = Delegation - { delegationDRepHash :: Maybe Text - , delegationDRepView :: Text - , delegationTxHash :: Text + { delegationDRepHash :: Maybe Text + , delegationDRepView :: Text + , delegationIsDRepScriptBased :: Bool + , delegationTxHash :: Text } diff --git a/govtool/backend/vva-be.cabal b/govtool/backend/vva-be.cabal index 97e5c5692..c8fa636c0 100644 --- a/govtool/backend/vva-be.cabal +++ b/govtool/backend/vva-be.cabal @@ -1,6 +1,6 @@ cabal-version: 3.6 name: vva-be -version: 1.0.17 +version: 1.0.26 -- A short (one-line) description of the package. -- synopsis: @@ -103,6 +103,7 @@ library , http-client-tls , vector , async + , random exposed-modules: VVA.Config , VVA.CommandLine diff --git a/govtool/frontend/Dockerfile.qovey b/govtool/frontend/Dockerfile.qovey new file mode 100644 index 000000000..33e83cbe9 --- /dev/null +++ b/govtool/frontend/Dockerfile.qovey @@ -0,0 +1,31 @@ +FROM node:18-alpine as builder + +ARG VITE_APP_ENV='beta' +ARG VITE_BASE_URL +ARG VITE_METADATA_API_URL +ARG VITE_GTM_ID +ARG VITE_NETWORK_FLAG=0 +ARG VITE_SENTRY_DSN +ARG NPMRC_TOKEN +ARG VITE_USERSNAP_SPACE_API_KEY +ARG VITE_IS_PROPOSAL_DISCUSSION_FORUM_ENABLED='true' +ARG VITE_PDF_API_URL + +ENV NODE_OPTIONS=--max_old_space_size=8192 + +WORKDIR /src + +# Set npm configuration settings using environment variables +RUN npm config set @intersect.mbo:registry "https://registry.npmjs.org/" --location=global +RUN npm config set //registry.npmjs.org/:_authToken ${NPMRC_TOKEN} --location=global + +COPY package.json package-lock.json ./ +RUN npm install +COPY . . +RUN npm run build + +FROM nginx:stable-alpine +EXPOSE 80 +COPY nginx.conf /etc/nginx/conf.d/default.conf +COPY --from=builder /src/maintenance-page/index.html /usr/share/nginx/html/maintenance.html +COPY --from=builder /src/dist /usr/share/nginx/html diff --git a/govtool/frontend/index.html b/govtool/frontend/index.html index 8032b4a36..cb8734a1b 100644 --- a/govtool/frontend/index.html +++ b/govtool/frontend/index.html @@ -24,6 +24,21 @@ +
diff --git a/govtool/frontend/junit-report.xml b/govtool/frontend/junit-report.xml index 2f3434f92..1396b87e1 100644 --- a/govtool/frontend/junit-report.xml +++ b/govtool/frontend/junit-report.xml @@ -1,230 +1,256 @@ - - - + + + - - - + - + - + + + - + - + - + - + - - + + - + - + - - - + + + - + + + - + - + - + - - - + - - + + - + - + - + - - + + - + - + - - + + - + + + - + - + - + - + - + - + - + - + + + - + - + - + + + - + - + + + - + - - - - + + - + - - + + - - - - - - + + - + - + - + - + + + - + - - + + - + - + - + - + + + - + - - + + - + - + - + + + - - + + - + - + - + - + - - + + - + + + - - + + - + + + - + - + - - + + - + - + + + + +Error: Failed to get PubDRepKey + at [90m/Users/asiadyczka/Desktop/govtool/govtool/frontend/[39msrc/utils/tests/getDRepID.test.ts:46:40 + at [90mfile:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4m@vitest[24m/runner/dist/index.js:135:14 + at [90mfile:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4m@vitest[24m/runner/dist/index.js:60:26 + at runTest [90m(file:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4m@vitest[24m/runner/dist/index.js:781:17[90m)[39m +[90m at processTicksAndRejections (node:internal/process/task_queues:95:5)[39m + at runSuite [90m(file:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4m@vitest[24m/runner/dist/index.js:909:15[90m)[39m + at runSuite [90m(file:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4m@vitest[24m/runner/dist/index.js:909:15[90m)[39m + at runFiles [90m(file:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4m@vitest[24m/runner/dist/index.js:958:5[90m)[39m + at startTests [90m(file:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4m@vitest[24m/runner/dist/index.js:967:3[90m)[39m + at [90mfile:///Users/asiadyczka/Desktop/govtool/govtool/frontend/[39mnode_modules/[4mvitest[24m/dist/chunks/runtime-runBaseTests.oAvMKtQC.js:116:7 + + - - + + - + - + - - - + - + - + + + - + - - - + + + - - + + - + - - - + - + - + @@ -234,88 +260,112 @@ - - + + - + - - - + - + - - + + - + - - - + - + - + - - - + - + - + - + - - - + - + + + + + + + + + + + - - + + - + - + + + + + - - + + - + - + - + + + + + - - + + - + - + - + + + - + + + + + - - + + - + - + - + - + + + + + + + + + diff --git a/govtool/frontend/package-lock.json b/govtool/frontend/package-lock.json index fdb9a75d1..395102589 100644 --- a/govtool/frontend/package-lock.json +++ b/govtool/frontend/package-lock.json @@ -1,20 +1,20 @@ { "name": "@govtool/frontend", - "version": "1.0.17", + "version": "1.0.26", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@govtool/frontend", - "version": "1.0.17", + "version": "1.0.26", "hasInstallScript": true, "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@emurgo/cardano-serialization-lib-asmjs": "12.0.0-beta.2", + "@emurgo/cardano-serialization-lib-asmjs": "^12.1.0", "@hookform/resolvers": "^3.3.1", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "^0.3.8", + "@intersect.mbo/pdf-ui": "^0.4.0", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@rollup/plugin-babel": "^6.0.4", @@ -2901,10 +2901,9 @@ "license": "MIT" }, "node_modules/@emurgo/cardano-serialization-lib-asmjs": { - "version": "12.0.0-beta.2", - "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-12.0.0-beta.2.tgz", - "integrity": "sha512-WVM/TDBb2/Pvq/PxKkoMFz/iuxXfFDuBbD1dem+olg4kqut7x2Fu5SM/iNmKvZF3DbiuA7fQVTabj14i7C9g+w==", - "license": "MIT" + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-asmjs/-/cardano-serialization-lib-asmjs-12.1.0.tgz", + "integrity": "sha512-LVClEWmZmTvaXTBUpSOdyAYr10PlnfEycG1Z5m96aHWN08mdQH/x2LyZxuCjWVSusrG3ZwFnsOYZCHLel7tu4g==" }, "node_modules/@esbuild/aix-ppc64": { "version": "0.19.12", @@ -3551,9 +3550,9 @@ "license": "ISC" }, "node_modules/@intersect.mbo/pdf-ui": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.3.8.tgz", - "integrity": "sha512-eI8Uo2b6Byi7kQKOYA7TGCj3h96P4WP+kv0ZHKCPs/yVR6w1IGQHrXmBM9B0SlXh/gAQvW/kzTeZyL+nnL5ZOg==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@intersect.mbo/pdf-ui/-/pdf-ui-0.4.0.tgz", + "integrity": "sha512-0Wc0fze9zFy7wCGVaDMdWf5eI9flwLHnUDlGQYKEFi7y5DM2uH2rT1OSFw7h2uAnfjZJ0w3pivI1TjPi3HkAIg==", "dependencies": { "@emurgo/cardano-serialization-lib-asmjs": "^12.0.0-beta.2", "@fontsource/poppins": "^5.0.14", diff --git a/govtool/frontend/package.json b/govtool/frontend/package.json index 541024da5..dc3cb3c1c 100644 --- a/govtool/frontend/package.json +++ b/govtool/frontend/package.json @@ -1,7 +1,7 @@ { "name": "@govtool/frontend", "private": true, - "version": "1.0.17", + "version": "1.0.26", "type": "module", "scripts": { "build": "vite build", @@ -25,10 +25,10 @@ "dependencies": { "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", - "@emurgo/cardano-serialization-lib-asmjs": "12.0.0-beta.2", + "@emurgo/cardano-serialization-lib-asmjs": "^12.1.0", "@hookform/resolvers": "^3.3.1", "@intersect.mbo/intersectmbo.org-icons-set": "^1.0.8", - "@intersect.mbo/pdf-ui": "^0.3.8", + "@intersect.mbo/pdf-ui": "^0.4.0", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.4", "@rollup/plugin-babel": "^6.0.4", @@ -109,5 +109,5 @@ "typescript": "^5.0.2" }, "readme": "ERROR: No README data found!", - "_id": "govtool@1.0.17" + "_id": "govtool@1.0.26" } diff --git a/govtool/frontend/src/App.tsx b/govtool/frontend/src/App.tsx index 9b667cb04..553d5295f 100644 --- a/govtool/frontend/src/App.tsx +++ b/govtool/frontend/src/App.tsx @@ -6,7 +6,6 @@ import { PATHS, PDF_PATHS } from "@consts"; import { useCardano, useFeatureFlag, useModal } from "@context"; import { useWalletConnectionListener } from "@hooks"; import { - DashboardCards, DashboardGovernanceActions, DashboardGovernanceActionDetails, } from "@organisms"; @@ -39,6 +38,7 @@ import { } from "@utils"; import { PublicRoute } from "./pages/PublicRoute"; import { TopBanners } from "./components/organisms/TopBanners"; +import { DashboardHome } from "./pages/DashboardHome"; export default () => { const { isProposalDiscussionForumEnabled } = useFeatureFlag(); @@ -104,7 +104,7 @@ export default () => { /> )} }> - } /> + } /> {isProposalDiscussionForumEnabled && ( errorMessage && ( = ({ ...props }) => { palette: { boxShadow1 }, } = theme; const { isMobile } = useScreenDimension(); - // const { powerIsLoading, votingPower } = - // useGetAdaHolderVotingPowerQuery(stakeKey); - // const { t } = useTranslation(); + const { powerIsLoading, votingPower } = + useGetAdaHolderVotingPowerQuery(stakeKey); + const { t } = useTranslation(); return ( = ({ ...props }) => { /> - {/* TODO uncomment when voting power value s correct */} - {/* + - {t("votingPower")} - : + {t("votingPower")}: {powerIsLoading ? ( @@ -82,7 +85,7 @@ export const StakeRadio: FC = ({ ...props }) => { ₳ {correctAdaFormat(votingPower) ?? 0} )} - */} + ); diff --git a/govtool/frontend/src/components/atoms/VotePill.test.tsx b/govtool/frontend/src/components/atoms/VotePill.test.tsx index 24f09a4d0..69bae3985 100644 --- a/govtool/frontend/src/components/atoms/VotePill.test.tsx +++ b/govtool/frontend/src/components/atoms/VotePill.test.tsx @@ -5,7 +5,7 @@ import { VotePill } from "@atoms"; describe("VotePill", () => { it('renders the VotePill component with "yes" vote correctly', () => { const { getByText } = render(); - const voteText = getByText("yes"); + const voteText = getByText("Yes"); expect(voteText).toBeInTheDocument(); expect(voteText.parentNode).toHaveStyle({ borderColor: "#C0E4BA", @@ -15,7 +15,7 @@ describe("VotePill", () => { it('renders the VotePill component with "no" vote correctly', () => { const { getByText } = render(); - const voteText = getByText("no"); + const voteText = getByText("No"); expect(voteText).toBeInTheDocument(); expect(voteText.parentNode).toHaveStyle({ borderColor: "#EDACAC", @@ -25,7 +25,7 @@ describe("VotePill", () => { it('renders the VotePill component with "abstain" vote correctly', () => { const { getByText } = render(); - const voteText = getByText("abstain"); + const voteText = getByText("Abstain"); expect(voteText).toBeInTheDocument(); expect(voteText.parentNode).toHaveStyle({ borderColor: "#99ADDE", diff --git a/govtool/frontend/src/components/atoms/VotePill.tsx b/govtool/frontend/src/components/atoms/VotePill.tsx index de29ac900..cd37c9e02 100644 --- a/govtool/frontend/src/components/atoms/VotePill.tsx +++ b/govtool/frontend/src/components/atoms/VotePill.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from "react-i18next"; import { Box, Typography } from "@mui/material"; import { Vote } from "@models"; @@ -6,12 +7,15 @@ export const VotePill = ({ vote, width, maxWidth, + isCC, }: { vote: Vote; width?: number; maxWidth?: number; + isCC?: boolean; }) => { - const VOTE = vote.toLowerCase(); + const { t } = useTranslation(); + const VOTE = vote.toLowerCase() as "yes" | "no" | "abstain"; return ( - {vote} + {t( + `votes.${ + isCC + ? VOTE === "yes" + ? "constitutional" + : vote === "no" + ? "unconstitutional" + : VOTE + : VOTE + }`, + )} ); diff --git a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx index 159859c90..fa11d764c 100644 --- a/govtool/frontend/src/components/atoms/VotingPowerChips.tsx +++ b/govtool/frontend/src/components/atoms/VotingPowerChips.tsx @@ -33,6 +33,7 @@ export const VotingPowerChips = ({ height: isMobile ? 16 : 24, px: 2, py: isMobile ? 1 : 1.5, + maxHeight: "14px", }} > {!isMobile && ( diff --git a/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx b/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx index 807cded95..772063fc5 100644 --- a/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx +++ b/govtool/frontend/src/components/atoms/modal/ModalWrapper.tsx @@ -32,6 +32,8 @@ export const BaseWrapper = styled("div")>` width: 80vw; max-width: 510px; padding: 52px 24px 34px 24px; + max-height: 80vh; + overflow: auto; `; } if (variant === "popup") { diff --git a/govtool/frontend/src/components/atoms/types.ts b/govtool/frontend/src/components/atoms/types.ts index 97307a5f3..52bf82151 100644 --- a/govtool/frontend/src/components/atoms/types.ts +++ b/govtool/frontend/src/components/atoms/types.ts @@ -57,11 +57,13 @@ export type CheckboxProps = Omit & { }; export type FormErrorMessageProps = { + dataTestId?: string; errorMessage?: string; errorStyles?: MUITypographyProps; }; export type FormHelpfulTextProps = { + dataTestId?: string; helpfulText?: string; helpfulTextStyle?: MUITypographyProps; sx?: SxProps; diff --git a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx index 855631938..729a02523 100644 --- a/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx +++ b/govtool/frontend/src/components/molecules/AutomatedVotingCard.tsx @@ -1,4 +1,4 @@ -import { Box } from "@mui/material"; +import { Box, Divider } from "@mui/material"; import { Button, Typography } from "@atoms"; import { primaryBlue } from "@consts"; @@ -20,6 +20,7 @@ export const AutomatedVotingCard = ({ onClickInfo, title, transactionId, + votingPower, }: AutomatedVotingCardProps) => { const { cExplorerBaseUrl } = useAppContext(); const { isMobile, screenWidth } = useScreenDimension(); @@ -83,8 +84,7 @@ export const AutomatedVotingCard = ({ {!inProgress && !isSelected && ( <> - {/* TODO uncomment when voting power value s correct */} - {/* */} + /> diff --git a/govtool/frontend/src/components/molecules/DRepInfoCard.tsx b/govtool/frontend/src/components/molecules/DRepInfoCard.tsx index 00663ea63..f1ac53a8d 100644 --- a/govtool/frontend/src/components/molecules/DRepInfoCard.tsx +++ b/govtool/frontend/src/components/molecules/DRepInfoCard.tsx @@ -5,18 +5,24 @@ import { CopyButton } from "@atoms"; import { useTranslation } from "@hooks"; import { Card } from "./Card"; import { gray } from "@/consts"; +import { useGetDRepDetailsQuery } from "@/hooks"; export const DRepInfoCard = () => { - const { dRepIDBech32 } = useCardano(); + const { dRepID } = useCardano(); + const { dRep } = useGetDRepDetailsQuery(dRepID); const { t } = useTranslation(); + if (!dRep) { + return null; + } + return ( {t("myDRepId")} - + { fontSize={14} fontWeight={500} > - {dRepIDBech32} + {dRep.view} diff --git a/govtool/frontend/src/components/molecules/DataActionsBar.tsx b/govtool/frontend/src/components/molecules/DataActionsBar.tsx index f66274ced..c2839ae78 100644 --- a/govtool/frontend/src/components/molecules/DataActionsBar.tsx +++ b/govtool/frontend/src/components/molecules/DataActionsBar.tsx @@ -25,7 +25,6 @@ type DataActionsBarProps = { setFiltersOpen?: Dispatch>; setSearchText: Dispatch>; setSortOpen: Dispatch>; - sortingActive: boolean; sortOpen: boolean; sortOptions?: { key: string; @@ -50,7 +49,6 @@ export const DataActionsBar: FC = ({ ...props }) => { setFiltersOpen, setSearchText, setSortOpen, - sortingActive, sortOpen, sortOptions = [], } = props; @@ -94,8 +92,8 @@ export const DataActionsBar: FC = ({ ...props }) => { filtersOpen={filtersOpen} isFiltering={isFiltering} setFiltersOpen={setFiltersOpen} + chosenSorting={chosenSorting} setSortOpen={setSortOpen} - sortingActive={sortingActive} sortOpen={sortOpen} > {filtersOpen && ( diff --git a/govtool/frontend/src/components/molecules/DataMissingHeader.tsx b/govtool/frontend/src/components/molecules/DataMissingHeader.tsx index 7e19844e9..6267d8742 100644 --- a/govtool/frontend/src/components/molecules/DataMissingHeader.tsx +++ b/govtool/frontend/src/components/molecules/DataMissingHeader.tsx @@ -1,13 +1,11 @@ import { Box, SxProps } from "@mui/material"; import { Typography } from "@atoms"; -import { Share } from "@molecules"; import { MetadataValidationStatus } from "@models"; import { getMetadataDataMissingStatusTranslation } from "@/utils"; type DataMissingHeaderProps = { isDataMissing: MetadataValidationStatus | null; - shareLink?: string; title?: string; titleStyle?: SxProps; }; @@ -15,7 +13,6 @@ type DataMissingHeaderProps = { export const DataMissingHeader = ({ title, isDataMissing, - shareLink, titleStyle, }: DataMissingHeaderProps) => ( - {shareLink && } ); diff --git a/govtool/frontend/src/components/molecules/Field/Input.tsx b/govtool/frontend/src/components/molecules/Field/Input.tsx index fac305b1f..9d3b1f4df 100644 --- a/govtool/frontend/src/components/molecules/Field/Input.tsx +++ b/govtool/frontend/src/components/molecules/Field/Input.tsx @@ -14,8 +14,10 @@ import { InputFieldProps } from "./types"; export const Input = forwardRef( ( { + errorDataTestId, errorMessage, errorStyles, + helpfulTextDataTestId, helpfulText, helpfulTextStyle, label, @@ -71,10 +73,12 @@ export const Input = forwardRef( ref={inputRef} /> diff --git a/govtool/frontend/src/components/molecules/Field/types.ts b/govtool/frontend/src/components/molecules/Field/types.ts index 56912fbc4..4c59a6a93 100644 --- a/govtool/frontend/src/components/molecules/Field/types.ts +++ b/govtool/frontend/src/components/molecules/Field/types.ts @@ -8,8 +8,10 @@ import { } from "@atoms"; export type InputFieldProps = InputProps & { + errorDataTestId?: string; errorMessage?: string; errorStyles?: MUITypographyProps; + helpfulTextDataTestId?: string; helpfulText?: string; helpfulTextStyle?: MUITypographyProps; label?: string; diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx index 89b802254..51ae0fdf9 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCard.tsx @@ -11,6 +11,7 @@ import { import { useScreenDimension, useTranslation } from "@hooks"; import { + encodeCIP129Identifier, getFullGovActionId, getProposalTypeLabel, getProposalTypeNoEmptySpaces, @@ -53,6 +54,11 @@ export const GovernanceActionCard: FC = ({ ...props }) => { const { t } = useTranslation(); const govActionId = getFullGovActionId(txHash, index); + const cip129GovernanceActionId = encodeCIP129Identifier({ + txID: txHash, + index: index.toString(16).padStart(2, "0"), + bech32Prefix: "gov_action", + }); return ( = ({ ...props }) => { title={title} isDataMissing={metadataStatus} /> - + {!metadataStatus && ( + + )} = ({ ...props }) => { /> + diff --git a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx index 4fc52f937..4ff9709be 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionCardElement.tsx @@ -108,9 +108,11 @@ export const GovernanceActionCardElement = ({ {isMarkdown ? ( diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx index 1615a2716..e6ee3cdaf 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardLinks.tsx @@ -4,12 +4,11 @@ import { Typography } from "@atoms"; import { ICONS } from "@consts"; import { useModal } from "@context"; import { useScreenDimension, useTranslation } from "@hooks"; -import { LinkWithIcon } from "@molecules"; export const GovernanceActionDetailsCardLinks = ({ links, }: { - links?: string[]; + links?: Reference[]; }) => { const { isMobile } = useScreenDimension(); const { t } = useTranslation(); @@ -41,21 +40,58 @@ export const GovernanceActionDetailsCardLinks = ({ rowGap: 2, }} > - {links.map((link) => ( - { - openModal({ - type: "externalLink", - state: { - externalLink: link, - }, - }); - }} - icon={link} - cutWithEllipsis - /> + {links.map(({ uri, label }) => ( + + {label && ( + + {label} + + )} + + {uri} + + {label && ( + + link { + openModal({ + type: "externalLink", + state: { + externalLink: uri, + }, + }); + }} + /> + + )} + ))} diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardOnChainData.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardOnChainData.tsx index 8cb0230c2..72d594189 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardOnChainData.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardOnChainData.tsx @@ -1,3 +1,4 @@ +import { ReactNode } from "react"; import { Box } from "@mui/material"; import { Typography } from "@atoms"; @@ -65,7 +66,7 @@ export const GovernanceActionDetailsCardOnChainData = ({ ml: 0.5, }} > - {content} + {content as ReactNode} ))} diff --git a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardVotes.tsx b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardVotes.tsx index f56c6d8d9..cdb211e7d 100644 --- a/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardVotes.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceActionDetailsCardVotes.tsx @@ -1,10 +1,11 @@ -import { Dispatch, SetStateAction } from "react"; +import { Dispatch, SetStateAction, useCallback } from "react"; import { Box } from "@mui/material"; import { useScreenDimension } from "@hooks"; import { VoteActionForm, VotesSubmitted } from "@molecules"; import { useFeatureFlag } from "@/context"; import { ProposalData, ProposalVote } from "@/models"; +import { SECURITY_RELEVANT_PARAMS_MAP } from "@/consts"; type GovernanceActionCardVotesProps = { setIsVoteSubmitted: Dispatch>; @@ -23,24 +24,20 @@ export const GovernanceActionDetailsCardVotes = ({ vote, isDashboard, isInProgress, - proposal: { - dRepAbstainVotes, - dRepNoVotes, - dRepYesVotes, - poolAbstainVotes, - poolNoVotes, - poolYesVotes, - ccAbstainVotes, - ccNoVotes, - ccYesVotes, - expiryDate, - expiryEpochNo, - type, - }, + proposal, }: GovernanceActionCardVotesProps) => { - const { isVotingOnGovernanceActionEnabled } = useFeatureFlag(); + const { areDRepVoteTotalsDisplayed } = useFeatureFlag(); const { screenWidth } = useScreenDimension(); - + const isSecurityGroup = useCallback( + () => + Object.values(SECURITY_RELEVANT_PARAMS_MAP).some( + (paramKey) => + proposal.protocolParams?.[ + paramKey as keyof typeof proposal.protocolParams + ] !== null, + ), + [proposal.protocolParams], + ); const isModifiedPadding = (isDashboard && screenWidth < 1368) ?? screenWidth < 1100; @@ -52,29 +49,16 @@ export const GovernanceActionDetailsCardVotes = ({ p: `40px ${isModifiedPadding ? "24px" : "80px"}`, }} > - {isVoter && isVotingOnGovernanceActionEnabled(type) ? ( + {isVoter && + areDRepVoteTotalsDisplayed(proposal.type, isSecurityGroup()) ? ( ) : ( - + )} ); diff --git a/govtool/frontend/src/components/molecules/GovernanceActionNewCommitteeDetailsTabContent.tsx b/govtool/frontend/src/components/molecules/GovernanceActionNewCommitteeDetailsTabContent.tsx new file mode 100644 index 000000000..920b72b08 --- /dev/null +++ b/govtool/frontend/src/components/molecules/GovernanceActionNewCommitteeDetailsTabContent.tsx @@ -0,0 +1,210 @@ +import { useTranslation } from "react-i18next"; +import { Box } from "@mui/material"; + +import { ProposalData } from "@/models"; +import { encodeCIP129Identifier } from "@/utils"; + +import { CopyButton, Typography } from "../atoms"; + +type CCMember = { + expirationEpoch: number; + hasScript: boolean; + hash: string; + newExpirationEpoch?: number; +}; + +export const GovernanceActionNewCommitteeDetailsTabContent = ({ + details, +}: Pick) => { + const { t } = useTranslation(); + const membersToBeAdded = ((details?.members as CCMember[]) || []) + .filter((member) => member.newExpirationEpoch === undefined) + .map((member) => ({ + cip129Identifier: encodeCIP129Identifier({ + txID: member.hash, + bech32Prefix: member.hasScript ? "cc_hot" : "cc_cold", + }), + expirationEpoch: member.expirationEpoch, + })); + + const membersToBeUpdated = ((details?.members as CCMember[]) || []) + .filter((member) => member.newExpirationEpoch !== undefined) + .map((member) => ({ + cip129Identifier: encodeCIP129Identifier({ + txID: member.hash, + bech32Prefix: member.hasScript ? "cc_hot" : "cc_cold", + }), + expirationEpoch: member.expirationEpoch, + newExpirationEpoch: member.newExpirationEpoch, + })); + + return ( + + {membersToBeAdded.length > 0 && ( + + + {t("govActions.membersToBeAdded")} + + {membersToBeAdded.map(({ cip129Identifier }) => ( + + + {cip129Identifier} + + + + + + ))} + + )} + {(details?.membersToBeRemoved as string[]).length > 0 && ( + + + {t("govActions.membersToBeRemoved")} + + {(details?.membersToBeRemoved as string[]).map((hash) => ( + + + {encodeCIP129Identifier({ + txID: hash, + bech32Prefix: "cc_cold", + })} + + + + + + ))} + + )} + + {membersToBeUpdated.length > 0 && ( + + + {t("govActions.changeToTermsOfExistingMembers")} + + {membersToBeUpdated.map( + ({ cip129Identifier, newExpirationEpoch, expirationEpoch }) => ( + <> + + + {cip129Identifier} + + + + + + + {t("govActions.changeToTermsEpochs", { + epochTo: newExpirationEpoch, + epochFrom: expirationEpoch, + })} + + + ), + )} + + )} + {details?.threshold && ( + + + {t("govActions.newThresholdValue")} + + + {(details?.threshold as number).toString()} + + + )} + + ); +}; diff --git a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx index e47980920..0ece58804 100644 --- a/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx +++ b/govtool/frontend/src/components/molecules/GovernanceVotedOnCard.tsx @@ -5,6 +5,7 @@ import { Button } from "@atoms"; import { PATHS } from "@consts"; import { useScreenDimension, useTranslation } from "@hooks"; import { + encodeCIP129Identifier, getFullGovActionId, getProposalTypeLabel, getProposalTypeNoEmptySpaces, @@ -43,6 +44,13 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { const { isMobile, screenWidth } = useScreenDimension(); const { t } = useTranslation(); + const govActionId = getFullGovActionId(txHash, index); + const cip129GovernanceActionId = encodeCIP129Identifier({ + txID: txHash, + index: index.toString(16).padStart(2, "0"), + bech32Prefix: "gov_action", + }); + return ( { /> + @@ -120,15 +135,12 @@ export const GovernanceVotedOnCard = ({ votedProposal, inProgress }: Props) => { >