Compare commits

...
Author SHA1 Message Date
Edd ef15184a0b Merge branch 'develop' into feat/2120-announce-node-tx 2023-02-10 12:04:44 +00:00
Edd 5f5cb965e3 feat(explorer): add vega key and eth key rotate views (#2891) 2023-02-10 12:03:50 +00:00
mattrussell36 86090c295c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-10 06:05:53 +00:00
mattrussell36 6983587f28 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-10 00:10:28 +00:00
mattrussell36 5e87baf174 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 18:07:43 +00:00
ArtandEdd fc93bbd7c5 feat(explorer): asset details (#2882)
Co-authored-by: Edd <edd@vega.xyz>
2023-02-09 18:12:38 +01:00
Edd ce832ad6f4 feat(explorer): state variable proposal tx (#2837) 2023-02-09 15:29:46 +00:00
Bartłomiej Głownia a5d53eee77 chore(trading): live update deal ticket market data (#2570) 2023-02-09 14:20:31 +00:00
Edd 6c5172405b feat(explorer): add basic node annouce tx view 2023-02-09 13:50:32 +00:00
Maciek 4dd63da62b chore(trading): handle positions with market data gql errors (#2884) 2023-02-09 14:26:23 +01:00
mattrussell36 2e8dd294de chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 12:06:41 +00:00
Joe Tsang 8917ceb08a test(token): e2e tests for validator tooltips (#2883) 2023-02-09 10:35:05 +00:00
mattrussell36 87f116daee chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 06:07:31 +00:00
mattrussell36 9e91746488 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-09 00:12:29 +00:00
mattrussell36 5b4ed1a0c3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 18:07:17 +00:00
Bartłomiej Głownia 56731e34cc chore(react-helpers): add generic data provider unit test (#2776) 2023-02-08 17:35:10 +00:00
daro-maj 026e5f5679 chore(trading): key to key transfers e2e tests (#2878) 2023-02-08 18:31:41 +01:00
Daniel e75c160579 feat(ci): archive logs in cypres pr run (#2879) 2023-02-08 16:37:01 +00:00
mattrussell36 5d613396e1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 12:09:14 +00:00
Sam Keen da551f9d3c feat(governance): replace proposal and vote busEvents with subscriptions (#2862) 2023-02-08 09:42:20 +00:00
Edd 47e4861fdb fix(explorer): use position decimal places to format size in order (#2861) 2023-02-08 08:22:27 +00:00
mattrussell36 1a85a89440 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 06:06:13 +00:00
mattrussell36 7ab412a8f7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-08 00:12:40 +00:00
Edd 5a14174a81 feat(explorer): add issuesignatures tx view (#2867) 2023-02-07 18:17:00 +00:00
mattrussell36 5e00b93783 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 18:06:49 +00:00
Edd 31d9b023e6 feat(explorer): change party to pubkey (#2868) 2023-02-07 18:00:26 +00:00
mattrussell36 103e503a47 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 12:07:30 +00:00
Maciek 1952cb0e78 fix(trading): inform the user his connection to wallet is lost (#2863) 2023-02-07 11:31:15 +01:00
mattrussell36 69340f4ddf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 06:06:15 +00:00
mattrussell36 c52bf2200e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-07 00:10:59 +00:00
Matthew Russell 8dccee69f2 chore(trading): remove unnecessary sentry capture from asyncrenderer component (#2869) 2023-02-06 15:21:39 -08:00
Art c7a6fdd879 chore(trading): new toasts designs (#2779) 2023-02-06 20:09:56 +00:00
Matthew Russell 8bcdaf4cda feat(trading): key to key transfers (#2784) 2023-02-06 11:35:40 -08:00
mattrussell36 3eb1359504 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 18:08:25 +00:00
Sam Keen 39cf8ad4d6 feat(governance): remove some performance info from validator mouseover (#2865) 2023-02-06 17:08:34 +00:00
Edd faef98f0ae fix(explorer): type generation broken in an oracle refactor (#2859) 2023-02-06 13:29:23 +00:00
macqbat 710e2daa27 chore(trading): notification component redesign (#2845) 2023-02-06 13:31:35 +01:00
daro-maj 06ea3924fa chore(trading): increase withdraw ac test coverage (#2853) 2023-02-06 13:27:12 +01:00
mattrussell36 965e2d8972 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 12:09:01 +00:00
m.ray 73de2fed43 fix(trading): when order is rejected change tx toast intent (#2851) 2023-02-06 11:44:04 +00:00
Sam Keen 536859e067 feat(governance): visual tweaks to main nav (#2839) 2023-02-06 11:18:14 +00:00
Sam Keen 54bfaef473 fix(governance): empty div disrupts other page elements (#2855) 2023-02-06 10:35:26 +00:00
Matthew Russell e2cad707a7 chore(trading): add validator testnet env file (#2852) 2023-02-06 09:04:34 +00:00
mattrussell36 ec8d9798ab chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 06:08:16 +00:00
mattrussell36 e629c1e81a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-06 00:10:27 +00:00
mattrussell36 25feed793c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 18:08:10 +00:00
mattrussell36 2867367e16 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 12:07:47 +00:00
mattrussell36 50b408e74d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 06:05:57 +00:00
mattrussell36 8f531c7c05 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-05 00:13:19 +00:00
mattrussell36 350d16fee1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 18:07:27 +00:00
mattrussell36 c052c66266 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 12:06:25 +00:00
mattrussell36 e2032df236 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-04 06:07:41 +00:00
Matthew Russell 9ece19869d chore(trading): update order subs to not use busevents (#2849) 2023-02-03 16:12:20 -08:00
Joe Tsang 22abc8160c test(token): pending balances (#2843) 2023-02-03 19:26:17 +00:00
Edd b1a7a22bf9 feat(explorer): add protocol upgrade view (#2802) 2023-02-03 19:25:36 +00:00
mattrussell36 c4b38cc0dc chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 18:05:46 +00:00
m.ray 44d6d931d5 fix(trading): update async renderer on error (#2842) 2023-02-03 16:52:10 +00:00
m.ray 7f1e47d7fd fix(trading): remove price flash cell from PNL (#2829) 2023-02-03 14:59:24 +00:00
Art 0eddfa4073 fix(ui-toolkit): dark and light shades mismatch (#2841) 2023-02-03 12:33:13 +00:00
mattrussell36 83a034ce91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 12:06:17 +00:00
5ab5aa01a2 feat(trading): 2803 console announcement banner (#2830)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-02-03 10:34:32 +00:00
mattrussell36 b22f9156b5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 06:07:49 +00:00
mattrussell36 bab5e2018b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-03 00:12:30 +00:00
macqbat 3f7692ce75 chore(trading): add alpha font to trading (#2833) 2023-02-02 19:25:14 +01:00
mattrussell36 b69fb3b0e5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 18:08:33 +00:00
macqbat 83af6e74f6 chore(trading): no data message in charts (#2816) 2023-02-02 18:43:16 +01:00
m.ray b9908003ba fix(trading): fix symbol on order estimates (#2827) 2023-02-02 13:30:47 +00:00
Sam KeenandMatthew Russell 1f9af79215 feat(ui-toolkit): mainnet sims announcement banner (#2806)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-02-02 13:13:55 +00:00
Sam Keen e31e40a7ef feat(ui-toolkit): mainnet sims announcement banner (#2806) 2023-02-02 12:56:22 +00:00
mattrussell36 81f6b3cc15 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 12:08:12 +00:00
mattrussell36 8adb208a91 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 06:06:32 +00:00
mattrussell36 545c3a17d2 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-02 00:09:59 +00:00
mattrussell36 ad76988f63 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 18:08:30 +00:00
Sam KeenandDexter d2bbd7e1fc feat(governance): pending balances: add event listeners (#2541)
Co-authored-by: Dexter <dexter.edwards93@gmail.com>
2023-02-01 16:02:50 +00:00
macqbat fb1ebb3bdf chore(trading): remove noise form the test logs (#2801) 2023-02-01 16:58:47 +01:00
m.ray b2489537ef fix(trading): market data and auction time not updating (#2813) 2023-02-01 14:08:49 +00:00
Sam Keen 382a7ea411 feat(governance): body can scroll again after clicking 'back to staking' (#2785) 2023-02-01 12:56:25 +00:00
mattrussell36 1ae2b4c153 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 12:06:46 +00:00
mattrussell36 e8875e817e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 06:06:26 +00:00
mattrussell36 4a01c880f3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-02-01 00:10:41 +00:00
mattrussell36 f7f00dc7bb chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 18:07:31 +00:00
macqbat d151b6e923 chore(trading): add sub statuses of opening auction trading mode (#2797) 2023-01-31 17:31:55 +01:00
Sam Keen 8f8e9c1061 feat(ui-toolkit): announcement banner to highlight mainnet sims (#2800) 2023-01-31 16:12:44 +00:00
m.ray b40358a723 fix(trading): consolidate view as user mode (#2778) 2023-01-31 16:04:52 +00:00
Art 01f0934da3 chore(ui-toolkit): new colour palette (#2783) 2023-01-31 16:33:16 +01:00
mattrussell36 df7755dbc4 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 12:06:33 +00:00
Joe Tsang 9073234040 test(ci): fix tags in cypress workflow (#2788) 2023-01-31 11:35:42 +00:00
Art 053cd0fa27 fix(trading): asset option on mobile got overflown (#2782) 2023-01-31 11:10:55 +01:00
mattrussell36 961dc88dc7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 06:08:30 +00:00
mattrussell36 b16607a844 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-31 00:13:30 +00:00
Matthew Russell d0d804631c chore(ci): add nx scope linting for pr title (#2781) 2023-01-30 21:06:18 +00:00
daro-maj 3e778a88dc chore: capsule tests refactor and increase deposit ac (#2773) 2023-01-30 20:35:58 +01:00
Edd dbb09c9c79 fix(explorer): correct calculation for whether party txs list has more txs (#2774) 2023-01-30 18:23:09 +00:00
Edd 06f44b67de fix(explorer): tx viewer dropdown did not correctly represent state (#2780) 2023-01-30 18:17:47 +00:00
Joe Tsang 1861852852 test(2704): proposals with market (#2775) 2023-01-30 18:06:04 +00:00
mattrussell36 3ee0eee80d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 12:09:00 +00:00
mattrussell36 69ce870a81 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 06:06:56 +00:00
mattrussell36 d8467c3206 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-30 00:12:51 +00:00
mattrussell36 de87f40a01 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 18:08:12 +00:00
mattrussell36 f22fb56afd chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 12:06:12 +00:00
mattrussell36 f2627e6ebf chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 06:07:03 +00:00
mattrussell36 24e6a13d93 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-29 00:13:02 +00:00
mattrussell36 b56050ad1c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 18:07:37 +00:00
mattrussell36 cff83ed062 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 12:06:05 +00:00
mattrussell36 8ecce874b0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 06:08:30 +00:00
Bartłomiej Głownia 00e319b3c6 chore: handle not found errors as correct response (#2759) 2023-01-27 18:35:45 -08:00
mattrussell36 05559f3ea0 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-28 00:11:14 +00:00
m.ray 4b3b5c322a fix: trades grid colors (#2766) 2023-01-27 18:55:20 +00:00
Bartłomiej Głownia b1280c8285 fix: hide close button if openVolume is zero (#2768) 2023-01-27 18:44:20 +00:00
Edd 1e0e1c7859 feat(explorer): add long text component for hashes and tx viewer (#2765) 2023-01-27 18:37:59 +00:00
mattrussell36 ec2bb81ec8 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 18:08:36 +00:00
m.ray fe95c6fcbc fix: show markets link & deal ticket validation (#2763) 2023-01-27 18:10:20 +01:00
macqbat 853ec8f69c chore(2351): improve handling wallet errors (#2729) 2023-01-27 15:45:29 +01:00
mattrussell36 b68c090ee5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 12:09:03 +00:00
Matthew Russellandasiaznik cb9b811730 feat(#1791): block height check (#2701)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
2023-01-27 11:31:23 +01:00
Bartłomiej Głownia 98b5260d93 feat: fix positions and margins providers update (#2753) 2023-01-27 09:39:39 +01:00
mattrussell36 613262f7a5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 06:05:57 +00:00
mattrussell36 9ae6f5201c chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-27 00:10:54 +00:00
m.ray cfb75fcf8f fix: volume charts position dp (#2756) 2023-01-26 19:36:29 +00:00
mattrussell36 ae6ccdfb52 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-26 18:08:18 +00:00
Radosław Szpiech 9e73c6a462 chore: 2593 trading-e2e refactor - split global.cy (#2754) 2023-01-26 17:50:33 +01:00
Edd 24c017bc2b feat(explorer): add basic votesubmission tx view (#2694) 2023-01-26 14:53:34 +00:00
Edd 3188f23439 fix(explorer): place sensible limits on oracles page queries (#2699) 2023-01-26 14:50:52 +00:00
Edd 33e528c569 fix(explorer): switch decimals used in lp commitment views (#2747) 2023-01-26 14:47:31 +00:00
Radosław Szpiech e10945eeda chore: correct ethereum releated envs in trading-e2e (#2744) 2023-01-26 15:40:49 +01:00
m.ray 47b2a2fdcb fix: volume chart values fixing - position dp (#2749) 2023-01-26 14:21:43 +00:00
m.ray 860938f26c fix: market selected no longer available - add link to all markets (#2748) 2023-01-26 13:46:44 +00:00
Art 5766d84804 fix: select position (#2745) 2023-01-26 12:36:47 +00:00
mattrussell36 6362c022b5 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-26 12:06:16 +00:00
Bartłomiej Głownia a6118c14dd feat: fix EthTxCompletedToastContent (#2741) 2023-01-26 12:00:00 +00:00
Sam Keen d384fb35f1 feat(2396): token delegations pagination (#2575) 2023-01-26 10:20:26 +00:00
Ciaran McGhie 3ed3714e79 fix(#2216): lp-dashboard grab market close from metadata (#2564) 2023-01-26 09:58:39 +00:00
Matthew Russell 3893b26d30 fix: dropdown menu portals (#2740) 2023-01-26 09:52:49 +01:00
mattrussell36 1ba0ad234b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-26 06:07:07 +00:00
mattrussell36 9f6a5aac39 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-26 00:13:06 +00:00
Bartłomiej GłowniaandMatthew Russell 129274a8e6 feat: add seperate transaction toast for completed eth transaction (#2733)
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-01-25 12:25:44 -08:00
Bartłomiej Głownia 3fadda15fd chore: remove sentry captureException in marketDepthProvider update (#2713) 2023-01-25 11:58:37 -08:00
m.rayandasiaznik 7ea7edc1e2 feat(#2565): orderbook populate limit (#2690)
Co-authored-by: asiaznik <artur@vegaprotocol.io>
2023-01-25 11:38:26 -08:00
Edd dad953b45b fix(explorer): fix various small lp tx view bugs (#2738) 2023-01-25 18:18:59 +00:00
mattrussell36 ed1363b035 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-25 18:08:10 +00:00
Art b2ab4f49d9 feat: rich asset details dropdown (2557) (#2711) 2023-01-25 14:24:53 +00:00
Edd a60379b40f feat(explorer): data submission tx view (#2698) 2023-01-25 13:33:12 +00:00
mattrussell36 7db2a58f9d chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-25 12:08:53 +00:00
m.ray 7279556bf7 feat(#2599): liquidity price range added in market info (#2716) 2023-01-25 13:42:29 +02:00
Dexter Edwards 52cc2ef7df fix: add risk parameters (#2706) 2023-01-25 10:36:22 +00:00
macqbat a218da93a6 feat: logging - working solution (#2696) 2023-01-25 09:43:11 +01:00
daro-maj 10fe82dea8 chore: deposit and withdrawal capsule e2e tests (#2720) 2023-01-25 08:21:17 +01:00
mattrussell36 b0de4dfbf3 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-25 06:06:42 +00:00
Matthew Russell cd0e2e41a8 chore(#2668): use ui-toolkit checkbox for orderbook (#2702) 2023-01-24 21:49:59 -08:00
mattrussell36 dc3330d55e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-25 00:12:48 +00:00
Matthew Russell a016feab2a fix: dont use current value in update callback for estimate queries (#2721) 2023-01-24 21:33:55 +00:00
mattrussell36 68446ccf65 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-24 18:08:20 +00:00
Matthew Russell c7aec5cfcb chore: add cypress files to ignore list and use glob ignore option (#2700) 2023-01-24 08:06:46 -08:00
Radosław Szpiech f58144d785 chore: fix skip cache typo in workflow (#2715) 2023-01-24 15:59:53 +00:00
Dexter Edwards 415b902f53 feat: splash loader more central (#2707) 2023-01-24 14:02:43 +00:00
165b131b27 chore: move token's web3 url connector to web3 lib (#2655)
Co-authored-by: Dexter <dexter.edwards93@gmail.com>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2023-01-24 14:02:17 +00:00
Bartłomiej Głownia 0cff5895c7 feat: call update with null data from useDataProvider hook on variables change (#2689) 2023-01-24 14:01:51 +00:00
Dexter Edwards 31881f8e5a fix: calculation of required stake (#2687) 2023-01-24 14:00:38 +00:00
Art 346af6118d chore: consolidate persisted order entries (#2673) 2023-01-24 13:56:12 +00:00
Dexter Edwards bcbc3bb146 fix: adjust route strcuture (#2708) 2023-01-24 13:44:11 +00:00
Radosław Szpiech d7440cfe54 chore: correct runner name (#2710) 2023-01-24 13:25:29 +01:00
mattrussell36 801606e37f chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-24 12:08:25 +00:00
Dexter Edwards 01e7b8caee fix: show error message correctly (#2705) 2023-01-24 11:29:42 +00:00
Joe Tsang e883ef5c5e test(#2639): e2e view public key tests (#2688) 2023-01-24 10:52:01 +00:00
mattrussell36 2f391cde5b chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-24 06:07:12 +00:00
mattrussell36 3620314dca chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-24 00:11:13 +00:00
mattrussell36 9bdd1ed387 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-23 18:06:31 +00:00
mattrussell36 37dc8bf110 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-23 12:08:39 +00:00
Edd 3754043f8b fix(explorer): add correct tendermint websocket url
Fairground block explorer seems subtly broken (block count not increasing) because this value is incorrect
2023-01-23 11:44:44 +00:00
mattrussell36 1ee45b6135 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-23 06:08:42 +00:00
mattrussell36 a51410c722 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-23 00:10:33 +00:00
mattrussell36 91173d2434 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-22 18:08:06 +00:00
mattrussell36 8dd33c285e chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-22 12:07:38 +00:00
mattrussell36 6a7385823a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-22 06:06:12 +00:00
mattrussell36 9053b343d1 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-22 00:11:09 +00:00
mattrussell36 6562107578 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-21 18:08:23 +00:00
mattrussell36 8f5bedc12a chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-21 12:08:20 +00:00
mattrussell36 3044f3bdf7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-21 06:08:34 +00:00
mattrussell36 5eb8fb64c7 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-21 00:10:21 +00:00
botond ae84e16b9b chore: bump wallet client (#2662) 2023-01-20 20:05:18 +00:00
mattrussell36 5e5cc1c587 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-20 18:08:38 +00:00
mattrussell36 a3aa84b983 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-20 12:12:31 +00:00
Radosław Szpiech 49374c8e7c chore: simplify cypress workflows (#2677) 2023-01-20 10:21:22 +00:00
mattrussell36 8f4eeba092 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-20 06:07:47 +00:00
mattrussell36 66cceb0f34 chore: update tranches
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2023-01-20 00:12:41 +00:00
Matthew Russell b34fc98c9c chore: make headers consistent (#2683) 2023-01-19 22:15:55 +00:00
615 changed files with 15026 additions and 7228 deletions
@@ -1,6 +1,18 @@
outputs:
token:
description: 'api-token of wallet'
value: ${{ steps.generate-api-token.outputs.api-token }}
runs:
using: 'composite'
steps:
- name: Vegacapsule version
shell: bash
run: vegacapsule version
- name: Vega wallet version
shell: bash
run: vega wallet software version
- name: Start nomad
shell: bash
run: vegacapsule nomad &
@@ -8,3 +20,32 @@ runs:
- name: Bootstrap network
shell: bash
run: vegacapsule network bootstrap --config-path=./frontend-monorepo/vegacapsule/config.hcl --force
- name: Initialize wallet
shell: bash
run: vega wallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
shell: bash
run: vega wallet import -w capsule_wallet --recovery-phrase-file ./frontend-monorepo/vegacapsule/recovery -p ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet
- name: Generate second public key
shell: bash
run: vega wallet key generate -w capsule_wallet -p ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import network
shell: bash
run: vega wallet network import --force --from-file ./frontend-monorepo/vegacapsule/wallet-config.toml --home ~/.vegacapsule/testnet/wallet
- name: Init api-token
shell: bash
run: vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file ./frontend-monorepo/vegacapsule/passphrase
- name: Generate api-token
id: generate-api-token
shell: bash
run: echo api-token=$(vega wallet api-token generate --wallet-name capsule_wallet --tokens-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --wallet-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet | grep -Eo '[a-zA-Z0-9]{64}') >> $GITHUB_OUTPUT
- name: Start service using capsule network
shell: bash
run: vega wallet service run -n DV --load-tokens --tokens-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet &
@@ -1,35 +0,0 @@
outputs:
token:
description: 'api-token of wallet'
value: ${{ steps.generate-api-token.outputs.api-token }}
runs:
using: 'composite'
steps:
- name: Initialize wallet
shell: bash
run: vega wallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
shell: bash
run: vega wallet import -w capsule_wallet --recovery-phrase-file ./frontend-monorepo/vegacapsule/recovery -p ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet
- name: Generate second public key
shell: bash
run: vega wallet key generate -w capsule_wallet -p ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import network
shell: bash
run: vega wallet network import --force --from-file ./frontend-monorepo/vegacapsule/wallet-config.toml --home ~/.vegacapsule/testnet/wallet
- name: Init api-token
shell: bash
run: vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file ./frontend-monorepo/vegacapsule/passphrase
- name: Generate api-token
id: generate-api-token
shell: bash
run: echo api-token=$(vega wallet api-token generate --wallet-name capsule_wallet --tokens-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --wallet-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --home ~/.vegacapsule/testnet/wallet | grep -Eo '[a-zA-Z0-9]{64}') >> $GITHUB_OUTPUT
- name: Start service using capsule network
shell: bash
run: vega wallet service run -n DV --load-tokens --tokens-passphrase-file ./frontend-monorepo/vegacapsule/passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet &
@@ -1,48 +0,0 @@
inputs:
recovery:
description: 'Recovery phrase'
passphrase:
description: 'Wallet password'
outputs:
token:
description: 'api-token of wallet'
value: ${{ steps.generate-api-token.outputs.api-token }}
runs:
using: 'composite'
steps:
- name: Create passphrase
shell: bash
run: echo "${{ inputs.passphrase }}" > ./passphrase
- name: Create recovery
shell: bash
run: echo "${{ inputs.recovery }}" > ./recovery
- name: Initialize wallet
shell: bash
run: vega wallet init -f --home ~/.vegacapsule/testnet/wallet
- name: Import wallet
shell: bash
run: vega wallet import -w UI_Trading_Test --recovery-phrase-file ./recovery -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Create public key 2
shell: bash
run: vega wallet key generate -w UI_Trading_Test -p ./passphrase --home ~/.vegacapsule/testnet/wallet
- name: Import network
shell: bash
run: vega wallet network import --from-url="https://raw.githubusercontent.com/vegaprotocol/networks-internal/main/stagnet3/vegawallet-stagnet3.toml" --force --home ~/.vegacapsule/testnet/wallet
- name: Init api-token
shell: bash
run: vega wallet api-token init --home ~/.vegacapsule/testnet/wallet --passphrase-file passphrase
- name: Generate api-token
id: generate-api-token
shell: bash
run: echo api-token=$(vega wallet api-token generate --wallet-name UI_Trading_Test --tokens-passphrase-file passphrase --wallet-passphrase-file passphrase --home ~/.vegacapsule/testnet/wallet | grep -Eo '[a-zA-Z0-9]{64}') >> $GITHUB_OUTPUT
- name: Start service using stagnet3 network
shell: bash
run: vega wallet service run -n stagnet3 --load-tokens --tokens-passphrase-file passphrase --no-version-check --automatic-consent --home ~/.vegacapsule/testnet/wallet &
@@ -1,49 +0,0 @@
name: Cypress tests -- manual trigger
# This workflow runs the frontend tests against chosen branch
on:
workflow_dispatch:
inputs:
project:
description: 'Project'
required: true
type: choice
options:
- explorer-e2e
- token-e2e
- trading-e2e
tags:
description: 'Test tags to run'
required: true
type: string
default: '@smoke, @regression, @slow'
skip-nx-cache:
description: 'Add --skip-nx-cache to cypress test'
required: false
type: boolean
default: false
jobs:
manual:
name: Run Cypress tests -- manual trigger
runs-on: ubuntu-latest
steps:
- name: Set tags
run: echo TAGS="--env.grepTags '[ ${{ github.event.inputs.tags }} ]'" >> $GITHUB_ENV
- name: Set --skip-nx-cache flag
if: ${{ github.event.inputs.skip-nx-cache == 'true' }}
run: echo SKIP_NX_CACHE="--skip-nx-cache" >> $GITHUB_ENV
outputs:
skip-cache: ${{env.SKIP_NX_CACHE}}
tags: ${{env.TAGS}}
dispatch:
needs: manual
uses: ./.github/workflows/tests-dispatcher.yml
secrets: inherit
with:
project: ${{ inputs.project }}
skip-cache: ${{needs.manual.outputs.skip-cache}}
tags: ${{needs.manual.outputs.tags}}
@@ -1,17 +0,0 @@
name: Cypress tests -- night run
# This workflow runs the frontend tests against latest develop of the core to preempt breaking changes
on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:
jobs:
nightly:
uses: ./.github/workflows/tests-dispatcher.yml
secrets: inherit
with:
project: '[explorer-e2e, token-e2e, trading-e2e]'
tags: --env.grepTags '[ @smoke, @regression, @slow ]'
night-run: true
-61
View File
@@ -1,61 +0,0 @@
name: Cypress tests - PR
on:
push:
branches:
- develop
- main
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
jobs:
pr:
runs-on: ubuntu-latest
steps:
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v2
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
# Check SHAs
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
with:
working-directory: frontend-monorepo
main-branch-name: ${{ github.base_ref || github.ref_name }}
set-environment-variables-for-job: true
# See affected projects
- name: See affected apps
run: echo AFFECTED=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects) >> $GITHUB_ENV
working-directory: frontend-monorepo
outputs:
projects: ${{ env.AFFECTED }}
dispatch:
needs: pr
uses: ./.github/workflows/tests-dispatcher.yml
secrets: inherit
with:
project: ${{ needs.pr.outputs.projects }}
tags: "--env.grepTags='[ @smoke, @regression ]'"
@@ -1,99 +0,0 @@
name: Cypress - explorer
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
skip-cache:
required: false
type: string
tags:
required: false
type: string
night-run:
required: false
type: boolean
default: false
jobs:
explorer-e2e:
if: ${{ inputs.trigger == 'true' }}
runs-on: self-hosted
timeout-minutes: 30
steps:
- name: Vega version
run: vega version
- name: Vegacapsule version
run: vegacapsule version
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
######
## Setup a Vega wallet for our user
######
- name: Run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
- name: Set up Vegawallet for capsule
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
######
## Run some tests
######
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run explorer-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_NIGHTLY_RUN: ${{ inputs.night-run }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
######
## Upload logs
######
# Artifact path is not valid: /ganache-1/capsule-logscolletor.stderr-2022-12-22T10:20:30Z.log. Contains the following character: Colon :
- name: Rename files to allow archive
if: ${{ always() }}
run: |
while read -r file; do
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: logs
path: /home/runner/.vegacapsule/testnet/logs
@@ -0,0 +1,33 @@
name: Cypress tests -- manual trigger
# This workflow runs the frontend tests against chosen branch
on:
workflow_dispatch:
inputs:
project:
description: 'Project'
required: true
type: choice
options:
- explorer-e2e
- token-e2e
- trading-e2e
tags:
description: 'Test tags to run'
required: true
type: string
default: '@smoke @regression @slow'
skip-nx-cache:
description: 'Skip NX cache'
required: false
type: boolean
default: false
jobs:
manual:
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: '["${{inputs.project}}"]'
skip-cache: ${{inputs.skip-nx-cache}}
tags: ${{inputs.tags}}
+14
View File
@@ -0,0 +1,14 @@
name: Cypress tests -- night run
on:
schedule:
- cron: '0 4 * * *'
workflow_dispatch:
jobs:
nightly:
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: '["explorer-e2e","token-e2e","trading-e2e"]'
tags: '@smoke @regression @slow'
+92
View File
@@ -0,0 +1,92 @@
name: Cypress tests - PR
on:
push:
branches:
- develop
- main
pull_request:
types:
- opened
- reopened
- synchronize
- ready_for_review
jobs:
pr:
runs-on: ubuntu-latest
steps:
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup node
uses: actions/setup-node@v3
- name: Remove package.json & yarn.lock to avoid installing everything
run: rm package.json yarn.lock
- name: Install nx
run: yarn add nx
# Check SHAs
- name: Derive appropriate SHAs for base and head for `nx affected` commands
uses: nrwl/nx-set-shas@v2
with:
main-branch-name: ${{ github.base_ref || github.ref_name }}
set-environment-variables-for-job: true
# See affected apps
- name: See affected apps
run: |
affected=$(yarn nx print-affected --base=${{ env.NX_BASE }} --head=${{ env.NX_HEAD }} --select=projects)
echo -n "Affected projects: $affected"
projects=""
if [[ $affected == *"token"* ]]; then projects+='"token-e2e" '; fi
if [[ $affected == *"trading"* ]]; then projects+='"trading-e2e" '; fi
if [[ $affected == *"explorer"* ]]; then projects+='"explorer-e2e" '; fi
if [[ -z "$projects" ]]; then projects+='"token-e2e" "trading-e2e" "explorer-e2e" '; fi
projects=${projects%?}
projects=[${projects// /,}]
echo PROJECTS=$projects >> $GITHUB_ENV
# Rename required because some of the files contains the colon character (in the dates)
- name: Rename files to allow archive
if: ${{ always() }}
run: |
while read -r file; do
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: logs-${{ matrix.project }}
path: /home/runner/.vegacapsule/testnet/logs
outputs:
projects: ${{ env.PROJECTS }}
run:
needs: pr
if: ${{ needs.pr.outputs.projects != '[]' }}
uses: ./.github/workflows/cypress-run.yml
secrets: inherit
with:
projects: ${{ needs.pr.outputs.projects }}
tags: '@smoke @regression'
# Report single result at the end, to avoid mess with required checks in PR
result:
if: ${{ always() }}
needs: run
runs-on: ubuntu-latest
name: Cypress result
steps:
- run: |
result="${{ needs.run.result }}"
if [[ $result == "success" || $result == "skipped" ]]; then
exit 0
else
exit 1
fi
@@ -1,30 +1,29 @@
name: Cypress - token
on:
workflow_call:
inputs:
trigger:
projects:
required: true
type: string
default: 'false'
skip-cache:
required: false
type: string
type: boolean
tags:
required: false
type: string
jobs:
token-e2e:
if: ${{ inputs.trigger == 'true' }}
runs-on: self-hosted
timeout-minutes: 60
e2e:
strategy:
fail-fast: false
matrix:
project: ${{ fromJSON(inputs.projects) }}
runs-on: self-hosted-runner
timeout-minutes: 30
steps:
- name: Vega version
run: vega version
- name: Vegacapsule version
run: vegacapsule version
# Checks if skip cache was requested
- name: Set skip-nx-cache flag
if: ${{ inputs.skip-cache == true }}
run: echo "SKIP_CACHE=--skip-nx-cache" >> $GITHUB_ENV
# Checkout front ends
- name: Checkout frontend mono repo
@@ -47,32 +46,28 @@ jobs:
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
# Make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
######
## Setup a Vega wallet for our user
## Setup Vegacapsule and Vega wallet
######
- name: Run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
- name: Set up Vegawallet for capsule
- name: Run Vegacapsule network and Vega wallet
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
######
## Run some tests
######
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run token-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
run: yarn nx run ${{ matrix.project }}:e2e ${{ env.SKIP_CACHE }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome --env.grepTags="${{ inputs.tags }}"
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
######
@@ -87,8 +82,8 @@ jobs:
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- uses: actions/upload-artifact@v2
- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: logs-token
name: logs-${{ matrix.project }}
path: /home/runner/.vegacapsule/testnet/logs
-86
View File
@@ -1,86 +0,0 @@
name: Cypress - trading
on:
workflow_call:
inputs:
trigger:
required: true
type: string
default: 'false'
skip-cache:
required: false
type: string
tags:
required: false
type: string
jobs:
trading-e2e:
if: ${{ inputs.trigger == 'true' }}
timeout-minutes: 30
runs-on: self-hosted
steps:
- name: Vega version
run: vega version
# Checkout front ends
- name: Checkout frontend mono repo
uses: actions/checkout@v3
with:
fetch-depth: 0
path: './frontend-monorepo'
# Restore node_modules from cache if possible
- name: Restore node_modules from cache
uses: actions/cache@v3
with:
path: |
frontend-monorepo/node_modules
/home/runner/.cache/Cypress
key: node_modules_cypress-${{ hashFiles('frontend-monorepo/yarn.lock', 'frontend-monorepo/package.json') }}
# Install frontend dependencies
- name: Install root dependencies
run: yarn install --frozen-lockfile
working-directory: frontend-monorepo
######
## Setup a Vega wallet for our user
######
- name: Run Vegacapsule network
uses: ./frontend-monorepo/.github/actions/run-vegacapsule
- name: Set up Vegawallet for capsule
id: setup-vega
uses: ./frontend-monorepo/.github/actions/setup-vegawallet-docker
# To make sure that all Cypress binaries are installed properly
- name: Install cypress bins
run: yarn cypress install
working-directory: frontend-monorepo
- name: Run Cypress tests
run: npx nx run trading-e2e:e2e ${{ inputs.skip-cache }} --record --key ${{ secrets.CYPRESS_RECORD_KEY }} --browser chrome ${{ inputs.tags }}
working-directory: frontend-monorepo
env:
CYPRESS_SLACK_WEBHOOK: ${{ secrets.CYPRESS_SLACK_WEBHOOK }}
CYPRESS_ETH_WALLET_MNEMONIC: ${{ secrets.CYPRESS_ETH_WALLET_MNEMONIC }}
CYPRESS_VEGA_WALLET_API_TOKEN: ${{ steps.setup-vega.outputs.token }}
######
## Upload logs
######
- name: Rename files to allow archive
if: ${{ always() }}
run: |
while read -r file; do
mv "${file}" "$(echo ${file} | sed 's|:|-|g')"
done< <(find /home/runner/.vegacapsule/testnet/logs -type f)
- uses: actions/upload-artifact@v2
if: ${{ always() }}
with:
name: logs-trading
path: /home/runner/.vegacapsule/testnet/logs
+7 -5
View File
@@ -8,14 +8,16 @@ jobs:
lint_pr:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Use Node.js 16
id: Node
uses: actions/setup-node@v2
with:
node-version: 16.14.0
- name: Install commitlint cli and config
run: npm install @commitlint/cli @commitlint/config-conventional
- name: Create config
run: echo "module.exports = {extends:['@commitlint/config-conventional']};" > commitlint.config.js
- name: Install root dependencies
run: yarn install
- name: Check PR title
run: echo "${{ github.event.pull_request.title }}" | npx commitlint
run: echo "${{ github.event.pull_request.title }}" | npx commitlint --config ./commitlint.config-ci.js
-41
View File
@@ -1,41 +0,0 @@
on:
workflow_call:
inputs:
project:
required: true
type: string
skip-cache:
required: false
type: string
tags:
required: false
type: string
night-run:
required: false
type: boolean
jobs:
run-explorer-e2e:
uses: ./.github/workflows/cypress-explorer-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'explorer-e2e') || contains(inputs.project, 'explorer') }}
skip-cache: ${{ inputs.skip-cache }}
tags: ${{ inputs.tags }}
night-run: ${{ inputs.night-run }}
run-token-e2e:
uses: ./.github/workflows/cypress-token-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'token-e2e') || contains(inputs.project, 'token') }}
skip-cache: ${{ inputs.skip-cache }}
tags: ${{ inputs.tags }}
run-trading-e2e:
uses: ./.github/workflows/cypress-trading-e2e.yml
secrets: inherit
with:
trigger: ${{ contains(inputs.project, 'trading-e2e') || contains(inputs.project, 'trading') }}
skip-cache: ${{ inputs.skip-cache }}
tags: ${{ inputs.tags }}
+45 -138
View File
@@ -1,150 +1,57 @@
context('Asset page', { tags: '@regression' }, function () {
before('gather system asset information', function () {
cy.get_asset_information().as('assetsInfo');
});
describe('Verify elements on page', function () {
const assetsNavigation = 'a[href="/assets"]';
const assetHeader = '[data-testid="asset-header"]';
const jsonSection = '.language-json';
before('Navigate to assets page', function () {
cy.visit('/');
cy.get(assetsNavigation).click();
context('Asset page', { tags: '@regression' }, () => {
const columns = ['symbol', 'name', 'id', 'type', 'status', 'actions'];
const hiddenOnMobile = ['id', 'type', 'status'];
describe('Verify elements on page', () => {
before('Navigate to assets page', () => {
cy.visit('/assets');
// Check we have enough enough assets
const assetNames = Object.keys(this.assetsInfo);
assert.isAtLeast(
assetNames.length,
5,
'Ensuring we have at least 5 assets to test'
);
});
it('should be able to see assets page sections', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
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 all asset details displayed in JSON', function () {
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.next()
.within(() => {
cy.get(jsonSection)
.invoke('text')
.convert_string_json_to_js_object()
.then((assetsListedInJson) => {
const assetInfo = this.assetsInfo[assetName];
assert.equal(assetsListedInJson.name, assetInfo.node.name);
assert.equal(assetsListedInJson.id, assetInfo.node.id);
assert.equal(
assetsListedInJson.decimals,
assetInfo.node.decimals
);
assert.equal(assetsListedInJson.symbol, assetInfo.node.symbol);
assert.equal(
assetsListedInJson.source.__typename,
assetInfo.node.source.__typename
);
if (assetInfo.node.source.__typename == 'ERC20') {
assert.equal(
assetsListedInJson.source.contractAddress,
assetInfo.node.source.contractAddress
);
}
if (assetInfo.node.source.__typename == 'BuiltinAsset') {
assert.equal(
assetsListedInJson.source.maxFaucetAmountMint,
assetInfo.node.source.maxFaucetAmountMint
);
}
let knownAssetTypes = ['BuiltinAsset', 'ERC20'];
assert.include(
knownAssetTypes,
assetInfo.node.source.__typename,
`Checking that current asset type of ${assetInfo.node.source.__typename} /
is one of: ${knownAssetTypes}: /
If fail then we need to add extra tests for un-encountered asset types`
);
});
});
});
});
it('should be able to switch assets between light and dark mode', function () {
const whiteThemeSelectedMenuOptionColor = 'rgb(255, 7, 127)';
const whiteThemeJsonFieldBackColor = 'rgb(255, 255, 255)';
const whiteThemeSideMenuBackgroundColor = 'rgb(255, 255, 255)';
const darkThemeSelectedMenuOptionColor = 'rgb(215, 251, 80)';
const darkThemeJsonFieldBackColor = 'rgb(38, 38, 38)';
const darkThemeSideMenuBackgroundColor = 'rgb(0, 0, 0)';
const themeSwitcher = '[data-testid="theme-switcher"]';
const jsonFields = '.hljs';
const sideMenuBackground = '.absolute';
// Engage dark mode if not allready set
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.then((background_color) => {
if (background_color.includes(whiteThemeSideMenuBackgroundColor))
cy.get(themeSwitcher).click();
it('should be able to see full assets list', () => {
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
});
// Engage white mode
cy.get(themeSwitcher).click();
// White Mode
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', whiteThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', whiteThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', whiteThemeSideMenuBackgroundColor);
// Dark Mode
cy.get(themeSwitcher).click();
cy.get(assetsNavigation)
.should('have.css', 'background-color')
.and('include', darkThemeSelectedMenuOptionColor);
cy.get(jsonFields)
.should('have.css', 'background-color')
.and('include', darkThemeJsonFieldBackColor);
cy.get(sideMenuBackground)
.should('have.css', 'background-color')
.and('include', darkThemeSideMenuBackgroundColor);
});
columns.forEach((col) => {
cy.get(`[col-id="${col}"]`).should('be.visible');
});
});
it('should be able to see assets page displayed in mobile', function () {
cy.common_switch_to_mobile_and_click_toggle();
cy.get(assetsNavigation).click();
it('should be able to see assets page displayed in mobile', () => {
cy.switchToMobile();
const assetNames = Object.keys(this.assetsInfo);
assetNames.forEach((assetName) => {
cy.get(assetHeader)
.contains(assetName)
.should('be.visible')
.next()
.within(() => {
cy.get(jsonSection).should('not.be.empty');
});
hiddenOnMobile.forEach((col) => {
cy.get(`[col-id="${col}"]`).should('have.length', 0);
});
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"]`).should('be.visible');
});
});
});
it('should open details dialog when clicked on "View details"', () => {
cy.getAssets().then((assets) => {
Object.values(assets).forEach((asset) => {
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
.eq(0)
.should('contain.text', 'View details');
cy.get(`[row-id="${asset.id}"] [col-id="actions"] button`)
.eq(0)
.click();
cy.getByTestId('dialog-content').should('be.visible');
cy.getByTestId('dialog-close').click();
});
});
});
});
+4 -6
View File
@@ -60,12 +60,10 @@ context('Home Page', function () {
cy.get(statsValue).eq(9).should('not.be.empty');
cy.get(statsValue).eq(10).should('not.be.empty');
cy.get(statsValue).eq(11).should('not.be.empty');
if (Cypress.env('NIGHTLY_RUN') != true) {
cy.get(statsValue)
.eq(12)
.invoke('text')
.should('match', /v\d+\.\d+\.\d+/i);
}
cy.get(statsValue)
.eq(12)
.invoke('text')
.should('match', /v\d+\.\d+\.\d+/i);
cy.get(statsValue)
.eq(13)
.invoke('text')
@@ -123,7 +123,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
.convert_string_json_to_js_object()
.get_party_accounts_data_from_js_object()
.then((accountsListedInJson) => {
cy.get_asset_information().then((assetsInfo) => {
cy.getAssets().then((assetsInfo) => {
const assetInfo =
assetsInfo[accountsListedInJson[assetInTest].asset.name];
@@ -205,7 +205,7 @@ context.skip('Parties page', { tags: '@regression' }, function () {
});
Cypress.Commands.add('get_asset_decimals', (assetID) => {
cy.get_asset_information().then((assetsInfo) => {
cy.getAssets().then((assetsInfo) => {
const assetDecimals = assetsInfo[assetData[assetID].name].decimals;
let decimals = '';
for (let i = 0; i < assetDecimals; i++) decimals += '0';
@@ -21,6 +21,10 @@ Cypress.Commands.add(
}
);
Cypress.Commands.add('switchToMobile', () => {
cy.viewport('iphone-x');
});
Cypress.Commands.add('common_switch_to_mobile_and_click_toggle', function () {
cy.viewport('iphone-x');
cy.visit('/');
+6 -3
View File
@@ -1,9 +1,12 @@
# App configuration variables
NX_TENDERMINT_URL=https://n04.d.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://n04.d.vega.xyz/tm/websocket
NX_TENDERMINT_URL=https://tm.be.devnet1.vega.xyz/tm
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.devnet1.vega.xyz/websocket
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=DEVNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.devnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://dev.token.vega.xyz
NX_VEGA_URL=https://api.devnet1.vega.xyz/graphql
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/devnet1-network.json
+1
View File
@@ -7,3 +7,4 @@ NX_VEGA_ENV=MAINNET
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.explorer.vega.xyz/rest/
NX_ETHERSCAN_URL=https://etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.vega.xyz
+1
View File
@@ -7,6 +7,7 @@ NX_VEGA_ENV=MIRROR
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.mainnet-mirror.vega.xyz/rest/
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://mainnet-mirror.token.vega.xyz
# App flags
NX_EXPLORER_ASSETS=1
+3 -2
View File
@@ -6,6 +6,7 @@ NX_VEGA_DOCS_URL=https://docs.vega.xyz/testnet
NX_ETHEREUM_PROVIDER_URL=https://sepolia.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_TENDERMINT_URL=https://tm.n01.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.sandbox.vega.xyz/websocket
NX_TENDERMINT_URL=https://tm.sandbox.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.sandbox.vega.xyz/websocket
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://sandbox.token.vega.xyz
+1
View File
@@ -11,3 +11,4 @@ NX_TENDERMINT_URL=https://tm.n01.stagnet1.vega.xyz
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.n01.stagnet1.vega.xyz/websocket
NX_BLOCK_EXPLORER=https://be.stagnet1.vega.xyz/rest
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://stagnet1.token.vega.xyz
+3 -1
View File
@@ -4,4 +4,6 @@ NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/stagnet3-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=STAGNET3
NX_GITHUB_FEEDBACK_URL=https://github.com/vegaprotocol/feedback/discussions
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_BLOCK_EXPLORER=https://be.stagnet3.vega.xyz/rest
NX_VEGA_GOVERNANCE_URL=https://stagnet3.token.vega.xyz
NX_VEGA_REPO_URL=https://github.com/vegaprotocol/vega-dev-releases/releases/
+2 -1
View File
@@ -1,7 +1,7 @@
# App configuration variables
NX_TENDERMINT_URL=https://tm.be.testnet.vega.xyz
NX_BLOCK_EXPLORER=https://be.testnet.vega.xyz/rest
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.testnet.vega.xyz/
NX_TENDERMINT_WEBSOCKET_URL=wss://tm.be.testnet.vega.xyz/websocket/
NX_VEGA_CONFIG_URL=https://static.vega.xyz/assets/testnet-network.json
NX_VEGA_NETWORKS={\"MAINNET"\:\"https://explorer.vega.xyz"\,\"TESTNET\":\"https://explorer.fairground.wtf\"}
NX_VEGA_ENV=TESTNET
@@ -10,3 +10,4 @@ NX_VEGA_URL=https://api.n07.testnet.vega.xyz/graphql
NX_HOSTED_WALLET_URL=https://wallet.testnet.vega.xyz
NX_VEGA_NETWORKS={}
NX_ETHERSCAN_URL=https://sepolia.etherscan.io
NX_VEGA_GOVERNANCE_URL=https://token.fairground.wtf
+1 -1
View File
@@ -70,7 +70,7 @@
"executor": "@nrwl/workspace:run-commands",
"options": {
"commands": [
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.66.1/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
"npx openapi-typescript https://raw.githubusercontent.com/vegaprotocol/documentation/main/specs/v0.67.3/blockexplorer.openapi.json --output apps/explorer/src/types/explorer.d.ts --immutable-types"
]
}
},
+29
View File
@@ -8,6 +8,24 @@ import { Main } from './components/main';
import { TendermintWebsocketProvider } from './contexts/websocket/tendermint-websocket-provider';
import type { InMemoryCacheConfig } from '@apollo/client';
import { Footer } from './components/footer/footer';
import { AnnouncementBanner, ExternalLink } from '@vegaprotocol/ui-toolkit';
import {
AssetDetailsDialog,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
const DialogsContainer = () => {
const { isOpen, id, trigger, asJson, setOpen } = useAssetDetailsDialogStore();
return (
<AssetDetailsDialog
assetId={id}
trigger={trigger || null}
asJson={asJson}
open={isOpen}
onChange={setOpen}
/>
);
};
function App() {
const [menuOpen, setMenuOpen] = useState(false);
@@ -40,12 +58,23 @@ function App() {
return (
<TendermintWebsocketProvider>
<NetworkLoader cache={cacheConfig}>
<AnnouncementBanner>
<div className="font-alpha calt uppercase text-center text-lg text-white">
<span className="pr-4">The Mainnet sims are live!</span>
<ExternalLink href="https://fairground.wtf/">
Come help stress test the network
</ExternalLink>
</div>
</AnnouncementBanner>
<div className={layoutClasses}>
<Header menuOpen={menuOpen} setMenuOpen={setMenuOpen} />
<Nav menuOpen={menuOpen} />
<Main />
<Footer />
</div>
<DialogsContainer />
</NetworkLoader>
</TendermintWebsocketProvider>
);
@@ -1,6 +1,6 @@
import { useAssetDataProvider } from '@vegaprotocol/assets';
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
import { AssetLink } from '../links';
import { useExplorerAssetQuery } from '../links/asset-link/__generated__/Asset';
export type AssetBalanceProps = {
assetId: string;
@@ -17,21 +17,17 @@ const AssetBalance = ({
price,
showAssetLink = true,
}: AssetBalanceProps) => {
const { data } = useExplorerAssetQuery({
variables: { id: assetId },
});
const { data: asset } = useAssetDataProvider(assetId);
const label =
data && data.asset?.decimals
? addDecimalsFormatNumber(price, data.asset.decimals)
asset && asset.decimals
? addDecimalsFormatNumber(price, asset.decimals)
: price;
return (
<div className="inline-block">
<span>{label}</span>{' '}
{showAssetLink && data?.asset?.id ? (
<AssetLink id={data.asset.id} />
) : null}
{showAssetLink && asset?.id ? <AssetLink assetId={assetId} /> : null}
</div>
);
};
@@ -0,0 +1,28 @@
import { render, waitFor } from '@testing-library/react';
import { assetsList } from '../../mocks/assets';
import { AssetsTable } from './assets-table';
describe('AssetsTable', () => {
it('shows loading message on first render', async () => {
const res = render(<AssetsTable data={null} />);
expect(await res.findByText('Loading...')).toBeInTheDocument();
});
it('shows no data message if no assets found', async () => {
const res = render(<AssetsTable data={[]} />);
expect(
await res.findByText('This chain has no assets')
).toBeInTheDocument();
});
it('shows a table/list with all the assets', async () => {
const res = render(<AssetsTable data={assetsList} />);
await waitFor(() => {
const rowA1 = res.container.querySelector('[row-id="123"]');
expect(rowA1).toBeInTheDocument();
const rowA2 = res.container.querySelector('[row-id="456"]');
expect(rowA2).toBeInTheDocument();
});
});
});
@@ -0,0 +1,115 @@
import type { AssetFieldsFragment } from '@vegaprotocol/assets';
import { useAssetDetailsDialogStore } from '@vegaprotocol/assets';
import { AssetTypeMapping, AssetStatusMapping } from '@vegaprotocol/assets';
import { t } from '@vegaprotocol/react-helpers';
import type { VegaICellRendererParams } from '@vegaprotocol/ui-toolkit';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import type { AgGridReact } from 'ag-grid-react';
import { AgGridColumn } from 'ag-grid-react';
import { AgGridDynamic as AgGrid } from '@vegaprotocol/ui-toolkit';
import { useRef, useLayoutEffect } from 'react';
import { BREAKPOINT_MD } from '../../config/breakpoints';
type AssetsTableProps = {
data: AssetFieldsFragment[] | null;
};
export const AssetsTable = ({ data }: AssetsTableProps) => {
const openAssetDetailsDialog = useAssetDetailsDialogStore(
(state) => state.open
);
const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => {
ref.current?.columnApi.setColumnsVisible(
['id', 'type', 'status'],
window.innerWidth > BREAKPOINT_MD
);
};
useLayoutEffect(() => {
window.addEventListener('resize', showColumnsOnDesktop);
return () => {
window.removeEventListener('resize', showColumnsOnDesktop);
};
}, []);
return (
<AgGrid
ref={ref}
rowData={data}
getRowId={({ data }: { data: AssetFieldsFragment }) => data.id}
overlayNoRowsTemplate={t('This chain has no assets')}
domLayout="autoHeight"
defaultColDef={{
flex: 1,
resizable: true,
sortable: true,
filter: true,
filterParams: { buttons: ['reset'] },
autoHeight: true,
}}
suppressCellFocus={true}
onGridReady={() => {
showColumnsOnDesktop();
}}
>
<AgGridColumn headerName={t('Symbol')} field="symbol" />
<AgGridColumn headerName={t('Name')} field="name" />
<AgGridColumn flex="2" headerName={t('ID')} field="id" />
<AgGridColumn
colId="type"
headerName={t('Type')}
field="source.__typename"
valueFormatter={({ value }: { value?: string }) =>
value && AssetTypeMapping[value].value
}
/>
<AgGridColumn
headerName={t('Status')}
field="status"
valueFormatter={({ value }: { value?: string }) =>
value && AssetStatusMapping[value].value
}
/>
<AgGridColumn
colId="actions"
headerName=""
sortable={false}
filter={false}
resizable={false}
wrapText={true}
field="id"
cellRenderer={({
value,
}: VegaICellRendererParams<AssetFieldsFragment, 'id'>) =>
value ? (
<div className="pb-1">
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(value, e.target as HTMLElement);
}}
>
{t('View details')}
</ButtonLink>{' '}
<span className="max-md:hidden">
<ButtonLink
onClick={(e) => {
openAssetDetailsDialog(
value,
e.target as HTMLElement,
true
);
}}
>
{t('View JSON')}
</ButtonLink>
</span>
</div>
) : (
''
)
}
/>
</AgGrid>
);
};
@@ -1,8 +0,0 @@
query ExplorerAsset($id: ID!) {
asset(id: $id) {
id
name
status
decimals
}
}
@@ -1,51 +0,0 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerAssetQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerAssetQuery = { __typename?: 'Query', asset?: { __typename?: 'Asset', id: string, name: string, status: Types.AssetStatus, decimals: number } | null };
export const ExplorerAssetDocument = gql`
query ExplorerAsset($id: ID!) {
asset(id: $id) {
id
name
status
decimals
}
}
`;
/**
* __useExplorerAssetQuery__
*
* To run a query within a React component, call `useExplorerAssetQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerAssetQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerAssetQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerAssetQuery(baseOptions: Apollo.QueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
}
export function useExplorerAssetLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerAssetQuery, ExplorerAssetQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerAssetQuery, ExplorerAssetQueryVariables>(ExplorerAssetDocument, options);
}
export type ExplorerAssetQueryHookResult = ReturnType<typeof useExplorerAssetQuery>;
export type ExplorerAssetLazyQueryHookResult = ReturnType<typeof useExplorerAssetLazyQuery>;
export type ExplorerAssetQueryResult = Apollo.QueryResult<ExplorerAssetQuery, ExplorerAssetQueryVariables>;
@@ -1,63 +1,37 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import AssetLink from './asset-link';
import { ExplorerAssetDocument } from './__generated__/Asset';
import { render, waitFor } from '@testing-library/react';
import { AssetLink } from './asset-link';
import { mockAssetA1 } from '../../../mocks/assets';
function renderComponent(id: string, mock: MockedResponse[]) {
return (
<MockedProvider mocks={mock}>
<MockedProvider mocks={mock} addTypename={false}>
<MemoryRouter>
<AssetLink id={id} />
<AssetLink assetId={id} />
</MemoryRouter>
</MockedProvider>
);
}
describe('Asset link component', () => {
it('Renders the ID at first', () => {
describe('AssetLink', () => {
it('renders the asset id when not found and makes the button disabled', async () => {
const res = render(renderComponent('123', []));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByTestId('asset-link')).toBeDisabled();
await waitFor(async () => {
expect(await res.queryByText('A ONE')).toBeFalsy();
});
});
it('Renders the asset name when the query returns a result', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
variables: {
id: '123',
},
},
result: {
data: {
asset: {
id: '123',
name: 'test-label',
status: 'irrelevant-test-data',
decimals: 18,
},
},
},
};
const res = render(renderComponent('123', [mock]));
it('renders the asset name when found and make the button enabled', async () => {
const res = render(renderComponent('123', [mockAssetA1]));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByText('test-label')).toBeInTheDocument();
});
it('Leaves the asset id when the asset is not found', async () => {
const mock = {
request: {
query: ExplorerAssetDocument,
variables: {
id: '123',
},
},
error: new Error('No such asset'),
};
const res = render(renderComponent('123', [mock]));
expect(await res.findByText('123')).toBeInTheDocument();
await waitFor(async () => {
expect(await res.findByText('A ONE')).toBeInTheDocument();
expect(await res.findByTestId('asset-link')).not.toBeDisabled();
});
});
});
@@ -1,35 +1,35 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { useExplorerAssetQuery } from './__generated__/Asset';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
import {
useAssetDataProvider,
useAssetDetailsDialogStore,
} from '@vegaprotocol/assets';
export type AssetLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
export type AssetLinkProps = Partial<ComponentProps<typeof ButtonLink>> & {
assetId: string;
};
/**
* Given an asset ID, it will fetch the asset name and show that,
* with a link to the assets list. 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.
*/
const AssetLink = ({ id, ...props }: AssetLinkProps) => {
const { data } = useExplorerAssetQuery({
variables: { id },
});
let label: string = id;
if (data?.asset?.name) {
label = data.asset.name;
}
export const AssetLink = ({ assetId, ...props }: AssetLinkProps) => {
const { data: asset } = useAssetDataProvider(assetId);
const open = useAssetDetailsDialogStore((state) => state.open);
const label = asset?.name ? asset.name : assetId;
return (
<Link className="underline" {...props} to={`/${Routes.ASSETS}#${id}`}>
{label}
</Link>
<ButtonLink
data-testid="asset-link"
disabled={!asset}
onClick={(e) => {
open(assetId, e.target as HTMLElement);
}}
{...props}
>
<Hash text={label} />
</ButtonLink>
);
};
export default AssetLink;
@@ -3,6 +3,7 @@ import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
height: string;
@@ -11,7 +12,7 @@ export type BlockLinkProps = Partial<ComponentProps<typeof Link>> & {
const BlockLink = ({ height, ...props }: BlockLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.BLOCKS}/${height}`}>
{height}
<Hash text={height} />
</Link>
);
};
@@ -1,6 +1,7 @@
import React from 'react';
import { DATA_SOURCES } from '../../../config';
import Hash from '../hash';
export enum EthExplorerLinkTypes {
block = 'block',
@@ -27,7 +28,7 @@ export const EthExplorerLink = ({
{...props}
href={link}
>
{id}
<Hash text={id} />
</a>
);
};
@@ -0,0 +1,18 @@
export type HashProps = {
text: string;
};
/**
* A simple component that ensures long text things like hashes
* are broken when they need to wrap. This will remove the need
* for a lot of the overflow scrolling that currently exists.
*/
const Hash = ({ text }: HashProps) => {
return (
<code className="break-all font-mono" style={{ wordWrap: 'break-word' }}>
{text}
</code>
);
};
export default Hash;
@@ -2,4 +2,4 @@ export { default as BlockLink } from './block-link/block-link';
export { default as PartyLink } from './party-link/party-link';
export { default as NodeLink } from './node-link/node-link';
export { default as MarketLink } from './market-link/market-link';
export { default as AssetLink } from './asset-link/asset-link';
export * from './asset-link/asset-link';
@@ -2,12 +2,16 @@ query ExplorerMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
settlementAsset {
decimals
}
}
}
}
@@ -8,7 +8,7 @@ export type ExplorerMarketQueryVariables = Types.Exact<{
}>;
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } | null };
export type ExplorerMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string, settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
export const ExplorerMarketDocument = gql`
@@ -16,12 +16,16 @@ export const ExplorerMarketDocument = gql`
market(id: $id) {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
product {
... on Future {
quoteName
settlementAsset {
decimals
}
}
}
}
@@ -55,6 +55,7 @@ describe('Market link component', () => {
market: {
id: '123',
decimalPlaces: 5,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -1,13 +1,14 @@
import React from 'react';
import { Routes } from '../../../routes/route-names';
import { useExplorerMarketQuery } from './__generated__/Market';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import { t } from '@vegaprotocol/react-helpers';
import Hash from '../hash';
export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
showMarketName?: boolean;
};
/**
@@ -15,7 +16,11 @@ export type MarketLinkProps = Partial<ComponentProps<typeof Link>> & {
* with a link to the markets list. If the name does not come back
* it will use the ID instead
*/
const MarketLink = ({ id, ...props }: MarketLinkProps) => {
const MarketLink = ({
id,
showMarketName = true,
...props
}: MarketLinkProps) => {
const { data, error, loading } = useExplorerMarketQuery({
variables: { id },
});
@@ -31,17 +36,31 @@ const MarketLink = ({ id, ...props }: MarketLinkProps) => {
<span role="img" aria-label="Unknown market" className="img">
&nbsp;{t('Invalid market')}
</span>
&nbsp;{id}
&nbsp;
<Hash text={id} />
</div>
);
}
}
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
{label}
</Link>
);
if (showMarketName) {
return (
<Link
className="underline"
{...props}
to={`/${Routes.MARKETS}#${id}`}
title={id}
>
{label}
</Link>
);
} else {
return (
<Link className="underline" {...props} to={`/${Routes.MARKETS}#${id}`}>
<Hash text={id} />
</Link>
);
}
};
export default MarketLink;
@@ -4,6 +4,7 @@ import { useExplorerNodeQuery } from './__generated__/Node';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type NodeLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -22,7 +23,7 @@ const NodeLink = ({ id, ...props }: NodeLinkProps) => {
return (
<Link className="underline" {...props} to={`/${Routes.VALIDATORS}#${id}`}>
<code>{label}</code>
<Hash text={label} />
</Link>
);
};
@@ -0,0 +1,23 @@
import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type OracleLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
};
const OracleLink = ({ id, ...props }: OracleLinkProps) => {
return (
<Link
className="underline font-mono"
{...props}
to={`/${Routes.ORACLES}/${id}`}
>
<Hash text={id} />
</Link>
);
};
export default OracleLink;
@@ -2,6 +2,7 @@ import { Routes } from '../../../routes/route-names';
import { Link } from 'react-router-dom';
import type { ComponentProps } from 'react';
import Hash from '../hash';
export type PartyLinkProps = Partial<ComponentProps<typeof Link>> & {
id: string;
@@ -14,7 +15,7 @@ const PartyLink = ({ id, ...props }: PartyLinkProps) => {
{...props}
to={`/${Routes.PARTIES}/${id}`}
>
{id}
<Hash text={id} />
</Link>
);
};
@@ -0,0 +1,9 @@
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
id
rationale {
title
description
}
}
}
@@ -0,0 +1,52 @@
import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerProposalQueryVariables = Types.Exact<{
id: Types.Scalars['ID'];
}>;
export type ExplorerProposalQuery = { __typename?: 'Query', proposal?: { __typename?: 'Proposal', id?: string | null, rationale: { __typename?: 'ProposalRationale', title: string, description: string } } | null };
export const ExplorerProposalDocument = gql`
query ExplorerProposal($id: ID!) {
proposal(id: $id) {
id
rationale {
title
description
}
}
}
`;
/**
* __useExplorerProposalQuery__
*
* To run a query within a React component, call `useExplorerProposalQuery` and pass it any options that fit your needs.
* When your component renders, `useExplorerProposalQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI.
*
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
*
* @example
* const { data, loading, error } = useExplorerProposalQuery({
* variables: {
* id: // value for 'id'
* },
* });
*/
export function useExplorerProposalQuery(baseOptions: Apollo.QueryHookOptions<ExplorerProposalQuery, ExplorerProposalQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<ExplorerProposalQuery, ExplorerProposalQueryVariables>(ExplorerProposalDocument, options);
}
export function useExplorerProposalLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<ExplorerProposalQuery, ExplorerProposalQueryVariables>) {
const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<ExplorerProposalQuery, ExplorerProposalQueryVariables>(ExplorerProposalDocument, options);
}
export type ExplorerProposalQueryHookResult = ReturnType<typeof useExplorerProposalQuery>;
export type ExplorerProposalLazyQueryHookResult = ReturnType<typeof useExplorerProposalLazyQuery>;
export type ExplorerProposalQueryResult = Apollo.QueryResult<ExplorerProposalQuery, ExplorerProposalQueryVariables>;
@@ -0,0 +1,82 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import ProposalLink from './proposal-link';
import { ExplorerProposalDocument } from './__generated__/Proposal';
import { GraphQLError } from 'graphql';
function renderComponent(id: string, mocks: MockedResponse[]) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<ProposalLink id={id} />
</MemoryRouter>
</MockedProvider>
);
}
describe('Proposal link component', () => {
it('Renders the ID at first', () => {
const res = render(renderComponent('123', []));
expect(res.getByText('123')).toBeInTheDocument();
});
it('Renders the ID on error', async () => {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: '456',
},
},
result: {
errors: [new GraphQLError('No such proposal')],
},
};
const res = render(renderComponent('456', [mock]));
// The ID
expect(res.getByText('456')).toBeInTheDocument();
});
it('Renders the proposal title when the query returns a result', async () => {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: '123',
},
},
result: {
data: {
proposal: {
id: '123',
rationale: {
title: 'test-title',
description: 'test description',
},
},
},
},
};
const res = render(renderComponent('123', [mock]));
expect(res.getByText('123')).toBeInTheDocument();
expect(await res.findByText('test-title')).toBeInTheDocument();
});
it('Leaves the proposal id when the market is not found', async () => {
const mock = {
request: {
query: ExplorerProposalDocument,
variables: {
id: '123',
},
},
error: new Error('No such asset'),
};
const res = render(renderComponent('123', [mock]));
expect(await res.findByText('123')).toBeInTheDocument();
});
});
@@ -0,0 +1,28 @@
import { useExplorerProposalQuery } from './__generated__/Proposal';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ENV } from '../../../config/env';
import Hash from '../hash';
export type ProposalLinkProps = {
id: string;
};
/**
* Given a proposal ID, generates an external link over to
* the Governance page for more information
*/
const ProposalLink = ({ id }: ProposalLinkProps) => {
const { data } = useExplorerProposalQuery({
variables: { id },
});
const base = ENV.dataSources.governanceUrl;
const label = data?.proposal?.rationale.title || id;
return (
<ExternalLink href={`${base}/proposals/${id}`}>
<Hash text={label} />
</ExternalLink>
);
};
export default ProposalLink;
@@ -117,7 +117,7 @@ const NestedDataListItem = ({
)}
</h4>
{!hasChildren && (
<code className="text-vega-light-400 mb-2 last:mb-0 dark:text-vega-dark-400 break-all">
<code className="text-vega-light-100 mb-2 last:mb-0 dark:text-vega-dark-100 break-all">
{JSON.stringify(value, null, ' ')}
</code>
)}
@@ -19,6 +19,7 @@ fragment ExplorerDeterministicOrderFields on Order {
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -3,7 +3,7 @@ import * as Types from '@vegaprotocol/types';
import { gql } from '@apollo/client';
import * as Apollo from '@apollo/client';
const defaultOptions = {} as const;
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderFieldsFragment = { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } };
export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
orderId: Types.Scalars['ID'];
@@ -11,7 +11,7 @@ export type ExplorerDeterministicOrderQueryVariables = Types.Exact<{
}>;
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export type ExplorerDeterministicOrderQuery = { __typename?: 'Query', orderByID: { __typename?: 'Order', id: string, type?: Types.OrderType | null, reference: string, status: Types.OrderStatus, version: string, createdAt: any, updatedAt?: any | null, expiresAt?: any | null, timeInForce: Types.OrderTimeInForce, price: string, side: Types.Side, remaining: string, size: string, rejectionReason?: Types.OrderRejectionReason | null, party: { __typename?: 'Party', id: string }, market: { __typename?: 'Market', id: string, decimalPlaces: number, positionDecimalPlaces: number, state: Types.MarketState, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', name: string, product: { __typename?: 'Future', quoteName: string } } } } } };
export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
fragment ExplorerDeterministicOrderFields on Order {
@@ -35,6 +35,7 @@ export const ExplorerDeterministicOrderFieldsFragmentDoc = gql`
market {
id
decimalPlaces
positionDecimalPlaces
tradableInstrument {
instrument {
name
@@ -61,6 +61,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
__typename: 'Market',
id: '789',
state: 'STATUS_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: '5',
tradableInstrument: {
instrument: {
@@ -88,6 +89,7 @@ function renderExistingAmend(id: string, version: number, amend: Amend) {
market: {
id: '789',
decimalPlaces: 5,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -4,6 +4,7 @@ import { MarketLink } from '../links';
import PriceInMarket from '../price-in-market/price-in-market';
import { Time } from '../time';
import { sideText, statusText, tifFull, tifShort } from './lib/order-labels';
import SizeInMarket from '../size-in-market/size-in-market';
export interface DeterministicOrderDetailsProps {
id: string;
@@ -90,7 +91,7 @@ const DeterministicOrderDetails = ({
<div className="mb-12 md:mb-0">
<h2 className="text-2xl font-bold text-dark mb-4">{t('Size')}</h2>
<h5 className="text-lg font-medium text-gray-500 mb-0">
{o.size}
<SizeInMarket size={o.size} marketId={o.market.id} />
</h5>
</div>
@@ -38,6 +38,7 @@ const mock = {
__typename: 'Market',
id: '789',
state: 'STATE_ACTIVE',
positionDecimalPlaces: 2,
decimalPlaces: 2,
tradableInstrument: {
instrument: {
@@ -90,6 +90,7 @@ describe('Order TX Summary component', () => {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
@@ -97,6 +98,10 @@ describe('Order TX Summary component', () => {
product: {
__typename: 'Future',
quoteName: 'TEST',
settlementAsset: {
__typeName: 'SettlementAsset',
decimals: 18,
},
},
},
},
@@ -3,61 +3,79 @@ import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import PriceInMarket from './price-in-market';
import type { DecimalSource } from './price-in-market';
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
function renderComponent(
price: string,
marketId: string,
mocks: MockedResponse[]
mocks: MockedResponse[],
decimalSource: DecimalSource = 'MARKET'
) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<PriceInMarket marketId={marketId} price={price} />
<PriceInMarket
marketId={marketId}
price={price}
decimalSource={decimalSource}
/>
</MemoryRouter>
</MockedProvider>
);
}
const fullMock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'test dai',
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 18,
},
},
},
},
},
},
},
};
describe('Price in Market component', () => {
it('Renders the raw price when there is no market data', () => {
const res = render(renderComponent('100', '123', []));
expect(res.getByText('100')).toBeInTheDocument();
});
it('Renders the formatted price when market data is fetched', async () => {
const mock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'test dai',
product: {
__typename: 'Future',
quoteName: 'dai',
},
},
},
},
},
},
};
const res = render(renderComponent('100', '123', [mock]));
it('Renders the formatted price when market data is fetched, using market decimals by default', async () => {
const res = render(renderComponent('100', '123', [fullMock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
expect(await res.findByText('dai')).toBeInTheDocument();
});
it('Renders the formatted price when market data is fetched, using settlement decimals', async () => {
const res = render(
renderComponent('100', '123', [fullMock], 'SETTLEMENT_ASSET')
);
expect(await res.findByText('0.0000000000000001')).toBeInTheDocument();
expect(await res.findByText('dai')).toBeInTheDocument();
});
it('Leaves the market id when the market is not found', async () => {
const mock = {
request: {
@@ -3,16 +3,23 @@ import isUndefined from 'lodash/isUndefined';
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
import get from 'lodash/get';
export type DecimalSource = 'MARKET' | 'SETTLEMENT_ASSET';
export type PriceInMarketProps = {
marketId: string;
price: string;
decimalSource?: DecimalSource;
};
/**
* Given a market ID and a price it will fetch the market
* and format the price in that market's decimal places.
*/
const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
const PriceInMarket = ({
marketId,
price,
decimalSource = 'MARKET',
}: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
@@ -20,8 +27,19 @@ const PriceInMarket = ({ marketId, price }: PriceInMarketProps) => {
let label = price;
if (data && data.market?.decimalPlaces) {
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
if (data) {
if (decimalSource === 'MARKET' && data.market?.decimalPlaces) {
label = addDecimalsFormatNumber(price, data.market.decimalPlaces);
} else if (
decimalSource === 'SETTLEMENT_ASSET' &&
data.market?.tradableInstrument.instrument.product.settlementAsset
) {
label = addDecimalsFormatNumber(
price,
data.market?.tradableInstrument.instrument.product.settlementAsset
.decimals
);
}
}
const suffix = get(
@@ -1,5 +1,4 @@
import { t } from '@vegaprotocol/react-helpers';
import React from 'react';
import { StatusMessage } from '../status-message';
interface RenderFetchedProps {
@@ -66,7 +66,9 @@ export const Search = () => {
className="text-white"
hasError={Boolean(error?.message)}
type="text"
placeholder={t('Enter block number, party id or transaction hash')}
placeholder={t(
'Enter block number, public key or transaction hash'
)}
/>
{error?.message && (
<div className="bg-white border border-t-0 border-accent absolute top-[100%] flex-1 w-full pb-2 px-2 rounded-b text-black">
@@ -0,0 +1,69 @@
import { MemoryRouter } from 'react-router-dom';
import { MockedProvider } from '@apollo/client/testing';
import type { MockedResponse } from '@apollo/client/testing';
import { render } from '@testing-library/react';
import SizeInMarket from './size-in-market';
import type { DecimalSource } from './size-in-market';
import { ExplorerMarketDocument } from '../links/market-link/__generated__/Market';
function renderComponent(
price: string,
marketId: string,
mocks: MockedResponse[],
decimalSource: DecimalSource = 'MARKET'
) {
return (
<MockedProvider mocks={mocks} addTypename={false}>
<MemoryRouter>
<SizeInMarket
marketId={marketId}
size={price}
decimalSource={decimalSource}
/>
</MemoryRouter>
</MockedProvider>
);
}
const fullMock = {
request: {
query: ExplorerMarketDocument,
variables: {
id: '123',
},
},
result: {
data: {
market: {
id: '123',
decimalPlaces: 2,
positionDecimalPlaces: 2,
state: 'irrelevant-test-data',
tradableInstrument: {
instrument: {
name: 'test dai',
product: {
__typename: 'Future',
quoteName: 'dai',
settlementAsset: {
decimals: 18,
},
},
},
},
},
},
},
};
describe('Size in Market component', () => {
it('Renders the raw size when there is no market data', () => {
const res = render(renderComponent('100', '123', []));
expect(res.getByText('100')).toBeInTheDocument();
});
it('Renders the formatted size when market data is fetched', async () => {
const res = render(renderComponent('100', '123', [fullMock]));
expect(await res.findByText('1.00')).toBeInTheDocument();
});
});
@@ -0,0 +1,41 @@
import { addDecimalsFormatNumber } from '@vegaprotocol/react-helpers';
import { useExplorerMarketQuery } from '../links/market-link/__generated__/Market';
export type DecimalSource = 'MARKET';
export type PriceInMarketProps = {
marketId: string;
size: string | number;
decimalSource?: DecimalSource;
};
/**
* Given a market ID and an order size it will fetch the market
* order size, and format the size accordingly
*/
const SizeInMarket = ({
marketId,
size,
decimalSource = 'MARKET',
}: PriceInMarketProps) => {
const { data } = useExplorerMarketQuery({
variables: { id: marketId },
fetchPolicy: 'cache-first',
});
let label = size;
if (data) {
if (decimalSource === 'MARKET' && data.market?.positionDecimalPlaces) {
label = addDecimalsFormatNumber(size, data.market.positionDecimalPlaces);
}
}
return (
<label>
<span>{label}</span>
</label>
);
};
export default SizeInMarket;
@@ -3,6 +3,7 @@ import React from 'react';
import classnames from 'classnames';
interface TableProps {
allowWrap?: boolean;
children: React.ReactNode;
className?: string;
}
@@ -25,8 +26,15 @@ interface TableCellProps extends ThHTMLAttributes<HTMLTableCellElement> {
modifier?: 'bordered' | 'background';
}
export const Table = ({ children, className, ...props }: TableProps) => {
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
export const Table = ({
allowWrap,
children,
className,
...props
}: TableProps) => {
const classes = allowWrap
? className
: classnames(className, 'overflow-x-auto whitespace-nowrap');
return (
<div className={classes}>
<table className="w-full" {...props}>
@@ -37,11 +45,14 @@ export const Table = ({ children, className, ...props }: TableProps) => {
};
export const TableWithTbody = ({
allowWrap,
children,
className,
...props
}: TableProps) => {
const classes = classnames(className, 'overflow-x-auto whitespace-nowrap');
const classes = allowWrap
? className
: classnames(className, 'overflow-x-auto whitespace-nowrap');
return (
<div className={classes}>
<table className="w-full" {...props}>
@@ -63,16 +63,20 @@ describe('Chain Event: Builtin asset deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -34,7 +34,7 @@ export const TxDetailsChainEventBuiltinDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId} /> ({t('built in asset')})
<AssetLink assetId={deposit.vegaAssetId} /> ({t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -69,16 +69,20 @@ describe('Chain Event: Builtin asset withdrawal', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.partyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.partyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -39,8 +39,8 @@ export const TxDetailsChainEventBuiltinWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId || ''} /> ({t('built in asset')}
)
<AssetLink assetId={withdrawal.vegaAssetId || ''} /> (
{t('built in asset')})
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -60,9 +60,10 @@ describe('Chain Event: ERC20 Asset Delist', () => {
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
});
});
@@ -29,7 +29,7 @@ export const TxDetailsChainEventErc20AssetDelist = ({
<TableRow modifier="bordered">
<TableCell>{t('Removed Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetDelist.vegaAssetId || ''} />
<AssetLink assetId={assetDelist.vegaAssetId || ''} />
</TableCell>
</TableRow>
</>
@@ -76,14 +76,18 @@ describe('Chain Event: ERC20 Asset limits updated', () => {
expect(screen.getByText(t('Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('ERC20 asset'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
@@ -51,7 +51,7 @@ export const TxDetailsChainEventErc20AssetLimitsUpdated = ({
<TableRow modifier="bordered">
<TableCell>{t('Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetLimitsUpdated.vegaAssetId} />
<AssetLink assetId={assetLimitsUpdated.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -62,14 +62,18 @@ describe('Chain Event: ERC20 Asset List', () => {
expect(screen.getByText(t('Added Vega asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.assetSource}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.assetSource}`
);
});
@@ -41,7 +41,7 @@ export const TxDetailsChainEventErc20AssetList = ({
<TableRow modifier="bordered">
<TableCell>{t('Added Vega asset')}</TableCell>
<TableCell>
<AssetLink id={assetList.vegaAssetId} />
<AssetLink assetId={assetList.vegaAssetId} />
</TableCell>
</TableRow>
</>
@@ -62,21 +62,28 @@ describe('Chain Event: ERC20 asset deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.targetPartyId}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.targetPartyId}`
);
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.sourceEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.sourceEthereumAddress}`
);
});
@@ -51,7 +51,7 @@ export const TxDetailsChainEventDeposit = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={deposit.vegaAssetId} />
<AssetLink assetId={deposit.vegaAssetId} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -57,14 +57,18 @@ describe('Chain Event: ERC20 asset deposit', () => {
expect(screen.getByText(t('Asset'))).toBeInTheDocument();
const assetLink = screen.getByText(`${fullMock.vegaAssetId}`);
expect(assetLink).toBeInTheDocument();
expect(assetLink.tagName).toEqual('A');
expect(assetLink.getAttribute('href')).toEqual(
`/assets#${fullMock.vegaAssetId}`
);
if (!assetLink.parentElement) {
throw new Error('Asset link does not exist');
}
expect(assetLink.parentElement.tagName).toEqual('BUTTON');
expect(assetLink.parentElement.textContent).toEqual(fullMock.vegaAssetId);
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.targetEthereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.targetEthereumAddress}`
);
});
@@ -45,7 +45,7 @@ export const TxDetailsChainEventWithdrawal = ({
<TableRow modifier="bordered">
<TableCell>{t('Asset')}</TableCell>
<TableCell>
<AssetLink id={withdrawal.vegaAssetId} />
<AssetLink assetId={withdrawal.vegaAssetId} />
</TableCell>
</TableRow>
</>
@@ -64,14 +64,20 @@ describe('Chain Event: Stake deposit', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
@@ -64,14 +64,20 @@ describe('Chain Event: Stake remove', () => {
expect(screen.getByText(t('Recipient'))).toBeInTheDocument();
const partyLink = screen.getByText(`${fullMock.vegaPublicKey}`);
expect(partyLink).toBeInTheDocument();
expect(partyLink.tagName).toEqual('A');
expect(partyLink.getAttribute('href')).toEqual(
if (!partyLink.parentElement) {
throw new Error('Party link does not exist');
}
expect(partyLink.parentElement.tagName).toEqual('A');
expect(partyLink.parentElement.getAttribute('href')).toEqual(
`/parties/${fullMock.vegaPublicKey}`
);
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.ethereumAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.ethereumAddress}`
);
});
@@ -66,7 +66,10 @@ describe('Chain Event: Stake total supply change', () => {
expect(screen.getByText(t('Source'))).toBeInTheDocument();
const ethLink = screen.getByText(`${fullMock.tokenAddress}`);
expect(ethLink.getAttribute('href')).toContain(
if (!ethLink.parentElement) {
throw new Error('ETH link does not exist');
}
expect(ethLink.parentElement.getAttribute('href')).toContain(
`/address/${fullMock.tokenAddress}`
);
});
@@ -2,16 +2,5 @@ query ExplorerSettlementAssetForMarket($id: ID!) {
market(id: $id) {
id
decimalPlaces
tradableInstrument {
instrument {
product {
... on Future {
settlementAsset {
decimals
}
}
}
}
}
}
}
@@ -8,7 +8,7 @@ export type ExplorerSettlementAssetForMarketQueryVariables = Types.Exact<{
}>;
export type ExplorerSettlementAssetForMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number, tradableInstrument: { __typename?: 'TradableInstrument', instrument: { __typename?: 'Instrument', product: { __typename?: 'Future', settlementAsset: { __typename?: 'Asset', decimals: number } } } } } | null };
export type ExplorerSettlementAssetForMarketQuery = { __typename?: 'Query', market?: { __typename?: 'Market', id: string, decimalPlaces: number } | null };
export const ExplorerSettlementAssetForMarketDocument = gql`
@@ -16,17 +16,6 @@ export const ExplorerSettlementAssetForMarketDocument = gql`
market(id: $id) {
id
decimalPlaces
tradableInstrument {
instrument {
product {
... on Future {
settlementAsset {
decimals
}
}
}
}
}
}
}
`;
@@ -91,7 +91,7 @@ describe('LiquidityProvisionDetails component', () => {
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('-1')).toBeInTheDocument();
expect(res.getByText('Best Bid')).toBeInTheDocument();
expect(res.getByText('40% (normalised from: 20%)')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
});
it('handles a missing offset gracefully (should not happen)', () => {
@@ -125,7 +125,7 @@ describe('LiquidityProvisionDetails component', () => {
);
// Row test ids and keys are based on the side, reference and proportion - and that proportion is scaled
expect(res.getByTestId('SIDE_SELL-40-1')).toBeInTheDocument();
expect(res.getByText('40% (normalised from: 20%)')).toBeInTheDocument();
expect(res.getByText('40%')).toBeInTheDocument();
expect(res.getByText('-')).toBeInTheDocument();
});
});
@@ -67,11 +67,7 @@ export function LiquidityProvisionDetailsRow({
<td className="text-center">
{order.reference ? LiquidityReferenceLabel[order.reference] : '-'}
</td>
<td className="text-center">
{proportion === order.proportion
? `${proportion}%`
: `${proportion}% (normalised from: ${order.proportion}%)`}{' '}
</td>
<td className="text-center">{proportion}%</td>
</TableRow>
);
}
@@ -13,15 +13,6 @@ const decimalsMock: ExplorerSettlementAssetForMarketQuery = {
id: '123',
__typename: 'Market',
decimalPlaces: 5,
tradableInstrument: {
instrument: {
product: {
settlementAsset: {
decimals: 5,
},
},
},
},
},
};
@@ -36,7 +36,7 @@ export function LiquidityProvisionOffset({
}
/**
* Does the work of formatting the number now we have the settlement decimal places.
* Does the work of formatting the number now we have the market decimal places.
* If no market data is assigned (i.e. during loading, or if the market doesn't exist)
* this function will return the unformatted number
*
@@ -49,9 +49,7 @@ export function getFormattedOffset(
offset: string,
data?: ExplorerSettlementAssetForMarketQuery
) {
const decimals =
data?.market?.tradableInstrument.instrument.product.settlementAsset
.decimals;
const decimals = data?.market?.decimalPlaces;
if (!decimals) {
return offset;
@@ -203,7 +203,7 @@ describe('LiquidityProvisionDetails component', () => {
};
const res = renderComponent(mock);
expect(res.getByText('45% (normalised from: 25%)')).toBeInTheDocument();
expect(res.getByText('55% (normalised from: 30%)')).toBeInTheDocument();
expect(res.getByText('45%')).toBeInTheDocument();
expect(res.getByText('55%')).toBeInTheDocument();
});
});
@@ -0,0 +1,45 @@
import { render } from '@testing-library/react';
import { JSONOracleData, parseData } from './data-submission-json-oracle';
describe('JSONOracleData', () => {
it('renders a code block if there is a payload that is base64 encoded', () => {
const p = 'eyJwcmljZXMuRVRIOTkudmFsdWUiOiAiMTMwNTAwMDAwMCJ9';
const screen = render(<JSONOracleData payload={p} />);
expect(screen.getByTestId('json-code')).toBeInTheDocument();
});
it('renders an error if b64 decoding fails', () => {
const p = 'not-encoded';
const screen = render(<JSONOracleData payload={p} />);
expect(
screen.getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
describe('Parse JSON oracle data', () => {
it('returns null for an invalid b64 string', () => {
expect(parseData('🤷')).toBeNull();
});
it('returns null for a string that looks like an object', () => {
expect(parseData('{}')).toBeNull();
});
it('returns null for null', () => {
expect(parseData(null as unknown as string)).toBeNull();
});
it('returns a string for b64 encoded data', () => {
const res = parseData('dGVzdCA9IHRydWU=');
expect(typeof res).toEqual('string');
expect(res).toEqual('test = true');
});
it('returns a string for b64 encoded json, without any extra parsing', () => {
const res = parseData('eyJ0ZXN0Ijp0cnVlfQ==');
expect(typeof res).toEqual('string');
expect(res).toEqual('{"test":true}');
});
});
@@ -0,0 +1,40 @@
import { t } from '@vegaprotocol/react-helpers';
interface JSONOracleDataProps {
payload: string;
}
/**
* Renders a JSON oracle. Given the shape could be any valid JSON,
* this doesn't try to do anything smart - it just renders the message
*/
export const JSONOracleData = ({ payload }: JSONOracleDataProps) => {
const decodedSubmission = parseData(payload);
if (!decodedSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
return (
<section>
<code data-testid="json-code">{decodedSubmission}</code>
</section>
);
};
/**
* Safely parses the JSON Oracle payload
*
* @param payload base64 encoded JSON
* @returns Object or null
*/
export function parseData(payload: string) {
try {
if (!payload || typeof payload !== 'string') {
throw new Error('Not a string');
}
return atob(payload);
} catch (e) {
return null;
}
}
@@ -0,0 +1,58 @@
import { render } from '@testing-library/react';
import { OpenOracleData, parseData } from './data-submission-open-oracle';
describe('OpenOracleData', () => {
it('renders a table if there is data', () => {
const p =
'eyJtZXNzYWdlcyI6IFsiMHgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDgwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDA2M2M3ZTMwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwYzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNGYxZjM4ZWEwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwNjcwNzI2OTYzNjU3MzAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAzNDI1NDQzMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMCJdLCAicHJpY2VzIjogeyJFVEgiOiIxMjMifSwgInNpZ25hdHVyZXMiOiBbIjB4N2U0Nzc2OWEyNzI5NzIwN2Q5ZTIyMmM3YTY2YjBlYzk3Njg4MWY3NmVkYjg5OGFhYzQ5OTRlZWYwOTc4MmI3NGUxMGE5MTJmZWQ1Y2E3NmFlN2U3NzVjOWZiOTBjZGE4ZjhkMDhlNzUyOTE1NzQ2ZTY2OGE1ZWM3OWRmOTZmNmQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDFjIl0sICJ0aW1lc3RhbXAiOiAiMTIzIn0=';
const screen = render(<OpenOracleData payload={p} />);
expect(screen.getByTestId('decoded-payload')).toBeInTheDocument();
expect(screen.getByText('ETH')).toBeInTheDocument();
expect(screen.getByText('123')).toBeInTheDocument();
});
it('renders an error if decoding fails', () => {
const p = 'not-encoded';
const screen = render(<OpenOracleData payload={p} />);
expect(
screen.getByText('Awaiting Block Explorer transaction details')
).toBeInTheDocument();
});
});
describe('Parse Open oracle data', () => {
it('returns null for an invalid b64 string', () => {
expect(parseData('🤷')).toBeNull();
});
it('returns null for a string that looks like an object', () => {
expect(parseData('{}')).toBeNull();
});
it('returns null for null', () => {
expect(parseData(null as unknown as string)).toBeNull();
});
it('returns a string for b64 encoded data', () => {
const res = parseData('dGVzdCA9IHRydWU=');
expect(res).toBeNull();
});
it('returns null if the parsed object does not look like te right shape', () => {
// Encoded: {"test":true}
const res = parseData('eyJ0ZXN0Ijp0cnVlfQ==');
expect(res).toBeNull();
});
it('returns an object if the payload parses and looks fine', () => {
// Encoded: {"messages": [], "prices": {}, "signatures": [], "timestamp": "123"}
const res = parseData(
'eyJtZXNzYWdlcyI6IFtdLCAicHJpY2VzIjoge30sICJzaWduYXR1cmVzIjogW10sICJ0aW1lc3RhbXAiOiAiMTIzIn0='
);
expect(typeof res.prices).toEqual('object');
expect(Array.isArray(res.messages)).toBeTruthy();
expect(Array.isArray(res.signatures)).toBeTruthy();
});
});
@@ -0,0 +1,64 @@
import { t } from '@vegaprotocol/react-helpers';
import { NestedDataList } from '../../../nested-data-list';
import { OpenOraclePrices } from './open-oracle/open-oracle-prices';
interface OpenOracleDataProps {
payload: string;
}
/**
* Decodes Open Oracle data based on it's main parts:
* - messages
* - signatures
* - prices
*/
export const OpenOracleData = ({ payload }: OpenOracleDataProps) => {
const decodedSubmission = parseData(payload);
if (!decodedSubmission) {
return <>{t('Awaiting Block Explorer transaction details')}</>;
}
const date = decodedSubmission.timestamp;
const formattedDate = new Date(date * 1000).toLocaleString();
return (
<section>
<span>{formattedDate}</span>
<code>
<OpenOraclePrices
prices={decodedSubmission.prices}
messages={decodedSubmission.messages}
signatures={decodedSubmission.signatures}
/>
</code>
<details data-testid="decoded-payload">
<summary>{t('Decoded payload')}</summary>
<NestedDataList data={decodedSubmission} />
</details>
</section>
);
};
/**
* Safely parses the Open Oracle payload
*
* @param payload base64 encoded JSON
* @returns Object or null
*/
export function parseData(payload: string) {
try {
if (!payload || typeof payload !== 'string') {
throw new Error('Not a string');
}
const res = JSON.parse(atob(payload));
if (!res.prices || !res.messages || !res.signatures) {
throw new Error('Not an open oracle format message');
}
return res;
} catch (e) {
return null;
}
}
@@ -0,0 +1,88 @@
import { render } from '@testing-library/react';
import { OpenOraclePrices } from './open-oracle-prices';
import type { Price } from './open-oracle-prices';
describe('OpenOraclePrices', () => {
it('Will not render with no prices', () => {
const p = undefined as unknown as Price;
const m: string[] = [];
const s: string[] = [];
const screen = render(
<OpenOraclePrices prices={p} messages={m} signatures={s} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('Will not render with no messages', () => {
const m = undefined as unknown as string[];
const p: Price = {};
const s: string[] = [];
const screen = render(
<OpenOraclePrices prices={p} messages={m} signatures={s} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('will not render with no signatures', () => {
const s = undefined as unknown as string[];
const m: string[] = [];
const p: Price = {};
const screen = render(
<OpenOraclePrices prices={p} messages={m} signatures={s} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('will not render with mismatched prices/signatures', () => {
const p: Price = { ETH: '123' };
const m = ['0x123', '0x456'];
const s: string[] = [];
const screen = render(
<OpenOraclePrices prices={p} messages={m} signatures={s} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('will not render with mismatched signatures and messages', () => {
const p: Price = { ETH: '123', BTC: '12' };
const s = ['0xcac', '0xb33f'];
const m = ['0x1204o42c', '0xb3ddd3f', '0x9999'];
const screen = render(
<OpenOraclePrices prices={p} messages={m} signatures={s} />
);
expect(screen.container).toBeEmptyDOMElement();
});
it('renders a table when there are prices and signatures and messages', () => {
const p: Price = { ETH: '123', BTC: '12' };
const s = ['0xcac', '0xb33f'];
const m = ['0x120442c', '0xb3ddd3f'];
const screen = render(
<OpenOraclePrices prices={p} messages={m} signatures={s} />
);
expect(screen.getByTestId('openoracleprices')).toBeInTheDocument();
// Table headings
expect(screen.getByText('Asset')).toBeInTheDocument();
expect(screen.getByText('Price')).toBeInTheDocument();
expect(screen.getByText('Signature')).toBeInTheDocument();
expect(screen.getByText('Message')).toBeInTheDocument();
// Disabled - see vegaprotocol/frontend-monorepo#/2726
// expect(screen.getByText('Signer')).toBeInTheDocument();
// One row per asset
expect(screen.getByText('ETH')).toBeInTheDocument();
expect(screen.getByText('123')).toBeInTheDocument();
expect(screen.getByText('0xcac')).toBeInTheDocument();
expect(screen.getByText('0xb33f')).toBeInTheDocument();
// Does not test signer element for this row
expect(screen.getByText('BTC')).toBeInTheDocument();
expect(screen.getByText('12')).toBeInTheDocument();
expect(screen.getByText('0x120442c')).toBeInTheDocument();
expect(screen.getByText('0xb3ddd3f')).toBeInTheDocument();
// Does not test signer element for this row
});
});
@@ -0,0 +1,132 @@
import { t } from '@vegaprotocol/react-helpers';
import { CopyWithTooltip } from '@vegaprotocol/ui-toolkit';
import { TableRow } from '../../../../table';
import { TruncateInline } from '../../../../truncate/truncate';
import { utils } from 'ethers';
export type Price = {
[key: string]: string;
};
interface OpenOraclePricesProps {
prices: Price;
signatures: string[];
messages: string[];
}
/**
* Open Oracle price table, showing the entries in this
* decoded message
*
* Notes:
* - Signer is derived by recovering the address from the
* signature and the message. This is currently disabled
* as the implementation is incorrect
*/
export function OpenOraclePrices({
prices,
messages,
signatures,
}: OpenOraclePricesProps) {
if (
!prices ||
!messages ||
!signatures ||
Object.keys(prices).length !== messages.length ||
signatures.length !== messages.length
) {
return null;
}
return (
<table data-testid="openoracleprices">
<thead>
<TableRow modifier="bordered">
<th>{t('Asset')}</th>
<th className="text-right">{t('Price')}</th>
<th>{t('Signature')}</th>
<th>{t('Message')}</th>
{/* <th>{t('Signer')}</th> */}
</TableRow>
</thead>
<tbody>
{Object.keys(prices).map((k: string, i: number) => {
if (k && prices[k]) {
const message = messages[i];
const signature = signatures[i];
return (
<OpenOraclePrice
key={`price-${i}`}
asset={k}
value={prices[k]}
message={message}
signature={signature}
/>
);
} else {
return null;
}
})}
</tbody>
</table>
);
}
type OpenOraclePriceProps = {
asset: string;
value: string;
message: string;
signature: string;
};
export function OpenOraclePrice({
asset,
value,
signature,
message,
}: OpenOraclePriceProps) {
// const addr = getAddressFromMessageAndSignature(message, signature);
return (
<TableRow modifier="bordered" key={`${asset}`}>
<td className="px-4">{asset}</td>
<td className="px-4 text-right">{value}</td>
<td className="px-4">
<CopyWithTooltip text={signature}>
<button>
<TruncateInline text={signature} startChars={5} endChars={5} />
</button>
</CopyWithTooltip>
</td>
<td className="px-4">
<CopyWithTooltip text={message}>
<button>
<TruncateInline text={message} startChars={5} endChars={5} />
</button>
</CopyWithTooltip>
</td>
{/*<td className="px-4">
<CopyWithTooltip text={message}>
<button>
<TruncateInline text={addr} startChars={5} endChars={5} />
</button>
</CopyWithTooltip>
</td> */}
</TableRow>
);
}
export function getAddressFromMessageAndSignature(
message: string,
signature: string
) {
try {
const m = utils.hashMessage(utils.arrayify(utils.keccak256(message)));
const s = utils.splitSignature(
signature.slice(0, 130) + signature.slice(-2)
);
return utils.recoverAddress(m, s);
} catch (e) {
return '-';
}
}
@@ -7,6 +7,8 @@ import type { BlockExplorerTransactionResult } from '../../../../routes/types/bl
import type { TendermintBlocksResponse } from '../../../../routes/blocks/tendermint-blocks-response';
import { Time } from '../../../time';
import { ChainResponseCode } from '../chain-response-code/chain-reponse.code';
import { TxDataView } from '../../tx-data-view';
import Hash from '../../../links/hash';
interface TxDetailsSharedProps {
txData: BlockExplorerTransactionResult | undefined;
@@ -46,7 +48,7 @@ export const TxDetailsShared = ({
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Hash')}</TableCell>
<TableCell>
<code>{txData.hash}</code>
<Hash text={txData.hash} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
@@ -82,6 +84,12 @@ export const TxDetailsShared = ({
<ChainResponseCode code={txData.code} error={txData.error} />
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell {...sharedHeaderProps}>{t('Transaction')}</TableCell>
<TableCell>
<TxDataView blockData={blockData} txData={txData} />
</TableCell>
</TableRow>
</>
);
};
@@ -0,0 +1,108 @@
import { getValues } from './bound-factors';
import type { components } from '../../../../../types/explorer';
type KeyValueBundle = components['schemas']['vegaKeyValueBundle'][];
describe('getValues', () => {
it('handles an empty array by returning a dashed template', () => {
const res = getValues([]);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '-');
expect(res.up).toHaveProperty('value', '-');
});
it('handles undefined', () => {
const res = getValues(undefined as unknown as KeyValueBundle);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '-');
expect(res.up).toHaveProperty('value', '-');
});
it('handles a kvb that only has one side (should not happen)', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
it('handles a kvb that has a matrixVal instead of a scalarval by ignoring it', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { matrixVal: { value: [{ value: ['0.123'] }] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '-');
expect(res.down).toHaveProperty('value', '-');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '-');
});
it('ignores unexpected extra values in the kvb', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123', '0.77'] } },
},
{
key: 'down',
tolerance: '0.001',
value: { vectorVal: { value: ['0.321'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '0.001');
expect(res.down).toHaveProperty('value', '0.321');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
it('handles a full kvb', () => {
const k: KeyValueBundle = [
{
key: 'up',
tolerance: '0.1',
value: { vectorVal: { value: ['0.123'] } },
},
{
key: 'down',
tolerance: '0.001',
value: { vectorVal: { value: ['0.321'] } },
},
];
const res = getValues(k);
expect(res).toHaveProperty('down');
expect(res.down).toHaveProperty('tolerance', '0.001');
expect(res.down).toHaveProperty('value', '0.321');
expect(res).toHaveProperty('up');
expect(res.up).toHaveProperty('tolerance', '0.1');
expect(res.up).toHaveProperty('value', '0.123');
});
});
@@ -0,0 +1,87 @@
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
interface StateVariableProposalBoundFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* A dumb as rocks function completely tied to what the structure of this variable should be
* @param kvb The key/value bundle
* @returns Object
*/
export function getValues(kvb: StateVariableProposalBoundFactorsProps['kvb']) {
const template = {
up: {
tolerance: '-',
value: '-',
},
down: {
tolerance: '-',
value: '-',
},
};
if (kvb && kvb.length > 0) {
kvb.forEach((v) => {
if (v.key === 'up') {
template.up.tolerance = v.tolerance || '-';
template.up.value = v.value?.vectorVal?.value
? v.value?.vectorVal.value[0]
: '-';
} else if (v.key === 'down') {
template.down.tolerance = v.tolerance || '-';
template.down.value = v.value?.vectorVal?.value
? v.value?.vectorVal.value[0]
: '-';
}
});
}
return template;
}
/**
* State Variable proposals updating Bound Factors. This contains two bundles,
* an up vector and a down vector
*
* This is nearly identical to risk factors.
*/
export const StateVariableProposalBoundFactors = ({
kvb,
}: StateVariableProposalBoundFactorsProps) => {
const v = getValues(kvb);
return (
<Table allowWrap={true} className="w-1/3">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Parameter')}</TableHeader>
<TableHeader align="center">{t('New value')}</TableHeader>
<TableHeader align="right">{t('Tolerance')}</TableHeader>
</TableRow>
</thead>
<tbody>
<TableRow modifier="bordered">
<TableCell>{t('Up')}</TableCell>
<TableCell align="right" className="font-mono">
{v.up.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.up.tolerance}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Down')}</TableCell>
<TableCell align="right" className="font-mono">
{v.down.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.down.tolerance}
</TableCell>
</TableRow>
</tbody>
</Table>
);
};
@@ -0,0 +1,31 @@
import type { components } from '../../../../../types/explorer';
import { StateVariableProposalUnknown } from './unknown';
import { StateVariableProposalBoundFactors } from './bound-factors';
import { StateVariableProposalRiskFactors } from './risk-factors';
interface StateVariableProposalWrapperProps {
stateVariable: string | undefined;
kvb: readonly components['schemas']['vegaKeyValueBundle'][] | undefined;
}
/**
* State Variable proposals
*/
export const StateVariableProposalWrapper = ({
stateVariable,
kvb,
}: StateVariableProposalWrapperProps) => {
if (!stateVariable || !kvb || kvb.length === 0) {
return null;
}
if (stateVariable.indexOf('bound-factors') !== -1) {
return <StateVariableProposalBoundFactors kvb={kvb} />;
} else if (stateVariable.indexOf('risk-factors') !== -1) {
return <StateVariableProposalRiskFactors kvb={kvb} />;
} else if (stateVariable.indexOf('probability_of_trading') !== -1) {
return <StateVariableProposalRiskFactors kvb={kvb} />;
} else {
return <StateVariableProposalUnknown kvb={kvb} />;
}
};
@@ -0,0 +1,124 @@
import { t } from '@vegaprotocol/react-helpers';
import zip from 'lodash/zip';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
import { StateVariableProposalUnknown } from './unknown';
interface StateVariableProposalRiskFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* A dumb as rocks function completely tied to what the structure of this variable should be
*
* @param kvb The key/value bundle
* @returns Object
*/
export function getValues(kvb: StateVariableProposalRiskFactorsProps['kvb']) {
try {
const template = {
bid: {
offsetTolerance: '-',
probabilityTolerance: '-',
offset: [] as Readonly<string[]>,
probability: [] as Readonly<string[]>,
rows: [] as [string | undefined, string | undefined][],
},
ask: {
offsetTolerance: '-',
probabilityTolerance: '-',
offset: [] as Readonly<string[]>,
probability: [] as Readonly<string[]>,
rows: [] as [string | undefined, string | undefined][],
},
};
kvb.forEach((v) => {
if (v.key === 'bidOffset') {
template.bid.offsetTolerance = v.tolerance || '-';
template.bid.offset = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'bidProbability') {
template.bid.probabilityTolerance = v.tolerance || '-';
template.bid.probability = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'askOffset') {
template.ask.offsetTolerance = v.tolerance || '-';
template.ask.offset = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
} else if (v.key === 'askProbability') {
template.ask.probabilityTolerance = v.tolerance || '-';
template.ask.probability = v.value?.vectorVal?.value
? v.value?.vectorVal?.value
: ['0'];
}
});
// Bundles up offset and probability in to a row
if (template.bid.offset.length > 0 && template.bid.probability.length > 0) {
template.bid.rows = zip(template.bid.offset, template.bid.probability);
}
if (template.ask.offset.length > 0 && template.ask.probability.length > 0) {
template.ask.rows = zip(template.ask.offset, template.ask.probability);
}
return template;
} catch (e) {
// This will result in the table not being rendered
return null;
}
}
/**
* State Variable proposals updating Risk Factors. This contains two bundles,
* a long vector and a short vector
*/
export const StateVariableProposalRiskFactors = ({
kvb,
}: StateVariableProposalRiskFactorsProps) => {
const v = getValues(kvb);
const all = v ? zip(v.bid.rows, v.ask.rows) : [];
if (all.length === 0) {
// Give up, do a JSON view
return <StateVariableProposalUnknown kvb={kvb} />;
}
return (
<Table allowWrap={true} className="text-xs lg:text-base max-w-2xl">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Mid offset')}</TableHeader>
<TableHeader align="right">{t('Bid probability')}</TableHeader>
<TableHeader align="right" className="pl-2">
{t('Ask probability')}
</TableHeader>
</TableRow>
</thead>
<tbody>
{all.map((r) => {
// Simple remapping of the data to protect against undefineds
const row = {
o: r[0] ? r[0][0] : r[1] ? r[1][0] : '-',
b: r[0] ? r[0][1] : '-',
a: r[1] ? r[1][1] : '-',
};
return (
<TableRow key={`${row.o}${row.b}${row.a}`}>
<TableCell align="left">{row.o}</TableCell>
<TableCell align="right" className="font-mono">
{row.b}
</TableCell>
<TableCell align="right" className="pl-2 font-mono">
{row.a}
</TableCell>
</TableRow>
);
})}
</tbody>
</Table>
);
};
@@ -0,0 +1,168 @@
import { getValues, StateVariableProposalRiskFactors } from './risk-factors';
import type { components } from '../../../../../types/explorer';
import { render } from '@testing-library/react';
type kvb = components['schemas']['vegaKeyValueBundle'][];
describe('Risk Factors: getValues', () => {
it('returns null if null is passed in', () => {
const res = getValues(null as unknown as kvb);
expect(res).toBeNull();
});
it('returns a blank template if kvb is empty', () => {
const res = getValues([]);
expect(res).not.toBeNull();
expect(res?.bid.offsetTolerance).toEqual('-');
expect(res?.bid.probabilityTolerance).toEqual('-');
expect(res?.bid.probability).toEqual([]);
expect(res?.bid.offset).toEqual([]);
expect(res?.bid.rows.length).toEqual(0);
expect(res?.ask.offsetTolerance).toEqual('-');
expect(res?.ask.probabilityTolerance).toEqual('-');
expect(res?.ask.probability).toEqual([]);
expect(res?.ask.offset).toEqual([]);
expect(res?.ask.rows.length).toEqual(0);
});
it('parses out a correct bid offset and probability', () => {
const k: kvb = [
{
key: 'bidOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['2'] } },
},
];
const res = getValues(k);
expect(res?.bid.offset).toEqual(['1']);
expect(res?.bid.probability).toEqual(['2']);
expect(res?.bid.rows).toEqual([['1', '2']]);
});
it('parses out a correct ask offset and probability', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2'] } },
},
];
const res = getValues(k);
expect(res?.ask.offset).toEqual(['1']);
expect(res?.ask.probability).toEqual(['2']);
expect(res?.ask.rows).toEqual([['1', '2']]);
});
it('parses out a correct ask/bid offset and probability', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4'] } },
},
];
const res = getValues(k);
expect(res?.ask.rows).toEqual([['1', '2']]);
expect(res?.bid.rows).toEqual([['3', '4']]);
});
});
describe('Risk Factors: component', () => {
it('renders 3 rows correctly', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2.2', '2.3', '2.4'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
},
];
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
expect(screen.getByText('Mid offset')).toBeInTheDocument();
expect(screen.getByText('Bid probability')).toBeInTheDocument();
expect(screen.getByText('Ask probability')).toBeInTheDocument();
// First row
expect(screen.getByText('1.1')).toBeInTheDocument();
expect(screen.getByText('2.2')).toBeInTheDocument();
expect(screen.getByText('4.4')).toBeInTheDocument();
// Second row
expect(screen.getByText('1.2')).toBeInTheDocument();
expect(screen.getByText('2.3')).toBeInTheDocument();
expect(screen.getByText('4.5')).toBeInTheDocument();
// Third row
expect(screen.getByText('1.3')).toBeInTheDocument();
expect(screen.getByText('2.4')).toBeInTheDocument();
expect(screen.getByText('4.6')).toBeInTheDocument();
});
it('renders uneven row counts correctly', () => {
const k: kvb = [
{
key: 'askOffset',
value: { vectorVal: { value: ['1.1'] } },
},
{
key: 'askProbability',
value: { vectorVal: { value: ['2.2'] } },
},
{
key: 'bidOffset',
value: { vectorVal: { value: ['1.1', '1.2', '1.3'] } },
},
{
key: 'bidProbability',
value: { vectorVal: { value: ['4.4', '4.5', '4.6'] } },
},
];
const screen = render(<StateVariableProposalRiskFactors kvb={k} />);
expect(screen.getByText('Mid offset')).toBeInTheDocument();
expect(screen.getByText('Bid probability')).toBeInTheDocument();
expect(screen.getByText('Ask probability')).toBeInTheDocument();
// First row, as previous test
expect(screen.getByText('1.1')).toBeInTheDocument();
expect(screen.getByText('2.2')).toBeInTheDocument();
expect(screen.getByText('4.4')).toBeInTheDocument();
// Second row - offset comes from bid, not ask
expect(screen.getByText('1.2')).toBeInTheDocument();
expect(screen.getByText('4.5')).toBeInTheDocument();
// Third row
expect(screen.getByText('1.3')).toBeInTheDocument();
expect(screen.getByText('4.6')).toBeInTheDocument();
// The askOffset levels without a probability render -
expect(screen.getAllByText('-')).toHaveLength(2);
});
});
@@ -0,0 +1,52 @@
import { t } from '@vegaprotocol/react-helpers';
import type { components } from '../../../../../types/explorer';
import { Table, TableRow, TableHeader, TableCell } from '../../../table';
import { getValues } from './bound-factors';
interface StateVariableProposalBoundFactorsProps {
kvb: readonly components['schemas']['vegaKeyValueBundle'][];
}
/**
* State Variable proposals updating Bound Factors. This contains two bundles,
* an up vector and a down vector
*
* This is nearly identical to risk factors.
*/
export const StateVariableProposalBoundFactors = ({
kvb,
}: StateVariableProposalBoundFactorsProps) => {
const v = getValues(kvb);
return (
<Table allowWrap={true} className="w-1/3">
<thead>
<TableRow modifier="bordered">
<TableHeader align="left">{t('Parameter')}</TableHeader>
<TableHeader align="center">{t('New value')}</TableHeader>
<TableHeader align="right">{t('Tolerance')}</TableHeader>
</TableRow>
</thead>
<tbody>
<TableRow modifier="bordered">
<TableCell>{t('Up')}</TableCell>
<TableCell align="right" className="font-mono">
{v.up.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.up.tolerance}
</TableCell>
</TableRow>
<TableRow modifier="bordered">
<TableCell>{t('Down')}</TableCell>
<TableCell align="right" className="font-mono">
{v.down.value}
</TableCell>
<TableCell align="right" className="font-mono">
{v.down.tolerance}
</TableCell>
</TableRow>
</tbody>
</Table>
);
};

Some files were not shown because too many files have changed in this diff Show More