diff --git a/package.json b/package.json index fd9552a..2036524 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,8 @@ "build:production": "lerna run --scope dashboard build:production", "build:staging": "lerna run --scope dashboard build:staging", "build:local": "lerna run --scope dashboard build:local", - "dev:dashboard-dev": "lerna run --scope dashboard dev:development", - "dev:dashboard-prod": "lerna run --scope dashboard dev:production", + "dev:dashboard-dev": "lerna run --stream --scope dashboard dev:development", + "dev:dashboard-prod": "lerna run --stream --scope dashboard dev:production", "test:dashboard-unit": "lerna run --scope dashboard test:unit", "cap:init": "git submodule update --init --recursive", "cap:update": "git submodule update --remote --merge", diff --git a/packages/dashboard/src/components/Link/index.tsx b/packages/dashboard/src/components/Link/index.tsx index 1987660..ff0773f 100644 --- a/packages/dashboard/src/components/Link/index.tsx +++ b/packages/dashboard/src/components/Link/index.tsx @@ -94,6 +94,24 @@ export const AccountLink = ({ ); + +export const NamedAccountLink = ({ + account, + name, + trim, +}: { + name: string, + account: string, + trim?: boolean, +}) => ( + + {trim ? trimAccount(name) : name} + +); + export const NamedLink = ({ url, name, diff --git a/packages/dashboard/src/components/Tables/AccountsTable/index.tsx b/packages/dashboard/src/components/Tables/AccountsTable/index.tsx index 221dd6f..f2437f3 100644 --- a/packages/dashboard/src/components/Tables/AccountsTable/index.tsx +++ b/packages/dashboard/src/components/Tables/AccountsTable/index.tsx @@ -2,7 +2,7 @@ import React, { useState, useMemo, useEffect } from 'react'; import { styled } from '@stitched'; import DataTable, { FormatterTypes, TableId } from '@components/Tables/DataTable'; import Title from '@components/Title'; -import { AccountLink, NamedLink } from '@components/Link'; +import { AccountLink, NamedAccountLink } from '@components/Link'; import { getDabMetadata, CanisterMetadata } from '@utils/dab'; import IdentityDab from '@components/IdentityDab'; @@ -31,8 +31,8 @@ const Container = styled('div', { }); export interface AccountData { - canister: string, - name: string, + contractId: string, + rootCanisterId: string, } interface Column { @@ -41,18 +41,18 @@ interface Column { } export const DEFAULT_COLUMN_ORDER: (keyof AccountData)[] = [ - 'name', - 'canister', + 'rootCanisterId', + 'contractId', ]; const columns: Column[] = [ { Header: 'Name', - accessor: 'name', + accessor: 'rootCanisterId', }, { - Header: 'Cap Root', - accessor: 'canister', + Header: 'Token contract', + accessor: 'contractId', }, ]; @@ -65,6 +65,9 @@ const AccountDab = ({ // Dab metadata handler useEffect(() => { + // TODO: Should this move to the store? + // at the moment is called as a "nice-to-have", + // not as main business logic... const getDabMetadataHandler = async () => { const metadata = await getDabMetadata({ canisterId, @@ -83,7 +86,7 @@ const AccountDab = ({ return identityInDab ? - : + : }; const AccountsTable = ({ @@ -98,10 +101,16 @@ const AccountsTable = ({ }) => { const formatters = useMemo(() => ({ body: { - canister: (cellValue: string) => , - name: (cellValue: string) => , + contractId: (cellValue: string) => { + const found = data.find((x) => x.contractId === cellValue); + + if (!found?.rootCanisterId) return ''; + + return ; + }, + rootCanisterId: (cellValue: string) => , }, - } as FormatterTypes), []); + } as FormatterTypes), [data]); return ( ((set) => ({ return; } - const pageData = parseUserRootBucketsResponse(response); + // Get the Root, Token Contract pair + // via promise all for concurrency + // TODO: Change to actual implementation once CAP PR's ready + const { tokenContractsPairedRoots } = await import('@utils/mocks/tokenContractsCapRoots'); + + const pageData = parseUserRootBucketsResponse({ + ...response, + tokenContractsPairedRoots, + }); set((state: AccountStore) => ({ accounts: response, diff --git a/packages/dashboard/src/utils/account.test.ts b/packages/dashboard/src/utils/account.test.ts index 3d8d3f1..b7ab298 100644 --- a/packages/dashboard/src/utils/account.test.ts +++ b/packages/dashboard/src/utils/account.test.ts @@ -144,12 +144,18 @@ describe('Account', () => { }); it('should parse the data', () => { - const parsed = parseUserRootBucketsResponse(response); + const parsed = parseUserRootBucketsResponse({ + ...response, + tokenContractsPairedRoots: {}, + }); expect(parsed).toBeTruthy(); }); it('should parse the data to the expected object type', () => { - const parsed = parseUserRootBucketsResponse(response); + const parsed = parseUserRootBucketsResponse({ + ...response, + tokenContractsPairedRoots: {}, + }); const expectedData = [{ canister: identity.toText(), age: undefined, @@ -172,7 +178,10 @@ describe('Account', () => { }); it('should return empty list', () => { - const parsed = parseUserRootBucketsResponse(response); + const parsed = parseUserRootBucketsResponse({ + ...response, + tokenContractsPairedRoots: {}, + }); const expectedData: any[] = []; expect(parsed).toStrictEqual(expectedData); }); @@ -192,7 +201,10 @@ describe('Account', () => { }); it('should return empty list', () => { - const parsed = parseUserRootBucketsResponse(response); + const parsed = parseUserRootBucketsResponse({ + ...response, + tokenContractsPairedRoots: {}, + }); const expectedData: any[] = []; expect(parsed).toStrictEqual(expectedData); }); diff --git a/packages/dashboard/src/utils/account.ts b/packages/dashboard/src/utils/account.ts index 03db571..ac0a564 100644 --- a/packages/dashboard/src/utils/account.ts +++ b/packages/dashboard/src/utils/account.ts @@ -5,6 +5,7 @@ import { } from '../components/BookmarkPanel'; import { Principal } from "@dfinity/principal"; import { AccountData } from '@components/Tables/AccountsTable'; +// import principal from './principal'; export const hashTrimmer = (hash: string) => { const size = 6; @@ -52,20 +53,47 @@ export const createBookmarkExpandHandler = ({ return bookmarkExpandHandler; }; +type TokenContractsPairedRoots = Record; + export const parseUserRootBucketsResponse = ({ contracts, + tokenContractsPairedRoots, }: { contracts?: Principal[], + tokenContractsPairedRoots: TokenContractsPairedRoots, }): AccountData[] | [] => { if (!contracts || !Array.isArray(contracts) || !contracts.length) return []; return contracts - .map((principal: Principal) => ({ - canister: principal.toText(), - // TODO: there's a call to Dab that requires - // the canister id, so this should be handle a bit differently - // but for now pass the canister id and the call to dab - // is made in the scope of the datatable generation - name: principal.toText(), - })); + .filter( + (principal: Principal) => getTokenContractCanisterIdByRoot( + tokenContractsPairedRoots, + principal.toText(), + ) + ) + .map((principal: Principal) => { + const rootCanisterId = principal.toText(); + const contractId = getTokenContractCanisterIdByRoot( + tokenContractsPairedRoots, + rootCanisterId, + ) as string; + + return { + contractId, + rootCanisterId, + } + }); } + +const getTokenContractCanisterIdByRoot = ( + tokenContractsPairedRoots: TokenContractsPairedRoots, + rootCanisterId: string, +) => { + if (!tokenContractsPairedRoots[rootCanisterId]) { + console.warn(`Oops! Token contract not found for root ${rootCanisterId}, omitted.` ); + + return false; + } + + return tokenContractsPairedRoots[rootCanisterId]; +} \ No newline at end of file diff --git a/packages/dashboard/src/utils/mocks/accountsMockData.ts b/packages/dashboard/src/utils/mocks/accountsMockData.ts index 5553f5c..d6b3e78 100644 --- a/packages/dashboard/src/utils/mocks/accountsMockData.ts +++ b/packages/dashboard/src/utils/mocks/accountsMockData.ts @@ -4,23 +4,24 @@ import { AccountData } from '@components/Tables/AccountsTable'; export const columns = [ { Header: 'Name', - accessor: 'name', + accessor: 'rootCanisterId', }, { Header: 'Canister', - accessor: 'canister', + accessor: 'contractId', }, ]; const NUM_TO_GENERATE = 10; export const generateData = (count: number = NUM_TO_GENERATE) => { - + // TODO: contract id was introduced + // this needs to be refactored at some point const data: AccountData[] = [...new Array(count)].map(() => { const principal = generateRandomPrincipal(); const accountData = { - canister: principal.toText(), - name: 'CanisterX' + contractId: principal.toText(), + rootCanisterId: 'CanisterX' }; return accountData; diff --git a/packages/dashboard/src/utils/mocks/tokenContractsCapRoots.ts b/packages/dashboard/src/utils/mocks/tokenContractsCapRoots.ts new file mode 100644 index 0000000..8d6b527 --- /dev/null +++ b/packages/dashboard/src/utils/mocks/tokenContractsCapRoots.ts @@ -0,0 +1,43 @@ +// Key is Root, value is token contract +// TODO: rename as rootsPairedTokenContract? +export const tokenContractsPairedRoots = { + 'cvfe7-zqaaa-aaaah-qceqa-cai': 'qcg3w-tyaaa-aaaah-qakea-cai', + 'hj662-syaaa-aaaah-qcepq-cai': 'njgly-uaaaa-aaaah-qb6pa-cai', + '2oigl-gaaaa-aaaah-qcgja-cai': 'e3izy-jiaaa-aaaah-qacbq-cai', + '2jja7-lyaaa-aaaah-qcgjq-cai': 'nbg4r-saaaa-aaaah-qap7a-cai', + '24ors-kqaaa-aaaah-qcgka-cai': 'bxdf4-baaaa-aaaah-qaruq-cai', + '23pxg-hiaaa-aaaah-qcgkq-cai': 'uzhxd-ziaaa-aaaah-qanaq-cai', + '2sm42-raaaa-aaaah-qcgla-cai': 'tde7l-3qaaa-aaaah-qansa-cai', + '2vn2o-4yaaa-aaaah-qcglq-cai': 'gevsk-tqaaa-aaaah-qaoca-cai', + '3yd6a-tqaaa-aaaah-qcgma-cai': 'owuqd-dyaaa-aaaah-qapxq-cai', + '37cyu-6iaaa-aaaah-qcgmq-cai': '3db6u-aiaaa-aaaah-qbjbq-cai', + '3wbti-iaaaa-aaaah-qcgna-cai': 'd3ttm-qaaaa-aaaai-qam4a-cai', + '3rav4-fyaaa-aaaah-qcgnq-cai': '73xld-saaaa-aaaah-qbjya-cai', + '3eher-eqaaa-aaaah-qcgoa-cai': 'xkbqi-2qaaa-aaaah-qbpqq-cai', + '3dgcf-jiaaa-aaaah-qcgoq-cai': '6olkk-7iaaa-aaaah-qboaa-cai', + '3kfjz-7aaaa-aaaah-qcgpa-cai': 'kss7i-hqaaa-aaaah-qbvmq-cai', + '3nepn-syaaa-aaaah-qcgpq-cai': 'k4qsa-4aaaa-aaaah-qbvnq-cai', + '6r7vi-zqaaa-aaaah-qcgqa-cai': 'hdxhu-qqaaa-aaaai-aasnq-cai', + '6w6t4-uiaaa-aaaah-qcgqq-cai': 'wxi2q-oiaaa-aaaaj-qab2q-cai', + '675ya-caaaa-aaaah-qcgra-cai': 'sr4qi-vaaaa-aaaah-qcaaq-cai', + '6y46u-pyaaa-aaaah-qcgrq-cai': 'cihkf-qyaaa-aaaah-qb7jq-cai', + '6n3pz-oqaaa-aaaah-qcgsa-cai': 'q6hjz-kyaaa-aaaah-qcama-cai', + '6k2jn-diaaa-aaaah-qcgsq-cai': 'ahl3d-xqaaa-aaaaj-qacca-cai', + '6dzcr-vaaaa-aaaah-qcgta-cai': 'er7d4-6iaaa-aaaaj-qac2q-cai', + '6eyef-yyaaa-aaaah-qcgtq-cai': 'lhq4n-3yaaa-aaaai-qaniq-cai', + '7jwal-xqaaa-aaaah-qcgua-cai': 'nfvlz-jaaaa-aaaah-qcciq-cai', + '7oxg7-2iaaa-aaaah-qcguq-cai': 'oeee4-qaaaa-aaaak-qaaeq-cai', + '7hund-maaaa-aaaah-qcgva-cai': 'pnpu4-3aaaa-aaaah-qcceq-cai', + '7avlx-byaaa-aaaah-qcgvq-cai': 'bid2t-gyaaa-aaaah-qcdea-cai', + '7vs22-aqaaa-aaaah-qcgwa-cai': 'btggw-4aaaa-aaaah-qcdgq-cai', + '7st4o-niaaa-aaaah-qcgwq-cai': 'dv6u3-vqaaa-aaaah-qcdlq-cai', + '73qxs-3aaaa-aaaah-qcgxa-cai': 'crt3j-mqaaa-aaaah-qcdnq-cai', + '74rrg-wyaaa-aaaah-qcgxq-cai': 'cnxby-3qaaa-aaaah-qcdpq-cai', + '5bm7o-fqaaa-aaaah-qcgya-cai': 'ckwhm-wiaaa-aaaah-qcdpa-cai', + '5gnz2-iiaaa-aaaah-qcgyq-cai': 'cdvmq-aaaaa-aaaah-qcdoq-cai', + '5posg-6aaaa-aaaah-qcgza-cai': 'b5el6-hqaaa-aaaah-qcdhq-cai', + '5ipus-tyaaa-aaaah-qcgzq-cai': 'gyuaf-kqaaa-aaaah-qceka-cai', + '55if7-sqaaa-aaaah-qcg2a-cai': 'dknxi-2iaaa-aaaah-qceuq-cai', + '52jdl-7iaaa-aaaah-qcg2q-cai': 'bzsui-sqaaa-aaaah-qce2a-cai', + '5tkix-jaaaa-aaaah-qcg3a-cai': 'ep54t-xiaaa-aaaah-qcdza-cai' +}