Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Add Nova Analytics #1285

Merged
merged 29 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
5073c25
feat: add tokens held daily by output type chart
brancoder Mar 8, 2024
abb0356
feat: add addresses with balance chart
brancoder Mar 8, 2024
2da5293
feat: add daily active addresses
brancoder Mar 8, 2024
dbcef2a
feat: add daily sent tokens chart
brancoder Mar 8, 2024
c564d3b
feat: add anchor activity daily chart
brancoder Mar 8, 2024
970471d
feat: add nft activity daily chart
brancoder Mar 8, 2024
9f42a20
Merge branch 'dev' into feat/add-daily-statistics
brancoder Mar 8, 2024
644088d
feat: add Account daily activity chart
brancoder Mar 8, 2024
e59fe24
feat: add foundry daily activity chart
brancoder Mar 8, 2024
89fcc7c
feat: add delegation activity daily chart
brancoder Mar 11, 2024
4432563
fix: delegation query
brancoder Mar 11, 2024
c90faf2
feat: add validators activity daily chart
brancoder Mar 11, 2024
441f698
feat: add delegators daily statistics chart
brancoder Mar 11, 2024
24c8e98
feat: add delegations daily activity chart
brancoder Mar 11, 2024
1bab456
feat: add staking daily activity chart
brancoder Mar 11, 2024
362d2d2
feat: add tokens with UC daily activity chart
brancoder Mar 11, 2024
ef516f8
feat: add total outputs with special unlock conditions statistics
brancoder Mar 11, 2024
317d985
feat: add total outputs with special unlock conditions statistics
brancoder Mar 11, 2024
78ae890
feat: add ledger size and total storage deposit charts
brancoder Mar 11, 2024
b25bb6b
feat: add block issuers daily charts
brancoder Mar 11, 2024
442fb71
feat: add mana burned daily chart
brancoder Mar 11, 2024
9f0b801
feat: add nova analytics
brancoder Mar 11, 2024
1a06ffe
Merge branch 'dev' into feat/add-daily-statistics
msarcev Mar 12, 2024
8bad5cd
fix: influx queries
brancoder Mar 13, 2024
d10f707
Merge branch 'feat/add-daily-statistics' of github.com:iotaledger/exp…
brancoder Mar 13, 2024
9f160c1
Merge branch 'dev' into feat/add-daily-statistics
brancoder Mar 13, 2024
f538b62
fix: resolve conflict
brancoder Mar 13, 2024
1409add
Merge branch 'dev' into feat/add-nova-analytics
msarcev Mar 14, 2024
88105ff
feat: Add cron job for analytics
msarcev Mar 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions api/src/models/influx/nova/IInfluxDbCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export interface IInfluxDailyCache {
manaBurnedDaily: Map<DayKey, IManaBurnedDailyInflux>;
}

/**
* The cache for influx analytics.
*/
export interface IInfluxAnalyticsCache {
addressesWithBalance?: string;
nativeTokensCount?: string;
nftsCount?: string;
lockedStorageDeposit?: string;
}

/**
* The helper to initialize empty maps
* @returns The initial cache object
Expand Down
7 changes: 7 additions & 0 deletions api/src/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ export const routes: IRoute[] = [
},
{ path: "/nova/slot/:network/:slotIndex", method: "get", folder: "nova/slot", func: "get" },
{ path: "/nova/epoch/committee/:network/:epochIndex", method: "get", folder: "nova/epoch/committee", func: "get" },
{
path: "/nova/analytics/:network",
method: "get",
folder: "nova/analytics/influx/stats",
func: "get",
sign: true,
},
{
path: "/nova/analytics/daily/:network",
method: "get",
Expand Down
45 changes: 45 additions & 0 deletions api/src/routes/nova/analytics/influx/stats/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { ServiceFactory } from "../../../../../factories/serviceFactory";
import { INetworkBoundGetRequest } from "../../../../../models/api/INetworkBoundGetRequest";
import { IResponse } from "../../../../../models/api/nova/IResponse";
import { IAnalyticStats } from "../../../../../models/api/stats/IAnalyticStats";
import { IConfiguration } from "../../../../../models/configuration/IConfiguration";
import { NOVA } from "../../../../../models/db/protocolVersion";
import { NetworkService } from "../../../../../services/networkService";
import { InfluxServiceNova } from "../../../../../services/nova/influx/influxServiceNova";
import { ValidationHelper } from "../../../../../utils/validationHelper";

/**
* The response with the current cached data.
*/
type IAnalyticStatsReponse = IAnalyticStats & IResponse;

