chore(explorer): change asset dialog to details page (#2941)

This commit is contained in:
Art 2023-02-20 15:15:18 +01:00 committed by GitHub
parent 7e957a2841
commit 4b83a10475
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 113 additions and 51 deletions

View File

@ -4,15 +4,6 @@ context('Asset page', { tags: '@regression' }, () => {
describe('Verify elements on page', () => { describe('Verify elements on page', () => {
before('Navigate to assets page', () => { before('Navigate to assets page', () => {
cy.visit('/assets'); cy.visit('/assets');
// Check we have enough enough assets
cy.getAssets().then((assets) => {
assert.isAtLeast(
Object.keys(assets).length,
5,
'Ensuring we have at least 5 assets to test'
);
});
}); });
it('should be able to see full assets list', () => { it('should be able to see full assets list', () => {
@ -40,7 +31,7 @@ context('Asset page', { tags: '@regression' }, () => {
}); });
}); });
it('should open details dialog when clicked on "View details"', () => { it('should open details page when clicked on "View details"', () => {
cy.getAssets().then((assets) => { cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => { Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`) cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
@ -49,8 +40,8 @@ context('Asset page', { tags: '@regression' }, () => {
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`) cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
.eq(0) .eq(0)
.click(); .click();
cy.getByTestId('dialog-content').should('be.visible'); cy.getByTestId('asset-header').should('have.text', asset.name);
cy.getByTestId('dialog-close').click(); cy.go('back');
}); });
}); });
}); });

View File

