From 0d144bc050dd7f320768c6c92a1cf7ad20b0860d Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva <1890113+MexicanAce@users.noreply.github.com> Date: Thu, 28 Nov 2024 07:14:39 -0800 Subject: [PATCH 1/7] feat: add page size support for pagination (#336) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Add page size dropdown to show `10`, `20`, `50`, or `100` records in paginated tables ## Why ❔ Allows users to see more data on a single page. Closes #215 ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. ## Evidence https://github.com/user-attachments/assets/77d0aefe-f4ba-4f22-b765-5b3e8872e1a7 --- .../app/src/components/common/Dropdown.vue | 8 +- .../app/src/components/common/Pagination.vue | 186 ++++++++++++------ .../app/src/components/transactions/Table.vue | 27 ++- .../composables/common/useFetchCollection.ts | 9 +- packages/app/src/locales/en.json | 3 + packages/app/src/locales/uk.json | 3 + packages/app/src/views/BatchesView.vue | 14 +- packages/app/tests/components/Account.spec.ts | 5 +- .../components/common/Pagination.spec.ts | 30 ++- .../components/transactions/Table.spec.ts | 14 +- .../tests/components/transfers/Table.spec.ts | 5 + .../common/useFetchCollection.spec.ts | 2 +- .../tests/composables/useTransactions.spec.ts | 2 +- packages/app/tests/e2e/src/pages/base.page.ts | 4 + .../e2e/src/steps/blockexplorer.steps.ts | 5 + packages/app/tests/views/BatchView.spec.ts | 5 +- packages/app/tests/views/BlockView.spec.ts | 5 +- packages/app/tests/views/HomeView.spec.ts | 5 +- 18 files changed, 229 insertions(+), 103 deletions(-) diff --git a/packages/app/src/components/common/Dropdown.vue b/packages/app/src/components/common/Dropdown.vue index 87b5d6a6f8..9093bb24da 100644 --- a/packages/app/src/components/common/Dropdown.vue +++ b/packages/app/src/components/common/Dropdown.vue @@ -17,7 +17,7 @@ - + - + + diff --git a/packages/app/tests/components/Account.spec.ts b/packages/app/tests/components/Account.spec.ts index 39fb86887f..c058bc21eb 100644 --- a/packages/app/tests/components/Account.spec.ts +++ b/packages/app/tests/components/Account.spec.ts @@ -17,10 +17,13 @@ const router = { value: {}, }, }; +const routeQueryMock = vi.fn(() => ({})); vi.mock("vue-router", () => ({ useRouter: () => router, - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), })); describe("Account:", () => { diff --git a/packages/app/tests/components/common/Pagination.spec.ts b/packages/app/tests/components/common/Pagination.spec.ts index 762e896bc3..e887afe6c5 100644 --- a/packages/app/tests/components/common/Pagination.spec.ts +++ b/packages/app/tests/components/common/Pagination.spec.ts @@ -1,3 +1,5 @@ +import { createI18n } from "vue-i18n"; + import { describe, expect, it, vi } from "vitest"; import { fireEvent } from "@testing-library/vue"; @@ -5,6 +7,8 @@ import { mount, RouterLinkStub } from "@vue/test-utils"; import Pagination from "@/components/common/Pagination.vue"; +import enUS from "@/locales/en.json"; + vi.mock("vue-router", () => ({ useRouter: () => vi.fn(), useRoute: () => ({ @@ -13,6 +17,14 @@ vi.mock("vue-router", () => ({ })); describe("Pagination:", () => { + const i18n = createI18n({ + locale: "en", + allowComposition: true, + messages: { + en: enUS, + }, + }); + it("renders default state properly", () => { const wrapper = mount(Pagination, { props: { @@ -23,12 +35,13 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("page")); expect(pageLinks.length).toBe(5); - expect(pageLinks[0].props().to.query).toEqual({}); + expect(pageLinks[0].props().to.query).toEqual({ pageSize: "10" }); for (let a = 2; a < 5; a++) { expect(pageLinks[a - 1].props().to.query.page).toBe(a); } @@ -46,9 +59,10 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); - expect(wrapper.classes("disabled")).toBe(true); + expect(wrapper.find(".page-numbers-container").classes("disabled")).toBe(true); }); describe("Dots:", () => { @@ -62,6 +76,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); expect(wrapper.findAll(".dots").length).toBe(1); @@ -77,6 +92,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); expect(wrapper.findAll(".dots").length).toBe(1); @@ -92,6 +108,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); expect(wrapper.findAll(".dots").length).toBe(2); @@ -109,6 +126,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); @@ -116,7 +134,7 @@ describe("Pagination:", () => { expect(wrapper.emitted("update:activePage")).toEqual([[5], [5], [5], [6]]); }); describe("Back & Next buttons:", () => { - it("back button doesn't have a query and is disabled if first page is active", () => { + it("back button only has pageSize query and is disabled if first page is active", () => { const wrapper = mount(Pagination, { props: { activePage: 1, @@ -126,10 +144,11 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("arrow")); - expect(pageLinks[0].props().to.query).toEqual({}); + expect(pageLinks[0].props().to.query).toEqual({ pageSize: "10" }); expect(pageLinks[0].classes().includes("disabled")).toEqual(true); }); it("next button is disabled if last page is active", () => { @@ -142,6 +161,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("arrow")); @@ -158,6 +178,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub).filter((e) => e.classes().includes("arrow")); @@ -177,6 +198,7 @@ describe("Pagination:", () => { stubs: { RouterLink: RouterLinkStub, }, + plugins: [i18n], }, }); const pageLinks = wrapper.findAllComponents(RouterLinkStub); diff --git a/packages/app/tests/components/transactions/Table.spec.ts b/packages/app/tests/components/transactions/Table.spec.ts index 58c4288267..0c8febfa73 100644 --- a/packages/app/tests/components/transactions/Table.spec.ts +++ b/packages/app/tests/components/transactions/Table.spec.ts @@ -18,8 +18,16 @@ import type { TransactionListItem } from "@/composables/useTransactions"; import $testId from "@/plugins/testId"; +const router = { + push: vi.fn(), +}; + +const routeQueryMock = vi.fn(() => ({})); vi.mock("vue-router", () => ({ - useRoute: vi.fn(() => ({ query: {} })), + useRoute: () => ({ + query: routeQueryMock(), + }), + useRouter: () => router, })); vi.mock("@/composables/useTokenLibrary", () => { return { @@ -278,12 +286,12 @@ describe("Transfers:", () => { }); it("renders pagination", async () => { - expect(renderResult!.container.querySelector(".pagination")).not.toBeNull(); + expect(renderResult!.container.querySelector(".pagination-container")).not.toBeNull(); }); it("does not render pagination if pagination prop is false", async () => { await renderResult?.rerender({ pagination: false }); - expect(renderResult!.container.querySelector(".pagination")).toBeNull(); + expect(renderResult!.container.querySelector(".pagination-container")).toBeNull(); }); }); diff --git a/packages/app/tests/components/transfers/Table.spec.ts b/packages/app/tests/components/transfers/Table.spec.ts index 37a1ea76b0..77a5f08259 100644 --- a/packages/app/tests/components/transfers/Table.spec.ts +++ b/packages/app/tests/components/transfers/Table.spec.ts @@ -16,7 +16,12 @@ import elements from "tests/e2e/testId.json"; import $testId from "@/plugins/testId"; +const router = { + push: vi.fn(), +}; + vi.mock("vue-router", () => ({ + useRouter: () => router, useRoute: vi.fn(), })); vi.mock("@/composables/useTokenLibrary", () => { diff --git a/packages/app/tests/composables/common/useFetchCollection.spec.ts b/packages/app/tests/composables/common/useFetchCollection.spec.ts index 45b703e125..100bd068c9 100644 --- a/packages/app/tests/composables/common/useFetchCollection.spec.ts +++ b/packages/app/tests/composables/common/useFetchCollection.spec.ts @@ -111,7 +111,7 @@ describe("useFetchCollection:", () => { await fc.load(1, new Date("2023-05-01T10:00:00.000Z")); expect($fetch).toHaveBeenCalledWith( - "https://block-explorer-api.testnets.zksync.dev/?pageSize=10&page=1&toDate=2023-05-01T10%3A00%3A00.000Z" + "https://block-explorer-api.testnets.zksync.dev/?limit=10&page=1&toDate=2023-05-01T10%3A00%3A00.000Z" ); }); }); diff --git a/packages/app/tests/composables/useTransactions.spec.ts b/packages/app/tests/composables/useTransactions.spec.ts index 3a58681d18..2924a9c3f2 100644 --- a/packages/app/tests/composables/useTransactions.spec.ts +++ b/packages/app/tests/composables/useTransactions.spec.ts @@ -90,7 +90,7 @@ describe("useTransactions:", () => { const composable = useTransactions(searchParams); await composable.load(1); expect(fetchMock.mock.calls[0][0]).toBe( - "https://block-explorer-api.testnets.zksync.dev/transactions?blockNumber=0&l1BatchNumber=0&pageSize=10&page=1" + "https://block-explorer-api.testnets.zksync.dev/transactions?blockNumber=0&l1BatchNumber=0&limit=10&page=1" ); }); diff --git a/packages/app/tests/e2e/src/pages/base.page.ts b/packages/app/tests/e2e/src/pages/base.page.ts index d3efdf341a..4d72816682 100644 --- a/packages/app/tests/e2e/src/pages/base.page.ts +++ b/packages/app/tests/e2e/src/pages/base.page.ts @@ -20,6 +20,10 @@ export class BasePage { return "//*[@aria-label='Pagination']"; } + get pageSizeDropdown() { + return "//div[contains(@class, 'page-size-container')]"; + } + async getColumnByText(text: string) { element = `//th[text()="${text}"]`; result = await this.world.page?.locator(element).first(); diff --git a/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts b/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts index 4fdef1efc3..f36b96826a 100644 --- a/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts +++ b/packages/app/tests/e2e/src/steps/blockexplorer.steps.ts @@ -319,6 +319,11 @@ Then("Pagination form should be visible", async function (this: ICustomWorld) { result = await this.page?.locator(element); await expect(result).toBeVisible(config.increasedTimeout); + + element = basePage.pageSizeDropdown; + result = await this.page?.locator(element); + + await expect(result).toBeVisible(config.increasedTimeout); }); Then("Column with {string} name is visible", async function (this: ICustomWorld, columnName: string) { diff --git a/packages/app/tests/views/BatchView.spec.ts b/packages/app/tests/views/BatchView.spec.ts index 8d4d6900f2..2cc1e622a3 100644 --- a/packages/app/tests/views/BatchView.spec.ts +++ b/packages/app/tests/views/BatchView.spec.ts @@ -17,6 +17,7 @@ const router = { value: {}, }, }; +const routeQueryMock = vi.fn(() => ({})); vi.mock("@/composables/useSearch", () => { return { @@ -28,7 +29,9 @@ vi.mock("@/composables/useSearch", () => { vi.mock("vue-router", () => ({ useRouter: () => router, - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), createWebHistory: () => vi.fn(), createRouter: () => vi.fn(), })); diff --git a/packages/app/tests/views/BlockView.spec.ts b/packages/app/tests/views/BlockView.spec.ts index 37572b2f79..6586df112e 100644 --- a/packages/app/tests/views/BlockView.spec.ts +++ b/packages/app/tests/views/BlockView.spec.ts @@ -17,6 +17,7 @@ const router = { value: {}, }, }; +const routeQueryMock = vi.fn(() => ({})); vi.mock("@/composables/useSearch", () => { return { @@ -28,7 +29,9 @@ vi.mock("@/composables/useSearch", () => { vi.mock("vue-router", () => ({ useRouter: () => router, - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), createWebHistory: () => vi.fn(), createRouter: () => vi.fn(), })); diff --git a/packages/app/tests/views/HomeView.spec.ts b/packages/app/tests/views/HomeView.spec.ts index 7168f27062..8b23c816bf 100644 --- a/packages/app/tests/views/HomeView.spec.ts +++ b/packages/app/tests/views/HomeView.spec.ts @@ -26,6 +26,7 @@ const getBatchesMockCollection = (length: number) => status: "sealed", timestamp: "2022-04-13T13:09:31.000Z", })); +const routeQueryMock = vi.fn(() => ({})); vi.mock("ohmyfetch", () => { return { @@ -35,7 +36,9 @@ vi.mock("ohmyfetch", () => { vi.mock("vue-router", () => ({ useRouter: () => vi.fn(), - useRoute: () => vi.fn(), + useRoute: () => ({ + query: routeQueryMock(), + }), createWebHistory: () => vi.fn(), createRouter: () => vi.fn(), })); From 6a9cf049fd1b7604e2d3342771d38e4d7852e164 Mon Sep 17 00:00:00 2001 From: Vasyl Ivanchuk Date: Tue, 3 Dec 2024 12:42:41 +0200 Subject: [PATCH 2/7] fix: rename tx created field to tx received at (#343) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Rename tx created field to tx received at, fix the tooltip for the field. ## Why ❔ The new name better reflects the purpose of the value. As for the tooltip, it was misleading, as it stated that the displayed time was when the transaction was added to the block, which is incorrect. The datetime shown is actually the time when the transaction was received. ## Checklist - [X] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [X] Tests for the changes have been added / updated. --- .../app/src/components/transactions/infoTable/GeneralInfo.vue | 4 ++-- packages/app/src/locales/en.json | 4 ++-- packages/app/src/locales/uk.json | 3 ++- .../app/tests/components/transactions/GeneralInfo.spec.ts | 2 +- .../app/tests/e2e/features/artifacts/artifactsSet1.feature | 4 ++-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue index 40fbb238d6..d68bfc5780 100644 --- a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue +++ b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue @@ -197,9 +197,9 @@ - {{ t("transactions.table.created") }} + {{ t("transactions.table.receivedAt") }} - {{ t("transactions.table.createdTooltip") }} + {{ t("transactions.table.receivedAtTooltip") }} diff --git a/packages/app/src/locales/en.json b/packages/app/src/locales/en.json index bb0da4e738..b70c6f0998 100644 --- a/packages/app/src/locales/en.json +++ b/packages/app/src/locales/en.json @@ -105,8 +105,8 @@ "transactionHashTooltip": "Transaction hash is a unique 66-character identifier that is generated whenever a transaction is executed", "nonce": "Nonce", "nonceTooltip": "Number of transactions sent from a sender address", - "created": "Created", - "createdTooltip": "The date and time at which a transaction is added to the block", + "receivedAt": "Received", + "receivedAtTooltip": "The date and time when the transaction was received", "sendersNonce": "Sender`s Nonce", "from": "From", "fromTooltip": "The sending party of the transaction", diff --git a/packages/app/src/locales/uk.json b/packages/app/src/locales/uk.json index fed2910f15..d12592ab63 100644 --- a/packages/app/src/locales/uk.json +++ b/packages/app/src/locales/uk.json @@ -77,7 +77,8 @@ "reasonTooltip": "Причина невиконання транзакції", "transactionHash": "Хеш Транзакції", "nonce": "Нонс", - "created": "Створено", + "receivedAt": "Отримано", + "receivedAtTooltip": "Час коли транзакцію було отримано", "from": "Від", "to": "До", "block": "Блок", diff --git a/packages/app/tests/components/transactions/GeneralInfo.spec.ts b/packages/app/tests/components/transactions/GeneralInfo.spec.ts index 63450cbd36..6efd2a5da1 100644 --- a/packages/app/tests/components/transactions/GeneralInfo.spec.ts +++ b/packages/app/tests/components/transactions/GeneralInfo.spec.ts @@ -303,7 +303,7 @@ describe("Transaction info table", () => { expect(gasLimitAndUsedTooltip).toBe(i18n.global.t("transactions.table.gasLimitAndUsedTooltip")); expect(gasPerPubdataTooltip).toBe(i18n.global.t("transactions.table.gasPerPubdataTooltip")); expect(nonceTooltip).toBe(i18n.global.t("transactions.table.nonceTooltip")); - expect(createdAtTooltip).toBe(i18n.global.t("transactions.table.createdTooltip")); + expect(createdAtTooltip).toBe(i18n.global.t("transactions.table.receivedAtTooltip")); }); it("renders indexing transaction status", async () => { const wrapper = mount(Table, { diff --git a/packages/app/tests/e2e/features/artifacts/artifactsSet1.feature b/packages/app/tests/e2e/features/artifacts/artifactsSet1.feature index bf0189a5fa..332cc2673d 100644 --- a/packages/app/tests/e2e/features/artifacts/artifactsSet1.feature +++ b/packages/app/tests/e2e/features/artifacts/artifactsSet1.feature @@ -141,7 +141,7 @@ Feature: Main Page | Input data | 0xa9059cbb00000000000000000000000 | | Block | 45751 | | Batch | #661 | - | Created | 2023-02-10 | + | Received | 2023-02-10 | | Tokens Transferred | 0x8f0F33583a5 | | Tokens Transferred | From | | Tokens Transferred | 0x8f0F33583a5...d8f6 | @@ -173,7 +173,7 @@ Feature: Main Page | Input data | Function: transfer | | Block | 3491940 | | Batch | #28739 | - | Created | 2023-05-14 | + | Received | 2023-05-14 | @id211 @testnet From 198b1ff322e967fde1c9c14863366492951dc469 Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva <1890113+MexicanAce@users.noreply.github.com> Date: Tue, 3 Dec 2024 04:55:59 -0800 Subject: [PATCH 3/7] fix: pagination display with page sizes (#345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Fix the display of page size dropdown with pagination component ## Why ❔ They were overlapping wherever previously the styling of the pagination component is occurring outside of `Pagination.vue` BEFORE: ![image](https://github.com/user-attachments/assets/79be7041-e0ee-4816-96e2-5150b1a370c0) AFTER: ![image](https://github.com/user-attachments/assets/035526b0-9b22-48d0-83c7-f1c37a45c32f) ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. --- packages/app/src/components/event/ContractEvents.vue | 2 +- packages/app/src/components/transactions/infoTable/Logs.vue | 2 +- packages/app/src/components/transfers/Table.vue | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/event/ContractEvents.vue b/packages/app/src/components/event/ContractEvents.vue index 2eca89c86f..6e8afd4436 100644 --- a/packages/app/src/components/event/ContractEvents.vue +++ b/packages/app/src/components/event/ContractEvents.vue @@ -159,7 +159,7 @@ watch( @apply block; } .pagination { - @apply flex justify-center p-3; + @apply p-3; } .only-mobile { @apply flex md:hidden; diff --git a/packages/app/src/components/transactions/infoTable/Logs.vue b/packages/app/src/components/transactions/infoTable/Logs.vue index 24934c12cb..f4741dc049 100644 --- a/packages/app/src/components/transactions/infoTable/Logs.vue +++ b/packages/app/src/components/transactions/infoTable/Logs.vue @@ -190,7 +190,7 @@ function scrollPageToTop() { } .pagination { - @apply flex justify-center p-3; + @apply p-3; } .loading-row { .table-body-col { diff --git a/packages/app/src/components/transfers/Table.vue b/packages/app/src/components/transfers/Table.vue index 7304ce9b35..c80ac12370 100644 --- a/packages/app/src/components/transfers/Table.vue +++ b/packages/app/src/components/transfers/Table.vue @@ -219,7 +219,7 @@ watch( } .pagination { - @apply flex justify-center p-3; + @apply p-3; } .transfer-type { From 9317a70426d6ea209e385a1a54360f7ff412feda Mon Sep 17 00:00:00 2001 From: Nikola Pavlov <144679078+tx-nikola@users.noreply.github.com> Date: Wed, 4 Dec 2024 15:44:38 +0100 Subject: [PATCH 4/7] feat: add break-all to tx reason field (#340) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ This PR adds a simple break-all to the reason field of the Transactions view, so that when the transaction returns the error, it doesn't mess up the UI - the error text just breaks into next lines. ## Why ❔ When the transaction fails, if the error text is too long, it will make the whole tx UI scrollable for no reason, so we just add break-all to fix the UI. This is a fix for this issue: https://github.com/matter-labs/block-explorer/issues/329 Example of a failed transaction and error text that makes the table scrollable: https://sepolia.explorer.zksync.io/tx/0xe8d8ec3d36bc6cbbcf3453fa855fb64b56c547411000a631fd41a6c622ce1d79#overview ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. --- .../app/src/components/transactions/infoTable/GeneralInfo.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue index d68bfc5780..f68d1368f6 100644 --- a/packages/app/src/components/transactions/infoTable/GeneralInfo.vue +++ b/packages/app/src/components/transactions/infoTable/GeneralInfo.vue @@ -324,7 +324,7 @@ const gasUsedPercent = computed(() => { @apply py-2; } .transaction-reason-value { - @apply text-error-600 whitespace-normal; + @apply text-error-600 whitespace-normal break-all; } } From 0b1a667f13fb4397c2fe78e401a77d4a0842e57c Mon Sep 17 00:00:00 2001 From: Nikola Pavlov <144679078+tx-nikola@users.noreply.github.com> Date: Fri, 6 Dec 2024 10:46:34 +0100 Subject: [PATCH 5/7] fix: preserve line numbers in contracts (#332) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ This PR preserves the lines and line numbers in contract view, so that the code lines don't break into the next line on smaller screens. When the screen is smaller, the lines are horizontally scrollable. ## Why ❔ PR ensures that the line number always corresponds to the correct line. Previously, on smaller screens, lines would break to the next line, causing a mismatch between lines and line numbers. This is a fix for this issue: https://github.com/matter-labs/block-explorer/issues/327 ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. --- packages/app/src/components/SolidityEditor.vue | 8 ++++++++ packages/app/tailwind.config.js | 3 +++ 2 files changed, 11 insertions(+) diff --git a/packages/app/src/components/SolidityEditor.vue b/packages/app/src/components/SolidityEditor.vue index 538aa35966..52ad41f04a 100644 --- a/packages/app/src/components/SolidityEditor.vue +++ b/packages/app/src/components/SolidityEditor.vue @@ -116,6 +116,14 @@ function focusEditor() { .prism-editor__line-numbers { @apply h-max; } + + .prism-editor__container { + @apply overflow-x-scroll; + } + + .prism-editor__editor { + @apply text-nowrap; + } } } diff --git a/packages/app/tailwind.config.js b/packages/app/tailwind.config.js index 28d9ad07bb..7047b87827 100644 --- a/packages/app/tailwind.config.js +++ b/packages/app/tailwind.config.js @@ -48,6 +48,9 @@ module.exports = { maxWidth: "1240px", }, }, + ".text-nowrap": { + textWrap: "nowrap", + }, }); }, ], From 1ad298879347475698d8ae1665394947b84ba715 Mon Sep 17 00:00:00 2001 From: Nicolas Villanueva <1890113+MexicanAce@users.noreply.github.com> Date: Mon, 16 Dec 2024 01:52:18 -0800 Subject: [PATCH 6/7] feat: update display of transaction method selectors to human readable names (#339) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ Update display of transaction method selectors to human readable names ## Why ❔ Closes #338 ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. --- packages/app/src/components/TheFooter.vue | 2 +- .../app/src/components/header/TheHeader.vue | 2 +- .../app/src/components/transactions/Table.vue | 59 ++++++++++++++----- packages/app/src/composables/useOpenChain.ts | 42 +++++++++++++ .../app/tests/components/TheFooter.spec.ts | 2 +- .../app/tests/components/TheHeader.spec.ts | 2 +- .../redirection/redirectionSet1.feature | 4 +- 7 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 packages/app/src/composables/useOpenChain.ts diff --git a/packages/app/src/components/TheFooter.vue b/packages/app/src/components/TheFooter.vue index 2347db302b..e5577f4bc4 100644 --- a/packages/app/src/components/TheFooter.vue +++ b/packages/app/src/components/TheFooter.vue @@ -24,7 +24,7 @@ const config = useRuntimeConfig(); const navigation = reactive([ { label: computed(() => t("footer.nav.docs")), - url: "https://docs.zksync.io/build/tooling/zksync-block-explorers", + url: "https://docs.zksync.io/zksync-era/tooling/block-explorers", }, { label: computed(() => t("footer.nav.terms")), diff --git a/packages/app/src/components/header/TheHeader.vue b/packages/app/src/components/header/TheHeader.vue index a311e4dd0a..f92cc6592e 100644 --- a/packages/app/src/components/header/TheHeader.vue +++ b/packages/app/src/components/header/TheHeader.vue @@ -159,7 +159,7 @@ const { currentNetwork } = useContext(); const navigation = reactive([ { label: computed(() => t("header.nav.documentation")), - url: "https://docs.zksync.io/build/tooling/zksync-block-explorers", + url: "https://docs.zksync.io/zksync-era/tooling/block-explorers", }, ]); diff --git a/packages/app/src/components/transactions/Table.vue b/packages/app/src/components/transactions/Table.vue index fab1c6245f..f7814219d9 100644 --- a/packages/app/src/components/transactions/Table.vue +++ b/packages/app/src/components/transactions/Table.vue @@ -58,9 +58,13 @@ -
- {{ item.methodName }} -
+ +
+ {{ item.methodName }} +
+ + +
{ +const methodNames = ref>({}); + +const loadMethodNames = async () => { + if (!data.value) return; + + const uniqueSighashes = [ + ...new Set( + data.value?.map((transaction) => transaction.data.slice(0, 10)).filter((sighash) => sighash !== "0x") ?? [] + ), + ]; + const fetchedMethodNames = await fetchMethodNames(uniqueSighashes); + methodNames.value = { ...methodNames.value, ...fetchedMethodNames }; +}; + +watch( + data, + async (newData) => { + if (!newData) return; + + await loadMethodNames(); + }, + { immediate: true } +); + +const getTransactionMethod = (transaction: TransactionListItem, methodNames: Record) => { if (transaction.data === "0x") { return t("transactions.table.transferMethodName"); } const sighash = transaction.data.slice(0, 10); if (props.contractAbi) { - return ( - decodeDataWithABI( - { - calldata: transaction.data, - value: transaction.value, - }, - props.contractAbi - )?.name ?? sighash + const decodedMethod = decodeDataWithABI( + { calldata: transaction.data, value: transaction.value }, + props.contractAbi ); + if (decodedMethod?.name) { + return decodedMethod.name; + } } - return sighash; + + return methodNames[sighash] ?? sighash; }; type TransactionListItemMapped = TransactionListItem & { @@ -301,7 +330,7 @@ type TransactionListItemMapped = TransactionListItem & { const transactions = computed(() => { return data.value?.map((transaction) => ({ ...transaction, - methodName: getTransactionMethod(transaction), + methodName: getTransactionMethod(transaction, methodNames.value), fromNetwork: transaction.isL1Originated ? "L1" : "L2", toNetwork: "L2", // even withdrawals go through L2 addresses (800A or bridge addresses) statusColor: transaction.status === "failed" ? "danger" : "dark-success", @@ -432,7 +461,7 @@ function getDirection(item: TransactionListItem): Direction { } } .transactions-data-method { - @apply w-[200px] truncate sm:w-auto; + @apply w-36 truncate border-slate-200 rounded border py-0.5 px-2 text-center bg-slate-400/10 text-xs text-slate-600 sm:w-28; } .transactions-data-transaction-amount, .transactions-data-age { diff --git a/packages/app/src/composables/useOpenChain.ts b/packages/app/src/composables/useOpenChain.ts new file mode 100644 index 0000000000..c84067feb0 --- /dev/null +++ b/packages/app/src/composables/useOpenChain.ts @@ -0,0 +1,42 @@ +import { $fetch } from "ohmyfetch"; + +interface OpenChainMethod { + name: string; + filtered: boolean; +} +interface OpenChainResponse { + ok: boolean; + result: { + function: Record; + }; +} +export async function fetchMethodNames(sighashes: string[]): Promise> { + try { + const response = await $fetch("https://api.openchain.xyz/signature-database/v1/lookup", { + method: "GET", + params: { + function: sighashes.join(","), + filter: true, + }, + headers: { + accept: "application/json", + }, + }); + const result = response?.result?.function ?? {}; + const methodNames: Record = {}; + Object.entries(result).forEach(([sighash, methods]) => { + // Ensure methods is an array of the expected shape + if (Array.isArray(methods)) { + methods.forEach((method) => { + if (typeof method === "object" && method.name && method.name.split("(").length > 1) { + methodNames[sighash] = method.name.split("(")[0]; + } + }); + } + }); + return methodNames; + } catch (error) { + console.error("Error fetching method names:", error); + return {}; + } +} diff --git a/packages/app/tests/components/TheFooter.spec.ts b/packages/app/tests/components/TheFooter.spec.ts index e48c3a721a..265b0f1239 100644 --- a/packages/app/tests/components/TheFooter.spec.ts +++ b/packages/app/tests/components/TheFooter.spec.ts @@ -24,7 +24,7 @@ describe("TheFooter:", () => { }, }); const links = wrapper.findAll("a"); - expect(links[0].attributes("href")).toBe("https://docs.zksync.io/build/tooling/zksync-block-explorers"); + expect(links[0].attributes("href")).toBe("https://docs.zksync.io/zksync-era/tooling/block-explorers"); expect(links[1].attributes("href")).toBe("https://zksync.io/terms"); expect(links[2].attributes("href")).toBe("https://zksync.io/contact"); }); diff --git a/packages/app/tests/components/TheHeader.spec.ts b/packages/app/tests/components/TheHeader.spec.ts index aa31d1562a..e46d560794 100644 --- a/packages/app/tests/components/TheHeader.spec.ts +++ b/packages/app/tests/components/TheHeader.spec.ts @@ -62,7 +62,7 @@ describe("TheHeader:", () => { expect(toolsLinks[2].attributes("href")).toBe("https://bridge.zksync.io/"); expect(wrapper.findAll(".navigation-container > .navigation-link")[0].attributes("href")).toBe( - "https://docs.zksync.io/build/tooling/zksync-block-explorers" + "https://docs.zksync.io/zksync-era/tooling/block-explorers" ); }); it("renders social links", () => { diff --git a/packages/app/tests/e2e/features/redirection/redirectionSet1.feature b/packages/app/tests/e2e/features/redirection/redirectionSet1.feature index cbd4948417..3cf0dadec0 100644 --- a/packages/app/tests/e2e/features/redirection/redirectionSet1.feature +++ b/packages/app/tests/e2e/features/redirection/redirectionSet1.feature @@ -13,7 +13,7 @@ Feature: Redirection Examples: | Extra button name | url | - | Docs | https://docs.zksync.io/build/tooling/zksync-block-explorers | + | Docs | https://docs.zksync.io/zksync-era/tooling/block-explorers | | Terms | https://zksync.io/terms | | Contact | https://zksync.io/contact | @@ -32,7 +32,7 @@ Feature: Redirection @id251 Scenario: Verify redirection for Documentation link Given I click by text "Documentation" - Then New page have "https://docs.zksync.io/build/tooling/zksync-block-explorers" address + Then New page have "https://docs.zksync.io/zksync-era/tooling/block-explorers" address @id252 Scenario Outline: Verify redirection for "" in BE menu From 38a05a3e43d9d3af2b3757f11cd5c98bb7a7886c Mon Sep 17 00:00:00 2001 From: Nikola Pavlov <144679078+tx-nikola@users.noreply.github.com> Date: Mon, 16 Dec 2024 13:56:56 +0100 Subject: [PATCH 7/7] fix: pass arguments for the contract functions in the correct order (#347) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # What ❔ This PR fixes the bug where the contract function arguments are not passed in the correct order. ## Why ❔ In order to call the contract functions correctly, we need to pass the arguments in the right order. # Explanation The issue was that we were trying to get `Object.entries(params)` without actually sorting it, so, for example, contract [0xE1D6A50E7101c8f8db77352897Ee3f1AC53f782B](https://explorer.zksync.io/address/0xE1D6A50E7101c8f8db77352897Ee3f1AC53f782B#contract#write-proxy), the `revokeRole` function asks for `[role, account]` arguments to be passed. In the form object, it looks like this: ``` { account: "0xExample", role: "0xExample } ``` and when we run `Object.entries(params)` we always get it like this: ``` [ ["account", "0x969Bb8Ae65602B4F8f9B459a11084e591c4491C7"], ["role", "0x03c321230b026fb61a5a21f62c5f618751ec6d8435327f673bae5bfa570e5879"]] ] ``` and later after we map it, it becomes `["0x969Bb8Ae65602B4F8f9B459a11084e591c4491C7", "0x03c321230b026fb61a5a21f62c5f618751ec6d8435327f673bae5bfa570e5879"]` - swapping the arguments from their intended places. This issue was happening when someone types the account first, therefore forms become ``` { account: "0xExample", role: "0xExample } ``` instead of ``` { role: "0xExample, account: "0xExample" } ``` thus messing up the order of the arguments in the function call. ## Checklist - [x] PR title corresponds to the body of PR (we generate changelog entries from PRs). - [x] Tests for the changes have been added / updated. - [x] Documentation comments have been added / updated. --------- Co-authored-by: Vasyl Ivanchuk --- .../src/composables/useContractInteraction.ts | 20 +++++++++---------- .../useContractInteraction.spec.ts | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/app/src/composables/useContractInteraction.ts b/packages/app/src/composables/useContractInteraction.ts index dea7b67cfa..a6050b0c65 100644 --- a/packages/app/src/composables/useContractInteraction.ts +++ b/packages/app/src/composables/useContractInteraction.ts @@ -45,16 +45,16 @@ export default (context = useContext()) => { const signer = await getL2Signer(); const contract = new Contract(address, [abiFragment], signer!); const method = contract[abiFragment.name]; - const methodArguments = Object.entries(params) - .filter(([key]) => key !== PAYABLE_AMOUNT_PARAM_NAME) - .map(([, inputValue]) => { - if (inputValue === "true") { - inputValue = true; - } else if (inputValue === "false") { - inputValue = false; - } - return inputValue; - }); + const abiFragmentNames = abiFragment.inputs.map((abiInput) => abiInput.name); + const methodArguments = abiFragmentNames.map((abiFragmentName) => { + if (params[abiFragmentName] === "true") { + return true; + } + if (params[abiFragmentName] === "false") { + return false; + } + return params[abiFragmentName]; + }); const valueMethodOption = { value: parseEther((params[PAYABLE_AMOUNT_PARAM_NAME] as string) ?? "0"), }; diff --git a/packages/app/tests/composables/useContractInteraction.spec.ts b/packages/app/tests/composables/useContractInteraction.spec.ts index 55a07fd4f5..d3cc5b31ee 100644 --- a/packages/app/tests/composables/useContractInteraction.spec.ts +++ b/packages/app/tests/composables/useContractInteraction.spec.ts @@ -115,7 +115,7 @@ describe("useContractInteraction:", () => { }, { [PAYABLE_AMOUNT_PARAM_NAME]: "0.1", - address: ["0x0cc725e6ba24e7db79f62f22a7994a8ee33adc1b"], + spender: ["0x0cc725e6ba24e7db79f62f22a7994a8ee33adc1b"], } ); expect(mock.mock.lastCall).toEqual([ @@ -162,7 +162,7 @@ describe("useContractInteraction:", () => { "0x0cc725e6ba24e7db79f62f22a7994a8ee33adc1b", { ...abiFragment, - inputs: [], + inputs: [{ internalType: "bool", name: "bool", type: "bool" }], stateMutability: "payable", }, {