chore(trading): add filters to the proposals table (#3375)

This commit is contained in:
Maciek 2023-04-06 15:30:21 +02:00 committed by GitHub
parent 643d5408cd
commit 7e4aafcb77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 162 additions and 22 deletions

View File

@ -1,5 +1,5 @@
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import { aliasGQLQuery } from '@vegaprotocol/cypress'; import { aliasGQLQuery, checkSorting } from '@vegaprotocol/cypress';
import { marketsQuery } from '@vegaprotocol/mock'; import { marketsQuery } from '@vegaprotocol/mock';
import { getDateTimeFormat } from '@vegaprotocol/utils'; import { getDateTimeFormat } from '@vegaprotocol/utils';
@ -85,6 +85,7 @@ describe('markets table', { tags: '@smoke' }, () => {
cy.getByTestId('view-market-list-link') cy.getByTestId('view-market-list-link')
.should('have.attr', 'href', '#/markets/all') .should('have.attr', 'href', '#/markets/all')
.click(); .click();
cy.get('[data-testid="All markets"]').should( cy.get('[data-testid="All markets"]').should(
'have.attr', 'have.attr',
'data-state', 'data-state',
@ -117,6 +118,85 @@ describe('markets table', { tags: '@smoke' }, () => {
`${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market` `${Cypress.env('VEGA_TOKEN_URL')}/proposals/propose/new-market`
); );
}); });
it('proposed markets tab should be sorted properly', () => {
cy.getByTestId('view-market-list-link').click();
cy.get('[data-testid="Proposed markets"]').click();
const marketColDefault = [
'ETHUSD',
'LINKUSD',
'ETHUSD',
'ETHDAI.MF21',
'AAPL.MF21',
'BTCUSD.MF21',
'TSLA.QM21',
'AAVEDAI.MF21',
'ETHBTC.QM21',
'UNIDAI.MF21',
];
const marketColAsc = [
'AAPL.MF21',
'AAVEDAI.MF21',
'BTCUSD.MF21',
'ETHBTC.QM21',
'ETHDAI.MF21',
'ETHUSD',
'ETHUSD',
'LINKUSD',
'TSLA.QM21',
'UNIDAI.MF21',
];
const marketColDesc = [
'UNIDAI.MF21',
'TSLA.QM21',
'LINKUSD',
'ETHUSD',
'ETHUSD',
'ETHDAI.MF21',
'ETHBTC.QM21',
'BTCUSD.MF21',
'AAVEDAI.MF21',
'AAPL.MF21',
];
checkSorting('market', marketColDefault, marketColAsc, marketColDesc);
const stateColDefault = [
'Open',
'Passed',
'Waiting for Node Vote',
'Open',
'Passed',
'Open',
'Passed',
'Open',
'Waiting for Node Vote',
'Open',
];
const stateColAsc = [
'Open',
'Open',
'Open',
'Open',
'Open',
'Passed',
'Passed',
'Passed',
'Waiting for Node Vote',
'Waiting for Node Vote',
];
const stateColDesc = [
'Waiting for Node Vote',
'Waiting for Node Vote',
'Passed',
'Passed',
'Passed',
'Open',
'Open',
'Open',
'Open',
'Open',
];
checkSorting('state', stateColDefault, stateColAsc, stateColDesc);
});
it('opening auction subsets should be properly displayed', () => { it('opening auction subsets should be properly displayed', () => {
cy.mockTradingPage( cy.mockTradingPage(

View File

@ -52,6 +52,9 @@ export const checkSorting = (
cy.get(`[col-id="${column}"]`).click(); cy.get(`[col-id="${column}"]`).click();
}); });
checkSortChange(orderTabDesc, column); checkSortChange(orderTabDesc, column);
cy.get('.ag-header-container').within(() => {
cy.get(`[col-id="${column}"]`).click();
});
}; };
const checkSortChange = (tabsArr: string[], column: string) => { const checkSortChange = (tabsArr: string[], column: string) => {

View File

@ -11,12 +11,13 @@ import {
AgGridDynamic as AgGrid, AgGridDynamic as AgGrid,
PriceFlashCell, PriceFlashCell,
MarketNameCell, MarketNameCell,
SetFilter,
} from '@vegaprotocol/datagrid'; } from '@vegaprotocol/datagrid';
import { ButtonLink } from '@vegaprotocol/ui-toolkit'; import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import { AgGridColumn } from 'ag-grid-react'; import { AgGridColumn } from 'ag-grid-react';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import * as Schema from '@vegaprotocol/types'; import * as Schema from '@vegaprotocol/types';
import type { MarketMaybeWithData, MarketFieldsFragment } from '../../'; import type { MarketMaybeWithData } from '../../';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets'; import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
const { MarketTradingMode, AuctionTrigger } = Schema; const { MarketTradingMode, AuctionTrigger } = Schema;
@ -33,7 +34,6 @@ export const MarketListTable = forwardRef<
return ( return (
<AgGrid <AgGrid
style={{ width: '100%', height: '100%' }} style={{ width: '100%', height: '100%' }}
overlayNoRowsTemplate={t('No markets')}
getRowId={getRowId} getRowId={getRowId}
ref={ref} ref={ref}
defaultColDef={{ defaultColDef={{
@ -43,7 +43,7 @@ export const MarketListTable = forwardRef<
filter: true, filter: true,
filterParams: { buttons: ['reset'] }, filterParams: { buttons: ['reset'] },
}} }}
suppressCellFocus={true} suppressCellFocus
components={{ PriceFlashCell, MarketNameCell }} components={{ PriceFlashCell, MarketNameCell }}
{...props} {...props}
> >
@ -59,11 +59,11 @@ export const MarketListTable = forwardRef<
/> />
<AgGridColumn <AgGridColumn
headerName={t('Trading mode')} headerName={t('Trading mode')}
field="data" field="tradingMode"
minWidth={170} minWidth={170}
valueGetter={({ valueFormatter={({
data, data,
}: VegaValueGetterParams<MarketMaybeWithData, 'data'>) => { }: VegaValueFormatterParams<MarketMaybeWithData, 'data'>) => {
if (!data?.data) return undefined; if (!data?.data) return undefined;
const { trigger } = data.data; const { trigger } = data.data;
const { tradingMode } = data; const { tradingMode } = data;
@ -75,14 +75,22 @@ export const MarketListTable = forwardRef<
- ${Schema.AuctionTriggerMapping[trigger]}` - ${Schema.AuctionTriggerMapping[trigger]}`
: Schema.MarketTradingModeMapping[tradingMode]; : Schema.MarketTradingModeMapping[tradingMode];
}} }}
filter={SetFilter}
filterParams={{
set: Schema.MarketTradingModeMapping,
}}
/> />
<AgGridColumn <AgGridColumn
headerName={t('Status')} headerName={t('Status')}
field="state" field="state"
valueGetter={({ valueFormatter={({
data, data,
}: VegaValueGetterParams<MarketFieldsFragment, 'state'>) => { }: VegaValueFormatterParams<MarketMaybeWithData, 'state'>) => {
return data?.state ? Schema.MarketStateMapping[data?.state] : '-'; return data?.state ? Schema.MarketStateMapping[data.state] : '-';
}}
filter={SetFilter}
filterParams={{
set: Schema.MarketStateMapping,
}} }}
/> />
<AgGridColumn <AgGridColumn

View File

@ -1,4 +1,6 @@
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';
import type { AgGridReact } from 'ag-grid-react';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { MarketListTable } from './market-list-table'; import { MarketListTable } from './market-list-table';
@ -12,15 +14,24 @@ interface MarketsContainerProps {
} }
export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => { export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0);
const { data, error, loading, reload } = useDataProvider({ const { data, error, loading, reload } = useDataProvider({
dataProvider, dataProvider,
skipUpdates: true, skipUpdates: true,
variables: undefined, variables: undefined,
}); });
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [data]);
const onFilterChanged = useCallback(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
return ( return (
<div className="h-full relative"> <div className="h-full relative">
<MarketListTable <MarketListTable
rowData={error ? [] : data} ref={gridRef}
rowData={data}
suppressLoadingOverlay suppressLoadingOverlay
suppressNoRowsOverlay suppressNoRowsOverlay
onCellClicked={(cellEvent: CellClickedEvent) => { onCellClicked={(cellEvent: CellClickedEvent) => {
@ -40,6 +51,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
); );
}} }}
onMarketClick={onSelect} onMarketClick={onSelect}
onFilterChanged={onFilterChanged}
/> />
<div className="pointer-events-none absolute inset-0"> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer <AsyncRenderer
@ -47,6 +59,7 @@ export const MarketsContainer = ({ onSelect }: MarketsContainerProps) => {
error={error} error={error}
data={data} data={data}
noDataMessage={t('No markets')} noDataMessage={t('No markets')}
noDataCondition={() => !dataCount}
reload={reload} reload={reload}
/> />
</div> </div>

View File

@ -91,6 +91,6 @@ describe('ProposalsList', () => {
await waitFor(() => { await waitFor(() => {
expect(container).toBeInTheDocument(); expect(container).toBeInTheDocument();
}); });
expect(screen.getByText('No Rows To Show')).toBeInTheDocument(); expect(screen.getByText('No markets')).toBeInTheDocument();
}); });
}); });

