diff --git a/public/locales/ca-CA/translations.json b/public/locales/ca-CA/translations.json index a4b62daf5..d5e305960 100644 --- a/public/locales/ca-CA/translations.json +++ b/public/locales/ca-CA/translations.json @@ -445,8 +445,8 @@ "other_chain_destination": "Destinació a una altra xarxa", "%_of_total_nodes_validators": "% total de nodes i validadors", "version_display": "Versió: {{version}}", - "validators_count": "# de Validadors: {{vals_count}}", - "nodes_count": "# de Nodes: {{nodes_count}}", + "validator_count": "# de Validadors: {{val_count}}", + "node_count": "# de Nodes: {{node_count}}", "current_stable_version": "Versió estable actual", "stable_version": "{{stableVersion}}", "nftoken_minter": "Encunyador NFT", diff --git a/public/locales/en-US/translations.json b/public/locales/en-US/translations.json index c7e8e0642..369edfa7a 100644 --- a/public/locales/en-US/translations.json +++ b/public/locales/en-US/translations.json @@ -445,8 +445,8 @@ "other_chain_destination": "Other Chain Destination", "%_of_total_nodes_validators": "% of Total Nodes & Validators", "version_display": "Version: {{version}}", - "validators_count": "# of Validators: {{vals_count}}", - "nodes_count": "# of Nodes: {{nodes_count}}", + "validator_count": "# of Validators: {{val_count}}", + "node_count": "# of Nodes: {{node_count}}", "current_stable_version": "Current Stable Version", "stable_version": "{{stableVersion}}", "nftoken_minter": "NFT Minter", diff --git a/public/locales/es-ES/translations.json b/public/locales/es-ES/translations.json index 4245651b0..bde04d337 100644 --- a/public/locales/es-ES/translations.json +++ b/public/locales/es-ES/translations.json @@ -441,8 +441,8 @@ "other_chain_destination": "Otro Destino de Cadena", "%_of_total_nodes_validators": "% Total de Nodos y Validadores", "version_display": "Versión: {{version}}", - "validators_count": "# de Validadores: {{vals_count}}", - "nodes_count": "# de Nodos: {{nodes_count}}", + "validator_count": "# de Validadores: {{val_count}}", + "node_count": "# de Nodos: {{node_count}}", "current_stable_version": "Versión Estable Actual", "stable_version": "{{stableVersion}}", "nftoken_minter": "Acuñador NFT", diff --git a/public/locales/fr-FR/translations.json b/public/locales/fr-FR/translations.json index 3265e919e..9cbdc4293 100644 --- a/public/locales/fr-FR/translations.json +++ b/public/locales/fr-FR/translations.json @@ -443,8 +443,8 @@ "other_chain_destination": "Autre destination de chaîne", "%_of_total_nodes_validators": "% des nœuds et des validateurs", "version_display": "Version: {{version}}", - "validators_count": "nb de Validateurs: {{vals_count}}", - "nodes_count": "nb de Nœuds: {{nodes_count}}", + "validator_count": "nb de Validateurs: {{val_count}}", + "node_count": "nb de Nœuds: {{node_count}}", "current_stable_version": "Version Stable Actuelle", "stable_version": "{{stableVersion}}", "nftoken_minter": "Créateur du NFT", diff --git a/public/locales/ja-JP/translations.json b/public/locales/ja-JP/translations.json index 76c396048..953a54eba 100644 --- a/public/locales/ja-JP/translations.json +++ b/public/locales/ja-JP/translations.json @@ -443,8 +443,8 @@ "other_chain_destination": null, "%_of_total_nodes_validators": null, "version_display": "バージョン: {{version}}", - "validators_count": "バリデータの数: {{vals_count}}", - "nodes_count": "ノードの数: {{nodes_count}}", + "validator_count": "バリデータの数: {{val_count}}", + "node_count": "ノードの数: {{node_count}}", "current_stable_version": "現在の安定バージョン", "stable_version": null, "nftoken_minter": "NFT発行者", diff --git a/public/locales/ko-KR/translations.json b/public/locales/ko-KR/translations.json index 73951775e..2094a1fe5 100644 --- a/public/locales/ko-KR/translations.json +++ b/public/locales/ko-KR/translations.json @@ -441,8 +441,8 @@ "other_chain_destination": "다른 체인의 목적지", "%_of_total_nodes_validators": "노드와 검증자의 총 비율", "version_display": "버전: {{version}}", - "validators_count": "검증자 수: {{vals_count}}", - "nodes_count": "노드 수: {{nodes_count}}", + "validator_count": "검증자 수: {{val_count}}", + "node_count": "노드 수: {{node_count}}", "current_stable_version": "현재 안정적인 버전", "stable_version": "{{stableVersion}}", "nftoken_minter": "NFT 발행자", diff --git a/src/containers/Network/BarChartVersion.tsx b/src/containers/Network/BarChartVersion.tsx index b9f63f6c4..b49b0ad13 100644 --- a/src/containers/Network/BarChartVersion.tsx +++ b/src/containers/Network/BarChartVersion.tsx @@ -51,13 +51,13 @@ const CustomTooltip = ({

{t('version_display', { version: label })}

- {t('validators_count', { - vals_count: payload ? payload[0].payload.validatorsCount : 0, + {t('validator_count', { + val_count: payload?.[0]?.payload?.validatorCount ?? 0, })}

- {t('nodes_count', { - nodes_count: payload ? payload[0].payload.nodesCount : 0, + {t('node_count', { + node_count: payload?.[0]?.payload?.nodeCount ?? 0, })}

@@ -162,7 +162,7 @@ const BarChartVersion = (props: Props) => { /> { ))} { - if (!validators) { - return [] - } +interface ValidatorStats { + validatorPercent: number + validatorCount: number +} - let totalVals = 0 - let totalNodes = 0 - interface aggregationTypes { - validatorsCount: number - nodesCount: number - } +interface ValidatorAggregation { + [label: string]: ValidatorStats +} - const aggregation: Record = {} +interface NodeAggregation { + [label: string]: NodeStats +} + +interface DataAggregation extends ValidatorStats, NodeStats { + label: string +} + +export const aggregateValidators = (validators: ValidatorResponse[]) => { + let totalVals = 0 + const aggregation: ValidatorAggregation = {} validators?.forEach((validator) => { if (!validator.signing_key) return const version = validator.server_version totalVals += 1 if (version) { if (!aggregation[version]) { - aggregation[version] = { validatorsCount: 0, nodesCount: 0 } + aggregation[version] = { validatorCount: 0, validatorPercent: 0 } } - aggregation[version].validatorsCount += 1 + aggregation[version].validatorCount += 1 } }) + for (const label of Object.keys(aggregation)) { + aggregation[label].validatorPercent = + totalVals > 0 ? (aggregation[label].validatorCount / totalVals) * 100 : 0 + } + + return aggregation +} +export const aggregateNodes = (nodes: NodeResponse[]) => { + let totalNodes = 0 + const aggregation: NodeAggregation = {} nodes?.forEach((node) => { const { version } = node if (!node.node_public_key) return totalNodes += 1 if (version) { if (!aggregation[version]) { - aggregation[version] = { validatorsCount: 0, nodesCount: 0 } + aggregation[version] = { nodeCount: 0, nodePercent: 0 } } - aggregation[version].nodesCount += 1 + aggregation[version].nodeCount += 1 } }) + for (const label of Object.keys(aggregation)) { + aggregation[label].nodePercent = + totalNodes > 0 ? (aggregation[label].nodeCount / totalNodes) * 100 : 0 + } + + return aggregation +} - return Object.entries(aggregation) - .map(([version, counts]) => ({ - label: version ? version.trim() : 'N/A', - validatorsPercent: - totalVals > 0 ? (counts.validatorsCount * 100) / totalVals : 0, - validatorsCount: counts.validatorsCount, - nodesPercent: totalNodes > 0 ? (counts.nodesCount * 100) / totalNodes : 0, - nodesCount: counts.nodesCount, +export const aggregateData = ( + validatorAggregation: ValidatorAggregation, + nodeAggregation: NodeAggregation, +): DataAggregation[] => { + const combinedAggregation: { [label: string]: ValidatorStats & NodeStats } = + {} + for (const label of Object.keys(validatorAggregation)) { + combinedAggregation[label] = { + validatorPercent: validatorAggregation[label].validatorPercent, + validatorCount: validatorAggregation[label].validatorCount, + nodePercent: 0, + nodeCount: 0, + } + } + + for (const label of Object.keys(nodeAggregation)) { + if (!combinedAggregation[label]) { + combinedAggregation[label] = { + validatorPercent: 0, + validatorCount: 0, + nodePercent: nodeAggregation[label].nodePercent, + nodeCount: nodeAggregation[label].nodeCount, + } + } else { + combinedAggregation[label].nodePercent = + nodeAggregation[label].nodePercent + combinedAggregation[label].nodeCount = nodeAggregation[label].nodeCount + } + } + + return Object.entries(combinedAggregation) + .map(([label, stats]) => ({ + label, + ...stats, })) .sort((a, b) => (isEarlierVersion(a.label, b.label) ? -1 : 1)) } @@ -110,7 +155,9 @@ export const UpgradeStatus = () => { const [vList, setVList] = useState>({}) const [validations, setValidations] = useState([]) const [unlCount, setUnlCount] = useState(0) - const [aggregated, setAggregated] = useState([]) + const [validatorAggregation, setValidatorAggregation] = + useState({}) + const [nodeAggregation, setNodeAggregation] = useState({}) const { t } = useTranslation() const language = useLanguage() const network = useContext(NetworkContext) @@ -145,7 +192,7 @@ export const UpgradeStatus = () => { ) const fetchData = () => { - const validatorsReq = axios + axios .get(`${process.env.VITE_DATA_URL}/validators/${network}`) .then((resp) => resp.data.validators) .then((validators: ValidatorResponse[]) => { @@ -153,16 +200,16 @@ export const UpgradeStatus = () => { validators.forEach((validator) => { newValidatorList[validator.signing_key] = validator }) - setVList(newValidatorList) setUnlCount( validators.filter((validator) => Boolean(validator.unl)).length, ) + setValidatorAggregation(aggregateValidators(validators)) return Object.values(newValidatorList) }) .catch((e) => Log.error(e)) - const nodesReq = axios + axios .get(`${process.env.VITE_DATA_URL}/topology/nodes/${network}`) .then((resp) => resp.data.nodes) .then((allNodes) => { @@ -188,12 +235,11 @@ export const UpgradeStatus = () => { } return 1 }) + + setNodeAggregation(aggregateNodes(nodes)) return nodes }) .catch((e) => Log.error(e)) - Promise.all([validatorsReq, nodesReq]).then(([validators, nodes]) => { - setAggregated(aggregateData(validators, nodes)) - }) } const fetchStableVersion = () => { @@ -250,9 +296,17 @@ export const UpgradeStatus = () => {
-
- -
+ {Object.keys(validatorAggregation).length > 0 || + Object.keys(nodeAggregation).length > 0 ? ( +
+ +
+ ) : ( + + )}
) diff --git a/src/containers/Network/test/upgradeStatus.test.js b/src/containers/Network/test/upgradeStatus.test.js index 79d15a0d4..28d43d480 100644 --- a/src/containers/Network/test/upgradeStatus.test.js +++ b/src/containers/Network/test/upgradeStatus.test.js @@ -1,84 +1,162 @@ -import { aggregateData } from '../UpgradeStatus' +import { mount } from 'enzyme' +import moxios from 'moxios' +import WS from 'jest-websocket-mock' +import { Route } from 'react-router' +import i18n from '../../../i18n/testConfig' +import { Network } from '../index' +import SocketContext from '../../shared/SocketContext' +import MockWsClient from '../../test/mockWsClient' +import { QuickHarness } from '../../test/utils' +import { NETWORK_ROUTE } from '../../App/routes' +import { + aggregateData, + aggregateNodes, + aggregateValidators, +} from '../UpgradeStatus' -describe('UpgradeStatus test functions', () => { - const undefinedValidatorsData = [ - { - ledger_index: 74661353, - ledger_hash: - '613E298A8C0AEB816D16AA61952E0834BBD9B5E5677EA3E9A2413118EE074363', +const undefinedValidatorsData = [ + { + ledger_index: 74661353, + ledger_hash: + '613E298A8C0AEB816D16AA61952E0834BBD9B5E5677EA3E9A2413118EE074363', + }, + { + master_key: 'nHUakYHufAvdx5XqTS2F4Pu7i8fQqDqpKqXN2kUGHhBFcG38GNqL', + signing_key: 'n9M38x7Sf7epp3gaxgcFxEtwkSc4w2ePb1SgfLiz9bVCr5Lvzrm8', + unl: false, + domain: 'gerty.one', + ledger_index: 74554449, + server_version: '1.9.4', + agreement_1hour: { + missed: 936, + total: 936, + score: '0.00000', + incomplete: false, }, - { - master_key: 'nHUakYHufAvdx5XqTS2F4Pu7i8fQqDqpKqXN2kUGHhBFcG38GNqL', - signing_key: 'n9M38x7Sf7epp3gaxgcFxEtwkSc4w2ePb1SgfLiz9bVCr5Lvzrm8', - unl: false, - domain: 'gerty.one', - ledger_index: 74554449, - server_version: '1.9.4', - agreement_1hour: { - missed: 936, - total: 936, - score: '0.00000', - incomplete: false, - }, - agreement_24hour: { - missed: 22338, - total: 22338, - score: '0.00000', - incomplete: false, - }, - agreement_30day: { - missed: 263139, - total: 535427, - score: '0.50854', - incomplete: false, - }, - chain: 'chain.4', - partial: false, + agreement_24hour: { + missed: 22338, + total: 22338, + score: '0.00000', + incomplete: false, }, - ] - - const nodesData = [ - { - node_public_key: 'n9JoeT8XKeBSR8y4D9aDz2PL1DD1j6LQwkRTbH2eFqeRmWYHj2Nw', - networks: 'dev', - complete_ledgers: '22085270-29882772', - ip: '34.208.12.148', - port: 2459, - uptime: 1257336, - version: '1.11.0-rc3', - server_state: 'full', - io_latency_ms: 1, - load_factor_server: '256', - inbound_count: 4, - outbound_count: 9, - lat: '45.82', - long: '-119.73', - country_code: 'US', - country: 'United States', - region: 'Oregon', - region_code: 'OR', - city: 'Boardman', - postal_code: '97818', - timezone: 'America/Los_Angeles', + agreement_30day: { + missed: 263139, + total: 535427, + score: '0.50854', + incomplete: false, }, - ] + chain: 'chain.4', + partial: false, + }, +] - it('aggregateData handle edge case', () => { - expect(aggregateData(undefinedValidatorsData, nodesData)).toEqual([ +const nodesData = [ + { + node_public_key: 'n9JoeT8XKeBSR8y4D9aDz2PL1DD1j6LQwkRTbH2eFqeRmWYHj2Nw', + networks: 'dev', + complete_ledgers: '22085270-29882772', + ip: '34.208.12.148', + port: 2459, + uptime: 1257336, + version: '1.11.0-rc3', + server_state: 'full', + io_latency_ms: 1, + load_factor_server: '256', + inbound_count: 4, + outbound_count: 9, + lat: '45.82', + long: '-119.73', + country_code: 'US', + country: 'United States', + region: 'Oregon', + region_code: 'OR', + city: 'Boardman', + postal_code: '97818', + timezone: 'America/Los_Angeles', + }, +] + +describe('UpgradeStatus test functions', () => { + it('aggregate data works with validators without keys', () => { + const validatorAggregate = aggregateValidators(undefinedValidatorsData) + expect(validatorAggregate).toEqual({ + '1.9.4': { validatorCount: 1, validatorPercent: 100 }, + }) + const nodeAggregate = aggregateNodes(nodesData) + expect(nodeAggregate).toEqual({ + '1.11.0-rc3': { nodeCount: 1, nodePercent: 100 }, + }) + expect(aggregateData(validatorAggregate, nodeAggregate)).toEqual([ { label: '1.9.4', - validatorsCount: 1, - validatorsPercent: 100, - nodesCount: 0, - nodesPercent: 0, + validatorCount: 1, + validatorPercent: 100, + nodeCount: 0, + nodePercent: 0, }, { label: '1.11.0-rc3', - validatorsCount: 0, - validatorsPercent: 0, - nodesCount: 1, - nodesPercent: 100, + validatorCount: 0, + validatorPercent: 0, + nodeCount: 1, + nodePercent: 100, }, ]) }) }) + +describe('UpgradeStatus renders', () => { + let server + let client + const WS_URL = 'ws://localhost:1234' + const createWrapper = () => + mount( + + + } /> + + , + ) + + beforeEach(async () => { + window.ResizeObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), + disconnect: jest.fn(), + })) + server = new WS(WS_URL, { jsonProtocol: true }) + client = new MockWsClient(WS_URL) + await server.connected + moxios.install() + }) + + afterEach(async () => { + moxios.uninstall() + server.close() + client.close() + WS.clean() + }) + + it('renders without crashing', async () => { + const wrapper = createWrapper() + wrapper.unmount() + }) + + it('renders when nodes request errors', async () => { + moxios.stubRequest(`${process.env.VITE_DATA_URL}/validators/main`, { + status: 200, + response: { validators: undefinedValidatorsData }, + }) + moxios.stubRequest(`${process.env.VITE_DATA_URL}/topology/nodes/main`, { + status: 502, + }) + + const wrapper = createWrapper() + wrapper.update() + setTimeout(() => { + wrapper.update() + expect(wrapper.find('.barchart').length).toEqual(1) + }) + wrapper.unmount() + }) +})