/**
* Get influx cached analytic stats for the requested network.
* @param _ The configuration.
* @param request The request.
* @returns The response.
*/
export async function get(_: IConfiguration, request: INetworkBoundGetRequest): Promise<IAnalyticStatsReponse> {
const networkService = ServiceFactory.get<NetworkService>("network");
const networks = networkService.networkNames();
const networkConfig = networkService.get(request.network);
ValidationHelper.oneOf(request.network, networks, "network");

if (networkConfig.protocolVersion !== NOVA) {
return {};
}

const influxService = ServiceFactory.get<InfluxServiceNova>(`influxdb-${request.network}`);

return influxService
? {
nativeTokens: influxService.nativeTokensCount,
nfts: influxService.nftsCount,
totalAddresses: influxService.addressesWithBalance,
dailyAddresses: "",
lockedStorageDeposit: influxService.lockedStorageDeposit,
}
: {
error: `Influx service not found for this network: ${request.network}`,
};
}
2 changes: 1 addition & 1 deletion api/src/services/influx/influxClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export abstract class InfluxDbClient {
* @returns Current datetime as INanoDate.
*/
protected getToNanoDate(): INanoDate {
return toNanoDate((moment().startOf("day").valueOf() * NANOSECONDS_IN_MILLISECOND).toString());
return toNanoDate((moment().valueOf() * NANOSECONDS_IN_MILLISECOND).toString());
}

/**
Expand Down
26 changes: 26 additions & 0 deletions api/src/services/nova/influx/influxQueries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -453,3 +453,29 @@ export const MANA_BURN_DAILY_QUERY = {
GROUP BY time(1d) fill(null)
`,
};

/* ANALYTIC QUERIES */

export const ADDRESSES_WITH_BALANCE_TOTAL_QUERY = `
SELECT
last("address_with_balance_count") AS "addressesWithBalance"
FROM "iota_addresses";
`;

export const NATIVE_TOKENS_STAT_TOTAL_QUERY = `
SELECT
last("foundry_count") AS "nativeTokensCount"
FROM "iota_ledger_outputs";
`;

export const NFT_STAT_TOTAL_QUERY = `
SELECT
last("nft_count") AS "nftsCount"
FROM "iota_ledger_outputs";
`;

export const STORAGE_DEPOSIT_TOTAL_QUERY = `
SELECT
last("total_storage_deposit_amount") AS "lockedStorageDeposit"
FROM "iota_ledger_size";
`;
85 changes: 84 additions & 1 deletion api/src/services/nova/influx/influxServiceNova.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import cron from "node-cron";
import {
ACCOUNT_ACTIVITY_DAILY_QUERY,
ADDRESSES_WITH_BALANCE_DAILY_QUERY,
ADDRESSES_WITH_BALANCE_TOTAL_QUERY,
ANCHOR_ACTIVITY_DAILY_QUERY,
BLOCK_DAILY_QUERY,
BLOCK_ISSUERS_DAILY_QUERY,
Expand All @@ -12,10 +13,13 @@ import {
FOUNDRY_ACTIVITY_DAILY_QUERY,
LEDGER_SIZE_DAILY_QUERY,
MANA_BURN_DAILY_QUERY,
NATIVE_TOKENS_STAT_TOTAL_QUERY,
NFT_ACTIVITY_DAILY_QUERY,
NFT_STAT_TOTAL_QUERY,
OUTPUTS_DAILY_QUERY,
STAKING_ACTIVITY_DAILY_QUERY,
STORAGE_DEPOSIT_DAILY_QUERY,
STORAGE_DEPOSIT_TOTAL_QUERY,
TOKENS_HELD_BY_OUTPUTS_DAILY_QUERY,
TOKENS_HELD_WITH_UC_DAILY_QUERY,
TOKENS_TRANSFERRED_DAILY_QUERY,
Expand All @@ -26,7 +30,7 @@ import {
} from "./influxQueries";
import logger from "../../../logger";
import { INetwork } from "../../../models/db/INetwork";
import { IInfluxDailyCache, initializeEmptyDailyCache } from "../../../models/influx/nova/IInfluxDbCache";
import { IInfluxAnalyticsCache, IInfluxDailyCache, initializeEmptyDailyCache } from "../../../models/influx/nova/IInfluxDbCache";
import {
IAccountActivityDailyInflux,
IActiveAddressesDailyInflux,
Expand All @@ -51,6 +55,7 @@ import {
IUnlockConditionsPerTypeDailyInflux,
IValidatorsActivityDailyInflux,
} from "../../../models/influx/nova/IInfluxTimedEntries";
import { ITimedEntry } from "../../../models/influx/types";
import { InfluxDbClient } from "../../influx/influxClient";

/**
Expand All @@ -59,6 +64,12 @@ import { InfluxDbClient } from "../../influx/influxClient";
*/
const COLLECT_GRAPHS_DATA_CRON = "55 59 * * * *";

/**
* The collect analyitics data interval cron expression.
* Every hour at 58 min 55 sec
*/
const COLLECT_ANALYTICS_DATA_CRON = "55 58 * * * *";

export class InfluxServiceNova extends InfluxDbClient {
/**
* The InfluxDb Client.
Expand All @@ -70,6 +81,11 @@ export class InfluxServiceNova extends InfluxDbClient {
*/
protected readonly _dailyCache: IInfluxDailyCache;

/**
* The current influx analytics cache instance.
*/
protected readonly _analyticsCache: IInfluxAnalyticsCache;

/**
* The network in context for this client.
*/
Expand All @@ -78,6 +94,7 @@ export class InfluxServiceNova extends InfluxDbClient {
constructor(network: INetwork) {
super(network);
this._dailyCache = initializeEmptyDailyCache();
this._analyticsCache = {};
}

public get blocksDaily() {
Expand Down Expand Up @@ -168,18 +185,41 @@ export class InfluxServiceNova extends InfluxDbClient {
return this.mapToSortedValuesArray(this._dailyCache.manaBurnedDaily);
}

public get addressesWithBalance() {
return this._analyticsCache.addressesWithBalance;
}

public get nativeTokensCount() {
return this._analyticsCache.nativeTokensCount;
}

public get nftsCount() {
return this._analyticsCache.nftsCount;
}

public get lockedStorageDeposit() {
return this._analyticsCache.lockedStorageDeposit;
}

protected setupDataCollection() {
const network = this._network.network;
logger.verbose(`[InfluxNova] Setting up data collection for (${network}).`);

// eslint-disable-next-line no-void
void this.collectGraphsDaily();
// eslint-disable-next-line no-void
void this.collectAnalytics();

if (this._client) {
cron.schedule(COLLECT_GRAPHS_DATA_CRON, async () => {
// eslint-disable-next-line no-void
void this.collectGraphsDaily();
});

cron.schedule(COLLECT_ANALYTICS_DATA_CRON, async () => {
// eslint-disable-next-line no-void
void this.collectAnalytics();
});
}
}

Expand Down Expand Up @@ -276,4 +316,47 @@ export class InfluxServiceNova extends InfluxDbClient {
);
this.updateCacheEntry<IManaBurnedDailyInflux>(MANA_BURN_DAILY_QUERY, this._dailyCache.manaBurnedDaily, "Mana Burned Daily");
}

/**
* Performs the InfluxDb analytics data collection.
* Populates the analyticsCache.
*/
private async collectAnalytics() {
logger.verbose(`[InfluxNova] Collecting analytic stats for "${this._network.network}"`);
try {
for (const update of await this.queryInflux<ITimedEntry & { addressesWithBalance: string }>(
ADDRESSES_WITH_BALANCE_TOTAL_QUERY,
null,
this.getToNanoDate(),
)) {
this._analyticsCache.addressesWithBalance = update.addressesWithBalance;
}

for (const update of await this.queryInflux<ITimedEntry & { nativeTokensCount: string }>(
NATIVE_TOKENS_STAT_TOTAL_QUERY,
null,
this.getToNanoDate(),
)) {
this._analyticsCache.nativeTokensCount = update.nativeTokensCount;
}

for (const update of await this.queryInflux<ITimedEntry & { nftsCount: string }>(
NFT_STAT_TOTAL_QUERY,
null,
this.getToNanoDate(),
)) {
this._analyticsCache.nftsCount = update.nftsCount;
}

for (const update of await this.queryInflux<ITimedEntry & { lockedStorageDeposit: string }>(
STORAGE_DEPOSIT_TOTAL_QUERY,
null,
this.getToNanoDate(),
)) {
this._analyticsCache.lockedStorageDeposit = update.lockedStorageDeposit;
}
} catch (err) {
logger.warn(`[InfluxNova] Failed refreshing analytics for "${this._network.network}"! Cause: ${err}`);
}
}
}
15 changes: 15 additions & 0 deletions client/src/app/components/nova/statistics/InfluxChartsTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import StackedLineChart from "../../stardust/statistics/charts/StackedLineChart"
import LineChart from "../../stardust/statistics/charts/LineChart";
import BarChart from "../../stardust/statistics/charts/BarChart";
import StackedBarChart from "../../stardust/statistics/charts/StackedBarChart";
import ChartInfoPanel from "~/app/components/stardust/statistics/ChartInfoPanel";
import { formatAmount } from "~/helpers/stardust/valueFormatHelper";
import { useNetworkInfoNova } from "~/helpers/nova/networkInfo";

export const InfluxChartsTab: React.FC = () => {
const { tokenInfo } = useNetworkInfoNova((s) => s.networkInfo);

const {
blocksDaily,
transactionsDaily,
Expand All @@ -25,8 +30,13 @@ export const InfluxChartsTab: React.FC = () => {
tokensHeldWithUnlockConditionDaily,
storageDeposit,
manaBurnedDaily,
analyticStats,
} = useChartsState();

const lockedStorageDepositValue = analyticStats?.lockedStorageDeposit
? formatAmount(analyticStats.lockedStorageDeposit, tokenInfo)
: "-";

const ids = idGenerator();

return (
Expand Down Expand Up @@ -88,6 +98,11 @@ export const InfluxChartsTab: React.FC = () => {
<h2>Addresses and Tokens</h2>
<Modal icon="info" data={graphMessages.addressesAndTokens} />
</div>
<div className="row info-panel">
<ChartInfoPanel label="Native tokens minted" value={analyticStats?.nativeTokens ?? "-"} />
<ChartInfoPanel label="NFTs minted" value={analyticStats?.nfts ?? "-"} />
<ChartInfoPanel label="Locked storage deposit" value={lockedStorageDepositValue} />
</div>
<div className="row statistics-row margin-b-s">
<StackedLineChart
chartId={ids.next().value}
Expand Down
16 changes: 16 additions & 0 deletions client/src/helpers/nova/hooks/useChartsState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { ServiceFactory } from "~factories/serviceFactory";
import { NOVA } from "~models/config/protocolVersion";
import { useNetworkInfoNova } from "../networkInfo";
import { DataPoint, IStatisticsGraphsData, mapDailyStatsToGraphsData } from "../../nova/statisticsUtils";
import { IAnalyticStats } from "~/models/api/nova/stats/IAnalyticStats";

/**
* State holder for Statistics page chart section.
Expand Down Expand Up @@ -32,6 +33,7 @@ export function useChartsState(): {
ledgerSize: DataPoint[];
storageDeposit: DataPoint[];
manaBurnedDaily: DataPoint[];
analyticStats: IAnalyticStats | null;
} {
const { name: network } = useNetworkInfoNova((s) => s.networkInfo);
const [apiClient] = useState(ServiceFactory.get<NovaApiClient>(`api-client-${NOVA}`));
Expand All @@ -58,6 +60,8 @@ export function useChartsState(): {
const [storageDeposit, setStorageDeposit] = useState<DataPoint[]>([]);
const [manaBurnedDaily, setManaBurnedDaily] = useState<DataPoint[]>([]);

const [analyticStats, setAnalyticStats] = useState<IAnalyticStats | null>(null);

useEffect(() => {
apiClient
.influxAnalytics({ network })
Expand Down Expand Up @@ -92,6 +96,17 @@ export function useChartsState(): {
}
})
.catch((e) => console.error("Influx analytics fetch failed", e));

apiClient
.chronicleAnalytics({ network })
.then((analytics) => {
if (!analytics.error && analytics) {
setAnalyticStats(analytics);
} else {
console.error("Fetching chronicle stats failed", analytics.error);
}
})
.catch((e) => console.error("Chronicle analytics fetch failed", e));
}, [network]);

return {
Expand All @@ -117,5 +132,6 @@ export function useChartsState(): {
ledgerSize,
storageDeposit,
manaBurnedDaily,
analyticStats,
};
}
13 changes: 13 additions & 0 deletions client/src/models/api/nova/stats/IAnalyticStats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/** Chronicle analytics [nova] */
import { IResponse } from "../IResponse";

/**
* The chronicle analytics.
*/
export interface IAnalyticStats extends IResponse {
nativeTokens?: string;
nfts?: string;
totalAddresses?: string;
dailyAddresses?: string;
lockedStorageDeposit?: string;
}
Loading
Loading