Skip to content

Commit

Permalink
fix: improve performance across many pages
Browse files Browse the repository at this point in the history
move most SSR api calls to client side
add e2e for the top pages
replace the v2 endpoint with a v1 /nonce fetch
add skeleton while loading data
remove some jotai code
leverage some caching

closes hirosystems#586
closes hirosystems#776
closes hirosystems#785
closes hirosystems#786
closes hirosystems#601
closes hirosystems#449
  • Loading branch information
beguene committed Jul 25, 2022
1 parent 4ba2134 commit 3c7abb3
Show file tree
Hide file tree
Showing 52 changed files with 867 additions and 395 deletions.
37 changes: 37 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# name: Integration tests

# on: [pull_request]

# jobs:
# test_setup:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - name: Vercel Action
# id: vercel_action
# uses: amondnet/vercel-action@v20
# with:
# vercel-token: ${{ secrets.VERCEL_TOKEN }}
# github-token: ${{ secrets.GITHUB_TOKEN }}
# vercel-org-id: ${{ secrets.ORG_ID}}
# vercel-project-id: ${{ secrets.PROJECT_ID}}
# scope: ${{ secrets.VERCEL_SCOPE }}
# test_e2e:
# needs: test_setup
# name: Playwright tests
# timeout-minutes: 5
# runs-on: ubuntu-latest
# steps:
# - name: Prepare testing env
# uses: actions/checkout@v2
# - uses: actions/setup-node@v2
# with:
# node-version: '16'
# - run: yarn install
# - run: npx playwright install --with-deps
# - name: Run tests
# run: |
# xvfb-run --auto-servernum -- \
# yarn run test:e2e
# env:
# PLAYWRIGHT_TEST_BASE_URL: ${{ steps.vercel_action.outputs.preview-url }}
28 changes: 27 additions & 1 deletion .github/workflows/lighthouse.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,14 @@ jobs:
# add as many urls here as you wish to audit
# prefixed by the preview-url
urls: |
https://explorer-git-prod-reference-blockstack.vercel.app/
https://explorer-git-prod-reference-blockstack.vercel.app/txid/0xa176909e4681cf41cf8662ce51ffcf109fb2fdb2aeae6bb8425d236241debe3f
https://explorer-git-prod-reference-blockstack.vercel.app/address/SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT
https://explorer-git-prod-reference-blockstack.vercel.app/block/0xe1c163a23ce0fa8a552b5237c25bc36f3ec0612eb8a01322d9f154f09163b1a0
${{ steps.vercel_action.outputs.preview-url }}
${{ steps.vercel_action.outputs.preview-url }}/transactions
${{ steps.vercel_action.outputs.preview-url }}/txid/0xa176909e4681cf41cf8662ce51ffcf109fb2fdb2aeae6bb8425d236241debe3f
${{ steps.vercel_action.outputs.preview-url }}/address/SM2MARAVW6BEJCD13YV2RHGYHQWT7TDDNMNRB1MVT
${{ steps.vercel_action.outputs.preview-url }}/block/0xe1c163a23ce0fa8a552b5237c25bc36f3ec0612eb8a01322d9f154f09163b1a0
budgetPath: '.github/lighthouse/budget.json'
uploadArtifacts: true
temporaryPublicStorage: true
Expand Down Expand Up @@ -112,3 +118,23 @@ jobs:
header: budget
message: |
${{ steps.budget_result.outputs.comment }}
test_e2e:
needs: test_setup
name: Playwright tests
timeout-minutes: 5
runs-on: ubuntu-latest
steps:
- name: Prepare testing env
uses: actions/checkout@v2
- uses: actions/setup-node@v2
with:
node-version: '16'
- run: yarn install
- run: npx playwright install --with-deps
- name: Run tests
run: |
xvfb-run --auto-servernum -- \
yarn run test:e2e
env:
PLAYWRIGHT_TEST_BASE_URL: ${{ steps.vercel_action.outputs.preview-url }}
20 changes: 20 additions & 0 deletions e2e/addresses-test-vector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export const addresses = {
mainnet: {
default: [
'SP3NM2RVWS0ST1B3XE5ZNDX7VTVYJE9HAB91SQCC3',
'SP17YZQB1228EK9MPHQXA8GC4G3HVWZ66X7VRPMAX',
],
},
};

