Skip to content

Commit

Permalink
Merge branch 'main' into yhabib/housekeeping/fillTokensStoreFromAggre…
Browse files Browse the repository at this point in the history
…gatorData
  • Loading branch information
yhabib authored Dec 6, 2024
2 parents 9a91324 + ac72b1a commit 5b96c59
Show file tree
Hide file tree
Showing 3 changed files with 267 additions and 1 deletion.
21 changes: 21 additions & 0 deletions frontend/src/lib/services/export-data.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,29 @@ import { getTransactions } from "$lib/api/icp-index.api";
import type { Account } from "$lib/types/account";
import { SignIdentity } from "@dfinity/agent";
import type { TransactionWithId } from "@dfinity/ledger-icp";
import type { NeuronInfo } from "@dfinity/nns";
import { isNullish } from "@dfinity/utils";

type TransactionEntity =
| {
identifier: string;
balance: bigint;
type: "account";
originalData: Account;
}
| {
identifier: string;
balance: bigint;
type: "neuron";
originalData: NeuronInfo;
};

export type TransactionResults = {
entity: TransactionEntity;
transactions: TransactionWithId[];
error?: string;
}[];

export type TransactionsAndAccounts = {
account: Account;
transactions: TransactionWithId[];
Expand Down
127 changes: 126 additions & 1 deletion frontend/src/lib/utils/export-to-csv.utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,14 @@
import { isNullish } from "@dfinity/utils";
import type { TransactionResults } from "$lib/services/export-data.services";
import {
nanoSecondsToDateTime,
nowInBigIntNanoSeconds,
} from "$lib/utils/date.utils";
import { replacePlaceholders } from "$lib/utils/i18n.utils";
import { mapIcpTransactionToReport } from "$lib/utils/icp-transactions.utils";
import { formatTokenV2 } from "$lib/utils/token.utils";
import { transactionName } from "$lib/utils/transactions.utils";
import type { Principal } from "@dfinity/principal";
import { ICPToken, TokenAmountV2, isNullish, nonNullish } from "@dfinity/utils";

type Metadata = {
label: string;
Expand Down Expand Up @@ -241,3 +251,118 @@ export const generateCsvFileToSave = async <T>({
);
}
};

export type TransactionsCsvData = {
id: string;
project: string;
symbol: string;
to: string | undefined;
from: string | undefined;
type: string;
amount: string;
timestamp: string;
};

export const buildTransactionsDatasets = ({
transactions,
i18n,
principal,
neuronAccounts,
swapCanisterAccounts,
}: {
transactions: TransactionResults;
i18n: I18n;
principal: Principal;
neuronAccounts: Set<string>;
swapCanisterAccounts: Set<string>;
}): CsvDataset<TransactionsCsvData>[] => {
return transactions.map(({ entity, transactions }) => {
const accountIdentifier = entity.identifier;
const amount = TokenAmountV2.fromUlps({
amount: entity.balance,
token: ICPToken,
});

const metadata = [
{
label: i18n.export_csv_neurons.account_id,
value: accountIdentifier,
},
];

if (entity.type === "account") {
metadata.push({
label: i18n.export_csv_neurons.account_name,
value: entity.originalData.name ?? "Main",
});
}

if (entity.type === "neuron") {
metadata.push({
label: i18n.export_csv_neurons.neuron_id,
value: entity.originalData.neuronId.toString(),
});
}

metadata.push({
label: replacePlaceholders(i18n.export_csv_neurons.balance, {
$tokenSymbol: ICPToken.symbol,
}),
value: formatTokenV2({
value: amount,
detailed: true,
}),
});

metadata.push(
{
label: i18n.export_csv_neurons.controller_id,
value: principal.toText() ?? i18n.core.not_applicable,
},
{
label: i18n.export_csv_neurons.numer_of_transactions,
value: transactions.length.toString(),
},
{
label: i18n.export_csv_neurons.date_label,
value: nanoSecondsToDateTime(nowInBigIntNanoSeconds()),
}
);

return {
metadata,
data: transactions.map((transaction) => {
const {
to,
from,
type,
tokenAmount,
timestampNanos,
transactionDirection,
} = mapIcpTransactionToReport({
accountIdentifier,
transaction,
neuronAccounts,
swapCanisterAccounts,
});

const sign = transactionDirection === "credit" ? "+" : "-";
const amount = formatTokenV2({ value: tokenAmount, detailed: true });
const timestamp = nonNullish(timestampNanos)
? nanoSecondsToDateTime(timestampNanos)
: i18n.core.not_applicable;

return {
id: transaction.id.toString(),
project: ICPToken.name,
symbol: ICPToken.symbol,
to,
from,
type: transactionName({ type, i18n }),
amount: `${sign}${amount}`,
timestamp,
};
}),
};
});
};
120 changes: 120 additions & 0 deletions frontend/src/tests/lib/utils/export-to-csv.utils.spec.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import {
FileSystemAccessError,
buildTransactionsDatasets,
combineDatasetsToCsv,
convertToCsv,
generateCsvFileToSave,
type CsvHeader,
} from "$lib/utils/export-to-csv.utils";
import { mockPrincipal } from "$tests/mocks/auth.store.mock";
import en from "$tests/mocks/i18n.mock";
import { mockMainAccount } from "$tests/mocks/icp-accounts.store.mock";
import { createTransactionWithId } from "$tests/mocks/icp-transactions.mock";
import { mockNeuron } from "$tests/mocks/neurons.mock";

type TestPersonData = { name: string; age: number };
type TestFormulaData = { formula: string; value: number };
Expand Down Expand Up @@ -310,4 +316,118 @@ describe("Export to Csv", () => {
});
});
});