View File

@ -1,13 +1,21 @@
import { useCallback, useEffect, useRef, useState } from 'react';
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit'; import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid'; import { AgGridDynamic as AgGrid } from '@vegaprotocol/datagrid';
import { useDataProvider } from '@vegaprotocol/react-helpers'; import { useDataProvider } from '@vegaprotocol/react-helpers';
import { t } from '@vegaprotocol/i18n';
import * as Types from '@vegaprotocol/types'; import * as Types from '@vegaprotocol/types';
import { proposalsDataProvider } from '../proposals-data-provider'; import { proposalsDataProvider } from '../proposals-data-provider';
import { useCallback, useRef } from 'react';
import type { AgGridReact } from 'ag-grid-react'; import type { AgGridReact } from 'ag-grid-react';
import { useColumnDefs } from './use-column-defs'; import { useColumnDefs } from './use-column-defs';
import type { ProposalListFieldsFragment } from '../proposals-data-provider/__generated__/Proposals'; import type { ProposalListFieldsFragment } from '../proposals-data-provider/__generated__/Proposals';
// prevent cutting filter windows by auto-height layout
const CUSTOM_GRID_STYLES = `
.ag-layout-auto-height {
min-height: 200px !important;
}
`;
export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) => export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
data.filter((proposal) => data.filter((proposal) =>
[ [
@ -19,6 +27,7 @@ export const getNewMarketProposals = (data: ProposalListFieldsFragment[]) =>
export const ProposalsList = () => { export const ProposalsList = () => {
const gridRef = useRef<AgGridReact | null>(null); const gridRef = useRef<AgGridReact | null>(null);
const [dataCount, setDataCount] = useState(0);
const handleOnGridReady = useCallback(() => { const handleOnGridReady = useCallback(() => {
gridRef.current?.api?.sizeColumnsToFit(); gridRef.current?.api?.sizeColumnsToFit();
}, [gridRef]); }, [gridRef]);
@ -30,22 +39,37 @@ export const ProposalsList = () => {
}); });
const filteredData = getNewMarketProposals(data || []); const filteredData = getNewMarketProposals(data || []);
const { columnDefs, defaultColDef } = useColumnDefs(); const { columnDefs, defaultColDef } = useColumnDefs();
useEffect(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, [filteredData]);
const onFilterChanged = useCallback(() => {
setDataCount(gridRef.current?.api?.getModel().getRowCount() ?? 0);
}, []);
return ( return (
<AsyncRenderer <div className="relative">
loading={loading}
error={error}
data={filteredData}
reload={reload}
>
<AgGrid <AgGrid
ref={gridRef} ref={gridRef}
className="w-full h-full"
domLayout="autoHeight" domLayout="autoHeight"
className="min-w-full"
columnDefs={columnDefs} columnDefs={columnDefs}
rowData={filteredData} rowData={filteredData}
defaultColDef={defaultColDef} defaultColDef={defaultColDef}
onGridReady={handleOnGridReady} onGridReady={handleOnGridReady}
suppressLoadingOverlay
suppressNoRowsOverlay
onFilterChanged={onFilterChanged}
customThemeParams={CUSTOM_GRID_STYLES}
/> />
</AsyncRenderer> <div className="pointer-events-none absolute inset-0">
<AsyncRenderer
loading={loading}
error={error}
data={filteredData}
noDataMessage={t('No markets')}
noDataCondition={() => !dataCount}
reload={reload}
/>
</div>
</div>
); );
}; };

View File

@ -1,6 +1,7 @@
import { useMemo } from 'react'; import { useMemo } from 'react';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import type { ColDef } from 'ag-grid-community'; import type { ColDef } from 'ag-grid-community';
import { DateRangeFilter, SetFilter } from '@vegaprotocol/datagrid';
import { useEnvironment } from '@vegaprotocol/environment'; import { useEnvironment } from '@vegaprotocol/environment';
import { getDateTimeFormat } from '@vegaprotocol/utils'; import { getDateTimeFormat } from '@vegaprotocol/utils';
import { t } from '@vegaprotocol/i18n'; import { t } from '@vegaprotocol/i18n';
@ -73,6 +74,10 @@ export const useColumnDefs = () => {
value, value,
}: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) => }: VegaValueFormatterParams<ProposalListFieldsFragment, 'state'>) =>
value ? ProposalStateMapping[value] : '-', value ? ProposalStateMapping[value] : '-',
filter: SetFilter,
filterParams: {
set: ProposalStateMapping,
},
}, },
{ {
colId: 'voting', colId: 'voting',
@ -99,6 +104,7 @@ export const useColumnDefs = () => {
} }
return '-'; return '-';
}, },
filter: false,
}, },
{ {
colId: 'closing-date', colId: 'closing-date',
@ -110,6 +116,7 @@ export const useColumnDefs = () => {
ProposalListFieldsFragment, ProposalListFieldsFragment,
'terms.closingDatetime' 'terms.closingDatetime'
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-'), >) => (value ? getDateTimeFormat().format(new Date(value)) : '-'),
filter: DateRangeFilter,
}, },
{ {
colId: 'enactment-date', colId: 'enactment-date',
@ -121,13 +128,18 @@ export const useColumnDefs = () => {
ProposalListFieldsFragment, ProposalListFieldsFragment,
'terms.enactmentDatetime' 'terms.enactmentDatetime'
>) => (value ? getDateTimeFormat().format(new Date(value)) : '-'), >) => (value ? getDateTimeFormat().format(new Date(value)) : '-'),
filter: DateRangeFilter,
}, },
]; ];
}, [VEGA_TOKEN_URL, requiredMajorityPercentage]); }, [VEGA_TOKEN_URL, requiredMajorityPercentage]);
const defaultColDef: ColDef = useMemo(() => { const defaultColDef: ColDef = useMemo(() => {
return { return {
sortable: false, sortable: true,
cellClass: cellCss, cellClass: cellCss,
flex: 1,
resizable: true,
filter: true,
filterParams: { buttons: ['reset'] },
}; };
}, []); }, []);
return useMemo( return useMemo(