export const invalidAddresses = {
mainnet: {
default: ['SPENM2RVWS0ST1B3XE5ZNDX7VTVYJE9HAB91SQCC3'],
},
};

export const emptyAddresses = {
mainnet: {
default: ['SP2Q3JDJ7AVY39VEWJTJP41GAF7SARDDE5DQZTN9P'],
},
};
3 changes: 3 additions & 0 deletions e2e/block-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function hasBlocks(page) {
return page.waitForSelector('[data-test=block-0]');
}
5 changes: 5 additions & 0 deletions e2e/blocks-test-vector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const blocks = {
mainnet: {
anchored: ['0xce670549f33d285cb69708b7c5157cf875161848493bb0fa5b8b1c0079d963aa'],
},
};
45 changes: 45 additions & 0 deletions e2e/page-address.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expect, test } from '@playwright/test';
import { addresses, emptyAddresses } from './addresses-test-vector';

async function hasTransactions(page) {
await expect(page.locator('[data-test=account-transaction-list]')).toHaveCount(1);
}

async function hasNoTransactions(page) {
await expect(page.locator('[data-test=account-transaction-list]')).toHaveCount(0);
}

test.describe('/address page', () => {
test.describe('Loads the address pages (non empty addresses)', () => {
Object.keys(addresses).forEach(network => {
Object.keys(addresses[network]).forEach(type => {
addresses[network][type].forEach(address => {
test(`transactions type ${type} with address=${address} on network=${network}`, async ({
page,
}) => {
await page.goto(`/address/${address}?chain=${network}`);
await expect(page.locator('[data-test=address-title]')).toBeTruthy();
await hasTransactions(page);
});
});
});
});
});

test.describe('Loads the address pages (empty addresses)', () => {
Object.keys(emptyAddresses).forEach(network => {
Object.keys(emptyAddresses[network]).forEach(type => {
emptyAddresses[network][type].forEach(address => {
test(`transactions type ${type} with address=${address} on network=${network}`, async ({
page,
}) => {
await page.goto(`/address/${address}?chain=${network}`);
await expect(page.locator('[data-test=address-title]')).toBeTruthy();
await page.waitForSelector('[data-test=account-transaction-list]');
await hasNoTransactions(page);
});
});
});
});
});
});
17 changes: 17 additions & 0 deletions e2e/page-block.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { expect, test } from '@playwright/test';

import { blocks } from './blocks-test-vector';
test.describe('/block page', () => {
test.describe('Loads the block pages', () => {
Object.keys(blocks).forEach(network => {
Object.keys(blocks[network]).forEach(type => {
blocks[network][type].forEach(hash => {
test(`block type ${type} with hash=${hash} on network=${network}`, async ({ page }) => {
await page.goto(`/block/${hash}?chain=${network}`);
await expect(page.locator('[data-test=tx-item')).toBeTruthy();
});
});
});
});
});
});
14 changes: 14 additions & 0 deletions e2e/page-blocks.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { test } from '@playwright/test';
import { hasBlocks } from './block-utils';

test.describe('/blocks page', () => {
test('intial load mainnet', async ({ page }) => {
await page.goto(`/blocks`);
await hasBlocks(page);
});

test('intial load testnet', async ({ page }) => {
await page.goto(`/blocks?chain=testnet`);
await hasBlocks(page);
});
});
34 changes: 34 additions & 0 deletions e2e/page-homepage.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { expect, Page, test } from '@playwright/test';
import { hasBlocks } from './block-utils';

const txTypes = [
'token_transfer',
'smart_contract',
'contract_call',
'poison_microblock',
'coinbase',
];

function hasTransactions(page) {
const selectors = txTypes.map(type => `[data-test=${type}-transaction]`);
return page.waitForSelector(selectors.join(', '));
}

async function hasPendingTransactions(page: Page) {
await page.waitForSelector('[data-test=tx-caption]');
const count = await page.locator('[data-test=tx-caption]', { hasText: 'Pending' }).count();
expect(count).toBeGreaterThan(0);
}