describe("buildTransactionsDatasets", () => {
const transactions = [createTransactionWithId({})];

beforeEach(() => {
const mockDate = new Date("2023-10-14T00:00:00Z");
vi.useFakeTimers();
vi.setSystemTime(mockDate);
});

it("should return an empty array when no transactions are provided", () => {
expect(
buildTransactionsDatasets({
transactions: [],
i18n: en,
neuronAccounts: new Set(),
principal: mockPrincipal,
swapCanisterAccounts: new Set(),
})
).toEqual([]);
});

it("should generate datasets for accounts transactions", () => {
const mockTransactions = [
{
entity: {
identifier: "1",
balance: 100n,
type: "account" as const,
originalData: mockMainAccount,
},
transactions,
},
];

const datasets = buildTransactionsDatasets({
transactions: mockTransactions,
i18n: en,
neuronAccounts: new Set(),
principal: mockPrincipal,
swapCanisterAccounts: new Set(),
});

expect(datasets).toEqual([
{
data: [
{
amount: "+1.00",
from: "d4685b31b51450508aff0331584df7692a84467b680326f5c5f7d30ae711682f",
id: "1234",
project: "Internet Computer",
symbol: "ICP",
timestamp: "Jan 1, 2023 12:00 AM",
to: "d0654c53339c85e0e5fff46a2d800101bc3d896caef34e1a0597426792ff9f32",
type: "Received",
},
],
metadata: [
{
label: "Account ID",
value: "1",
},
{
label: "Account Name",
value: "Main",
},
{
label: "Balance(ICP)",
value: "0.000001",
},
{
label: "Controller Principal ID",
value:
"xlmdg-vkosz-ceopx-7wtgu-g3xmd-koiyc-awqaq-7modz-zf6r6-364rh-oqe",
},
{
label: "Transactions",
value: "1",
},
{
label: "Export Date Time",
value: "Oct 14, 2023 12:00 AM",
},
],
},
]);
});

it("should add neuron metadata when neurons transactions", () => {
const mockTransactions = [
{
entity: {
identifier: "1",
balance: 100n,
type: "neuron" as const,
originalData: mockNeuron,
},
transactions,
},
];
const datasets = buildTransactionsDatasets({
transactions: mockTransactions,
i18n: en,
neuronAccounts: new Set(),
principal: mockPrincipal,
swapCanisterAccounts: new Set(),
});

expect(datasets[0].metadata[1]).toEqual({
label: "Neuron ID",
value: "1",
});
});
});
});

0 comments on commit 5b96c59

Please sign in to comment.