chore(explorer): change asset dialog to details page (#2941)
This commit is contained in:
parent
7e957a2841
commit
4b83a10475
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -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();
|
||||||
|
@ -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>
|
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
)
|
)
|
||||||
|
@ -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}
|
||||||
>
|
>
|
||||||
|
50
apps/explorer/src/app/routes/assets/asset-page.tsx
Normal file
50
apps/explorer/src/app/routes/assets/asset-page.tsx
Normal 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}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -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();
|
||||||
|
|
@ -1 +1,2 @@
|
|||||||
export * from './assets';
|
export * from './assets-page';
|
||||||
|
export * from './asset-page';
|
||||||
|
@ -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 />,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: [];
|
: [];
|
||||||
|
Loading…
Reference in New Issue
Block a user