test.describe('/ homepage', () => {
test.describe('Loads the home page', () => {
test('intial load', async ({ page }) => {
await page.goto(`/`);
await expect(page.locator('[data-test=homepage-title]')).toBeTruthy();
await hasTransactions(page);
await hasBlocks(page);
await page.locator('button:has-text("Pending")').click();
await hasPendingTransactions(page);
});
});
});
20 changes: 20 additions & 0 deletions e2e/page-txid.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { expect, test } from '@playwright/test';

import { txs } from './transactions-test-vector';
test.describe('/txid page', () => {
test.describe('Loads the transactions txid pages', () => {
Object.keys(txs).forEach(network => {
console.log('network', network);
Object.keys(txs[network]).forEach(type => {
txs[network][type].forEach(txid => {
test(`transactions type ${type} with txid=${txid} on network=${network}`, async ({
page,
}) => {
await page.goto(`/txid/${txid}?chain=${network}`);
await expect(page.locator('[data-test=txid-title')).toBeTruthy();
});
});
});
});
});
});
23 changes: 23 additions & 0 deletions e2e/transactions-test-vector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const txs = {
mainnet: {
coinbase: ['0xa1d9f5d4d1a2d0cdeaaa8316507596feeca97d62c3596f937205425222989528'],
token_transfer: ['0x44c64a8975bdd4b2f6eef4b0d1ac1203ce6f67fe0e4490289495727de8f311f3'],
contract_call: [
'0xa176909e4681cf41cf8662ce51ffcf109fb2fdb2aeae6bb8425d236241debe3f',
'0xf9bd54f478cd5a519a43ecb65f4ca9c11525852527ea25e4c7b2e1bf7a5444e4',
],
smart_contract: ['SP3K8BC0PPEVCV7NZ6QSRWPQ2JE9E5B6N3PA0KBR9.alex-reserve-pool'],
poison_microblock: [],
},
testnet: {
coinbase: ['0xd5c4b3fd90c171d0e172ee732962593b52f25b0aca9b76f87d50b0468f04ccae'],
token_transfer: ['0x7501c52bfaee4b9e748e9bf369443840a71f44ab6eb446d910214b9d81ab7aec'],
contract_call: [
'0x8d42daa35773f18035fb2534405456c497552b276602a23d9fffe1b876997ae3',
'0x48deac1e587bdc6a9a5f31758c6df5198b5dbac0d71510256d45740e97e988f4',
'0xb0839b68fd355bf8a26cc701dcc1035eae170c3091d32d0aa8c467e8fde06354',
],
smart_contract: ['ST1Z0T93FB36WMEDN21Z66EB96M8WYYCQPGZQW25X.bitbasel-marketplaceV4'],
poison_microblock: [],
},
};
3 changes: 3 additions & 0 deletions e2e/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"lint:fix": "eslint --ext .ts,.tsx ./src/ -f unix --fix && prettier --write src/**/*.{ts,tsx} *.js",
"lint:prettier": "prettier --check \"src/**/*.{ts,tsx}\" *.js *.json",
"lint:prettier:fix": "prettier --write \"src/**/*.{ts,tsx}\" *.js *.json",
"lint:unused-exports": "ts-unused-exports tsconfig.json",
"start": "next start",
"typecheck": "tsc --noEmit",
"test:e2e": "playwright test"
Expand All @@ -34,9 +35,9 @@
"@reduxjs/toolkit": "1.7.2",
"@segment/snippet": "^4.15.3",
"@sentry/nextjs": "6.17.2",
"@stacks/auth": "4.0.1",
"@stacks/blockchain-api-client": "4.0.1",
"@stacks/common": "4.1.0",
"@stacks/auth": "4.0.1",
"@stacks/connect": "6.8.4",
"@stacks/connect-ui": "5.5.0",
"@stacks/network": "4.1.0",
Expand Down Expand Up @@ -88,6 +89,7 @@
"prismjs": "1.24.1",
"react": "17.0.2",
"react-aria": "3.8.0",
"react-content-loader": "^6.2.0",
"react-dom": "17.0.2",
"react-error-boundary": "3.1.3",
"react-hot-toast": "2.1.1",
Expand Down Expand Up @@ -141,6 +143,7 @@
"lint-staged": "11.0.0",
"package-json-cleanup-loader": "1.0.3",
"prettier": "2.3.2",
"ts-unused-exports": "^8.0.0",
"typescript": "4.3.5"
},
"homepage": "https://explorer.stacks.co",
Expand Down
24 changes: 14 additions & 10 deletions playwright.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PlaywrightTestConfig, devices } from '@playwright/test';
import { devices, PlaywrightTestConfig } from '@playwright/test';
import path from 'path';

