Skip to content

Commit

Permalink
feat: 🎸 token contracts paired roots on overview (#34)
Browse files Browse the repository at this point in the history
* chore: 🤖 token contract paired roots mock

* chore: 🤖 token contract to root mapping (WIP)

* refactor: 💡 root paired token contract handling and mapping

* test: 💍 amend utils account tests

* chore: 🤖 set lerna stream output flag
  • Loading branch information
heldrida authored Nov 12, 2021
1 parent 6d2a374 commit 46c3e2d
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 32 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions packages/dashboard/src/components/Link/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,24 @@ export const AccountLink = ({
</PrimaryLink>
);


export const NamedAccountLink = ({
account,
name,
trim,
}: {
name: string,
account: string,
trim?: boolean,
}) => (
<PrimaryLink
to={getRouteByName('AppTransactions', { id: account })}
tableLink={true}
>
{trim ? trimAccount(name) : name}
</PrimaryLink>
);

export const NamedLink = ({
url,
name,
Expand Down
33 changes: 21 additions & 12 deletions packages/dashboard/src/components/Tables/AccountsTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -31,8 +31,8 @@ const Container = styled('div', {
});

export interface AccountData {
canister: string,
name: string,
contractId: string,
rootCanisterId: string,
}

interface Column {
Expand All @@ -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',
},
];

Expand All @@ -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,
Expand All @@ -83,7 +86,7 @@ const AccountDab = ({

return identityInDab
? <IdentityDab name={identityInDab?.name} image={identityInDab?.logo_url} />
: <NamedLink url={'https://dab.ooo'} name='Unnamed' />
: <NamedAccountLink name='Unnamed' account={canisterId} />
};

const AccountsTable = ({
Expand All @@ -98,10 +101,16 @@ const AccountsTable = ({
}) => {
const formatters = useMemo(() => ({
body: {
canister: (cellValue: string) => <AccountLink account={cellValue} trim={false} />,
name: (cellValue: string) => <AccountDab canisterId={cellValue} />,
contractId: (cellValue: string) => {
const found = data.find((x) => x.contractId === cellValue);

if (!found?.rootCanisterId) return '';

return <NamedAccountLink name={cellValue} account={found.rootCanisterId} />;
},
rootCanisterId: (cellValue: string) => <AccountDab canisterId={cellValue} />,
},
} as FormatterTypes), []);
} as FormatterTypes), [data]);

return (
<Container
Expand Down
10 changes: 9 additions & 1 deletion packages/dashboard/src/hooks/store/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,15 @@ export const useAccountStore = create<AccountStore>((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,
Expand Down
20 changes: 16 additions & 4 deletions packages/dashboard/src/utils/account.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand Down
44 changes: 36 additions & 8 deletions packages/dashboard/src/utils/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -52,20 +53,47 @@ export const createBookmarkExpandHandler = ({
return bookmarkExpandHandler;
};

type TokenContractsPairedRoots = Record<string, string>;

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];
}
11 changes: 6 additions & 5 deletions packages/dashboard/src/utils/mocks/accountsMockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
43 changes: 43 additions & 0 deletions packages/dashboard/src/utils/mocks/tokenContractsCapRoots.ts
Original file line number Diff line number Diff line change
@@ -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'
}

0 comments on commit 46c3e2d

Please sign in to comment.