@ -1,22 +1,35 @@
import { render, waitFor } from '@testing-library/react'; import { render, waitFor } from '@testing-library/react';
import { MemoryRouter } from 'react-router-dom';
import { assetsList } from '../../mocks/assets'; import { assetsList } from '../../mocks/assets';
import { AssetsTable } from './assets-table'; import { AssetsTable } from './assets-table';
describe('AssetsTable', () => { describe('AssetsTable', () => {
it('shows loading message on first render', async () => { it('shows loading message on first render', async () => {
const res = render(<AssetsTable data={null} />); const res = render(
<MemoryRouter>
<AssetsTable data={null} />
</MemoryRouter>
);
expect(await res.findByText('Loading...')).toBeInTheDocument(); expect(await res.findByText('Loading...')).toBeInTheDocument();
}); });
it('shows no data message if no assets found', async () => { it('shows no data message if no assets found', async () => {
const res = render(<AssetsTable data={[]} />); const res = render(
<MemoryRouter>
<AssetsTable data={[]} />
</MemoryRouter>
);
expect( expect(
await res.findByText('This chain has no assets') await res.findByText('This chain has no assets')
).toBeInTheDocument(); ).toBeInTheDocument();
}); });
it('shows a table/list with all the assets', async () => { it('shows a table/list with all the assets', async () => {
const res = render(<AssetsTable data={assetsList} />); const res = render(
<MemoryRouter>
<AssetsTable data={assetsList} />
</MemoryRouter>
);
await waitFor(() => { await waitFor(() => {
const rowA1 = res.container.querySelector('[row-id="123"]'); const rowA1 = res.container.querySelector('[row-id="123"]');
expect(rowA1).toBeInTheDocument(); expect(rowA1).toBeInTheDocument();

View File

@ -1,5 +1,4 @@
import type { AssetFieldsFragment } from '@vegaprotocol/assets'; import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets'; import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/react-helpers'; import { t } from '@vegaprotocol/react-helpers';
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit'; import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
@ -9,15 +8,14 @@ import { AgGridColumn } from 'ag-grid-react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit'; import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { useRef, useLayoutEffect } from 'react'; import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints'; import { BREAKPOINT_MD } from '../../config/breakpoints';
import { useNavigate } from 'react-router-dom';
import type { RowClickedEvent } from 'ag-grid-community';
type AssetsTableProps = { type AssetsTableProps = {
data: AssetFieldsFragment[] | null; data: AssetFieldsFragment[] | null;
}; };
export const AssetsTable = ({ data }: AssetsTableProps) => { export const AssetsTable = ({ data }: AssetsTableProps) => {
const openAssetDetailsDialog = useAssetDetailsDialogStore( const navigate = useNavigate();
(state) => state.open
);
const ref = useRef<AgGridReact>(null); const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => { const showColumnsOnDesktop = () => {
ref.current?.columnApi.setColumnsVisible( ref.current?.columnApi.setColumnsVisible(
@ -49,17 +47,23 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
autoHeight: true, autoHeight: true,
}} }}
suppressCellFocus={true} suppressCellFocus={true}
onGridReady={() => { onRowClicked={({ data }: RowClickedEvent) => {
showColumnsOnDesktop(); navigate(data.id);
}} }}
> >
<AgGridColumn headerName={t('Symbol')} field="symbol" /> <AgGridColumn headerName={t('Symbol')} field="symbol" />
<AgGridColumn headerName={t('Name')} field="name" /> <AgGridColumn headerName={t('Name')} field="name" />
<AgGridColumn flex="2" headerName={t('ID')} field="id" /> <AgGridColumn
flex="2"
headerName={t('ID')}
field="id"
hide={window.innerWidth < BREAKPOINT_MD}
/>
<AgGridColumn <AgGridColumn
colId="type" colId="type"
headerName={t('Type')} headerName={t('Type')}
field="source.__typename" field="source.__typename"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) => valueFormatter={({ value }: { value?: string }) =>
value && AssetTypeMapping[value].value value && AssetTypeMapping[value].value
} }
@ -67,6 +71,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
<AgGridColumn <AgGridColumn
headerName={t('Status')} headerName={t('Status')}
field="status" field="status"
hide={window.innerWidth < BREAKPOINT_MD}
valueFormatter={({ value }: { value?: string }) => valueFormatter={({ value }: { value?: string }) =>
value && AssetStatusMapping[value].value value && AssetStatusMapping[value].value
} }
@ -83,28 +88,13 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
value, value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) => }: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? ( value ? (
<div className="pb-1"> <ButtonLink
<ButtonLink onClick={(e) => {
onClick={(e) => { navigate(value);
openAssetDetailsDialog(value, e.target as HTMLElement); }}
}} >
> {t('View details')}
{t('View details')} </ButtonLink>
</ButtonLink>{' '}
<span className="max-md:hidden">
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(
value,
e.target as HTMLElement,
true
);
}}
>
{t('View JSON')}
</ButtonLink>
</span>
</div>
) : ( ) : (
'' ''
) )

View File

@ -5,9 +5,12 @@ import {
useAssetDataProvider, useAssetDataProvider,
useAssetDetailsDialogStore, useAssetDetailsDialogStore,
} from '@vegaprotocol/assets'; } from '@vegaprotocol/assets';
import { useNavigate } from 'react-router-dom';
import { Routes } from '../../../routes/route-names';
export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & { export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
assetId: string; assetId: string;
asDialog?: boolean;
}; };
/** /**
@ -15,17 +18,22 @@ export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
* with a link to the assets modal. If the name does not come back * with a link to the assets modal. If the name does not come back
* it will use the ID instead. * it will use the ID instead.
*/ */
export const AssetLink = ({ assetId, ...props }: AssetLinkProps) => { export const AssetLink = ({ assetId, asDialog, ...props }: AssetLinkProps) => {
const { data: asset } = useAssetDataProvider(assetId); const { data: asset } = useAssetDataProvider(assetId);
const open = useAssetDetailsDialogStore((state) => state.open); const open = useAssetDetailsDialogStore((state) => state.open);
const navigate = useNavigate();
const label = asset?.name ? asset.name : assetId; const label = asset?.name ? asset.name : assetId;
return ( return (
<ButtonLink <ButtonLink
data-testid="asset-link" data-testid="asset-link"
disabled={!asset} disabled={!asset}
onClick={(e) => { onClick={(e) => {
open(assetId, e.target as HTMLElement); if (asDialog) {
open(assetId, e.target as HTMLElement);
} else {
navigate(`${Routes.ASSETS}/${asset?.id}`);
}
}} }}
{...props} {...props}
> >

View File

@ -0,0 +1,50 @@
import { t } from '@vegaprotocol/react-helpers';
import { RouteTitle } from '../../components/route-title';
import { AsyncRenderer, Button } from '@vegaprotocol/ui-toolkit';
import { useScrollToLocation } from '../../hooks/scroll-to-location';
import { useDocumentTitle } from '../../hooks/use-document-title';
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { AssetDetailsTable, useAssetDataProvider } from '@vegaprotocol/assets';
import { useParams } from 'react-router-dom';
import { JsonViewerDialog } from '../../components/dialogs/json-viewer-dialog';
import { useState } from 'react';
export const AssetPage = () => {
useDocumentTitle(['Assets']);
useScrollToLocation();
const { assetId } = useParams<{ assetId: string }>();
const { data, loading, error } = useAssetDataProvider(assetId || '');
const title = data ? data.name : error ? t('Asset not found') : '';
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
return (
<>
<section className="relative">
<RouteTitle data-testid="asset-header">{title}</RouteTitle>
<AsyncRenderer
noDataMessage={t('Asset not found')}
data={data}
loading={loading}
error={error}
>
<div className="absolute top-0 right-0">
<Button size="xs" onClick={() => setDialogOpen(true)}>
{t('View JSON')}
</Button>
</div>
<div className="h-full relative">
<AssetDetailsTable asset={data as AssetFieldsFragment} />
</div>
</AsyncRenderer>
</section>
<JsonViewerDialog
open={dialogOpen}
onChange={(isOpen) => setDialogOpen(isOpen)}
title={data?.name || ''}
content={data}
/>
</>
);
};

View File

@ -6,7 +6,7 @@ import { useDocumentTitle } from '../../hooks/use-document-title';
import { useAssetsDataProvider } from '@vegaprotocol/assets'; import { useAssetsDataProvider } from '@vegaprotocol/assets';
import { AssetsTable } from '../../components/assets/assets-table'; import { AssetsTable } from '../../components/assets/assets-table';
export const Assets = () => { export const AssetsPage = () => {
useDocumentTitle(['Assets']); useDocumentTitle(['Assets']);
useScrollToLocation(); useScrollToLocation();

View File

@ -1 +1,2 @@
export * from './assets'; export * from './assets-page';
export * from './asset-page';

View File

@ -1,4 +1,4 @@
import { Assets } from './assets'; import { AssetPage, AssetsPage } from './assets';
import BlockPage from './blocks'; import BlockPage from './blocks';
import Governance from './governance'; import Governance from './governance';
import Home from './home'; import Home from './home';
@ -53,7 +53,16 @@ const assetsRoutes: Route[] = flags.assets
path: Routes.ASSETS, path: Routes.ASSETS,
text: t('Assets'), text: t('Assets'),
name: 'Assets', name: 'Assets',
element: <Assets />, children: [
{
index: true,
element: <AssetsPage />,
},
{
path: ':assetId',
element: <AssetPage />,
},
],
}, },
] ]
: []; : [];