// Reference: https://playwright.dev/docs/test-configuration
Expand All @@ -12,20 +12,12 @@ const config: PlaywrightTestConfig = {
// Artifacts folder where screenshots, videos, and traces are stored.
outputDir: 'test-results/',

// Run your local dev server before starting the tests:
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
webServer: {
command: 'yarn dev',
port: 3000,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
},

use: {
// Retry a test if its failing with enabled tracing. This allows you to analyse the DOM, console logs, network traffic etc.
// More information: https://playwright.dev/docs/trace-viewer
trace: 'retry-with-trace',
headless: false,
baseURL: process.env.PLAYWRIGHT_TEST_BASE_URL || 'http://localhost:3000',

// All available context options: https://playwright.dev/docs/api/class-browser#browser-new-context
// contextOptions: {
Expand Down Expand Up @@ -66,4 +58,16 @@ const config: PlaywrightTestConfig = {
// },
],
};

if (!process.env.PLAYWRIGHT_TEST_BASE_URL) {
// Run your local dev server before starting the tests:
// https://playwright.dev/docs/test-advanced#launching-a-development-web-server-during-the-tests
config.webServer = {
command: 'yarn dev',
port: 3000,
timeout: 120 * 1000,
reuseExistingServer: !process.env.CI,
};
}

export default config;
6 changes: 6 additions & 0 deletions src/common/api/account.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { fetchFromSidecar } from './fetch';

export const fetchNonce = (apiServer: string) => async (principal: string) => {
const res = await fetchFromSidecar(apiServer)(`/address/${principal}/nonces`);
return res.json();
};
2 changes: 2 additions & 0 deletions src/common/api/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export interface FetchBlocksListResponse {
*
* @param {string} apiServer - the current apiServer
*/
// TOREMOVE unused
export const fetchBlock =
(apiServer: string) =>
async (hash: Block['hash']): Promise<Block> => {
Expand All @@ -39,6 +40,7 @@ export const fetchBlock =
*
* @param {FetchBlocksListOptions} options
*/
// TOREMOVE unused
export const fetchBlocksList =
(options: FetchBlocksListOptions) => async (): Promise<FetchBlocksListResponse> => {
const { apiServer, offset, limit = 30 } = options;
Expand Down
1 change: 1 addition & 0 deletions src/common/api/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { fetchFromSidecar } from '@common/api/fetch';
import type { ContractInterfaceResponse } from '@stacks/stacks-blockchain-api-types';
import type { Contract, ContractResponse } from '@common/types/tx';

// TOREMOVE unused
export const fetchContract =
(apiServer: string) =>
async (contract_id: string): Promise<Contract | { error: string }> => {
Expand Down
1 change: 1 addition & 0 deletions src/common/api/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export const fetchFromSidecar =
});
};

// TOREMOVE unused
export const postToApi =
(apiServer: string) =>
async (path: string, opts = {}) =>
Expand Down
2 changes: 2 additions & 0 deletions src/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export const DEFAULT_BLOCKS_LIST_LIMIT = 11;
export const DEFAULT_LIST_LIMIT_SMALL = 10;
export const DEFAULT_LIST_LIMIT = 30;

export const MAX_BLOCK_TRANSACTIONS_PER_CALL = 200;

export const TransactionType = {
SMART_CONTRACT: 'smart_contract' as Transaction['tx_type'],
CONTRACT_CALL: 'contract_call' as Transaction['tx_type'],
Expand Down
Loading

0 comments on commit 3c7abb3

Please sign in to comment.