Skip to content

Commit

Permalink
Add changeNeuronVisibility into api and services and update i18n and … (
Browse files Browse the repository at this point in the history
#5553)

# Motivation

To have a method in api and services to be able to update
neruonVisibility in the app

# Changes

governance api, services and neurons services has been updated.
Test are updated accordingly
i18n en.json entry and types have been updated.

# Tests

New tests are added to cover changeNeuronVisibility method.

# Todos

- [ ] Add entry to changelog (if necessary).
  • Loading branch information
coskucinkilic authored Oct 1, 2024
1 parent 0521511 commit 009d8af
Show file tree
Hide file tree
Showing 8 changed files with 283 additions and 6 deletions.
5 changes: 5 additions & 0 deletions frontend/src/lib/api-services/governance.api-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
addHotkey,
autoStakeMaturity,
changeNeuronVisibility,
claimOrRefreshNeuron,
disburse,
increaseDissolveDelay,
Expand All @@ -23,6 +24,7 @@ import {
startDissolving,
stopDissolving,
type ApiAutoStakeMaturityParams,
type ApiChangeNeuronVisibilityParams,
type ApiDisburseParams,
type ApiIncreaseDissolveDelayParams,
type ApiManageHotkeyParams,
Expand Down Expand Up @@ -204,4 +206,7 @@ export const governanceApiService = {
stopDissolving(params: ApiManageNeuronParams) {
return clearCacheAfter(stopDissolving(params));
},
changeNeuronVisibility(params: ApiChangeNeuronVisibilityParams) {
return clearCacheAfter(changeNeuronVisibility(params));
},
};
30 changes: 29 additions & 1 deletion frontend/src/lib/api/governance.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import type {
Topic,
Vote,
} from "@dfinity/nns";
import { GovernanceCanister, type RewardEvent } from "@dfinity/nns";
import {
GovernanceCanister,
NeuronVisibility,
type RewardEvent,
} from "@dfinity/nns";
import type { Principal } from "@dfinity/principal";
import { ledgerCanister as getLedgerCanister } from "./icp-ledger.api";

Expand Down Expand Up @@ -574,3 +578,27 @@ export const governanceCanister = async ({
agent,
};
};
export type ApiChangeNeuronVisibilityParams = ApiCallParams & {
neuronIds: NeuronId[];
visibility: NeuronVisibility;
};

export const changeNeuronVisibility = async ({
neuronIds,
visibility,
identity,
}: ApiChangeNeuronVisibilityParams): Promise<void> => {
logWithTimestamp(
`Changing visibility for ${neuronIds.length} neurons. IDs: ${neuronIds.join(", ")}. New visibility: ${visibility}`
);

const { canister } = await governanceCanister({ identity });

await Promise.all(
neuronIds.map((neuronId) => canister.setVisibility(neuronId, visibility))
);

logWithTimestamp(
`Visibility change complete for ${neuronIds.length} neurons. IDs: ${neuronIds.join(", ")}. New visibility: ${visibility}`
);
};
4 changes: 3 additions & 1 deletion frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,9 @@
"amount_maturity": "$amount maturity",
"created": "Date Created",
"neuron_state_tooltip": "Your neuron can be locked, unlocked or dissolving. In a locked state, it is accruing age bonus, while its dissolve delay stays constant. If the neuron is in a dissolving state, its age bonus is set to 0, while dissolve delay decreases with time. After dissolve delay reaches 0, the neuron is unlocked, and $token held in it can be sent to any $token account.",
"dissolve_delay_tooltip": "Dissolve delay is the minimum amount of time you have to wait for the neuron to unlock, and $token to be available again. If your neuron is dissolving, your $token will be available in $duration."
"dissolve_delay_tooltip": "Dissolve delay is the minimum amount of time you have to wait for the neuron to unlock, and $token to be available again. If your neuron is dissolving, your $token will be available in $duration.",
"change_neuron_visibility_partial_failure": "$failedCount out of $totalCount neurons have failed to update their visibility, please try again later. You can always change neuron visibility under \"Advanced Details & Settings\".",
"change_neuron_visibility_failure": "Your neurons have failed to update their visibility, please try again later. You can always change neuron visibility under \"Advanced Details & Settings\"."
},
"sns_launchpad": {
"header": "Launchpad",
Expand Down
59 changes: 58 additions & 1 deletion frontend/src/lib/services/neurons.services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,12 @@ import {
} from "$lib/utils/neuron.utils";
import { numberToE8s } from "$lib/utils/token.utils";
import { AnonymousIdentity, type Identity } from "@dfinity/agent";
import { Topic, type NeuronId, type NeuronInfo } from "@dfinity/nns";
import {
NeuronVisibility,
Topic,
type NeuronId,
type NeuronInfo,
} from "@dfinity/nns";
import { Principal } from "@dfinity/principal";
import { get } from "svelte/store";
import { getAuthenticatedIdentity } from "./auth.services";
Expand Down Expand Up @@ -1008,3 +1013,55 @@ export const makeDummyProposals = async (neuronId: NeuronId): Promise<void> => {
toastsShow(mapNeuronErrorToToastMessage(error));
}
};

export const changeNeuronVisibility = async ({
neurons,
makePublic,
}: {
neurons: NeuronInfo[];
makePublic: boolean;
}): Promise<{ success: boolean }> => {
const results = await Promise.all(
neurons.map(async (neuron) => {
try {
const identity: Identity = await getIdentityOfControllerByNeuronId(
neuron.neuronId
);
await governanceApiService.changeNeuronVisibility({
neuronIds: [neuron.neuronId],
identity,
visibility: makePublic
? NeuronVisibility.Public
: NeuronVisibility.Private,
});
await getAndLoadNeuron(neuron.neuronId);
return true;
} catch (err) {
console.error(
`Failed to change visibility for neuron ${neuron.neuronId}:`,
err
);
return false;
}
})
);

const failedCount = results.filter((result) => result === false).length;

if (failedCount === 0) {
return { success: true };
} else if (failedCount < neurons.length) {
toastsError({
labelKey: "neuron_detail.change_neuron_visibility_partial_failure",
substitutions: {
$failedCount: failedCount.toString(),
$totalCount: neurons.length.toString(),
},
});
} else {
toastsError({
labelKey: "neuron_detail.change_neuron_visibility_failure",
});
}
return { success: false };
};
2 changes: 2 additions & 0 deletions frontend/src/lib/types/i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -714,6 +714,8 @@ interface I18nNeuron_detail {
created: string;
neuron_state_tooltip: string;
dissolve_delay_tooltip: string;
change_neuron_visibility_partial_failure: string;
change_neuron_visibility_failure: string;
}

interface I18nSns_launchpad {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
} from "$tests/mocks/neurons.mock";
import { mockRewardEvent } from "$tests/mocks/nns-reward-event.mock";
import { mockProposalInfo } from "$tests/mocks/proposal.mock";
import { Topic, Vote } from "@dfinity/nns";
import { NeuronVisibility, Topic, Vote } from "@dfinity/nns";
import type { RewardEvent } from "@dfinity/nns/dist/candid/governance";
import type { Mock } from "vitest";

Expand Down Expand Up @@ -1086,4 +1086,34 @@ describe("neurons api-service", () => {
});
});
});

describe("changeNeuronVisibility", () => {
const params: api.ApiChangeNeuronVisibilityParams = {
neuronIds: [neuronId],
identity: mockIdentity,
visibility: NeuronVisibility.Public,
};

it("should call changeNeuronVisibility api", () => {
governanceApiService.changeNeuronVisibility(params);
expect(api.changeNeuronVisibility).toHaveBeenCalledWith(params);
expect(api.changeNeuronVisibility).toHaveBeenCalledTimes(1);
});

it("should invalidate the cache", async () => {
await shouldInvalidateCache({
apiFunc: api.changeNeuronVisibility,
apiServiceFunc: governanceApiService.changeNeuronVisibility,
params,
});
});

it("should invalidate the cache on failure", async () => {
await shouldInvalidateCacheOnFailure({
apiFunc: api.changeNeuronVisibility,
apiServiceFunc: governanceApiService.changeNeuronVisibility,
params,
});
});
});
});
50 changes: 49 additions & 1 deletion frontend/src/tests/lib/api/governance.api.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
addHotkey,
autoStakeMaturity,
changeNeuronVisibility,
disburse,
increaseDissolveDelay,
joinCommunityFund,
Expand All @@ -27,7 +28,12 @@ import { mockMainAccount } from "$tests/mocks/icp-accounts.store.mock";
import { mockNeuron } from "$tests/mocks/neurons.mock";
import type { Agent } from "@dfinity/agent";
import { LedgerCanister } from "@dfinity/ledger-icp";
import { GovernanceCanister, Topic, Vote } from "@dfinity/nns";
import {
GovernanceCanister,
NeuronVisibility,
Topic,
Vote,
} from "@dfinity/nns";
import { Principal } from "@dfinity/principal";
import { mock } from "vitest-mock-extended";

Expand Down Expand Up @@ -729,4 +735,46 @@ describe("neurons-api", () => {
);
});
});

describe("changeNeuronVisibility", () => {
const neuronIds = [10n, 20n, 30n];
const visibility = NeuronVisibility.Public;

it("changes visibility for multiple neurons", async () => {
expect(mockGovernanceCanister.setVisibility).not.toHaveBeenCalled();

mockGovernanceCanister.setVisibility.mockResolvedValue(undefined);

await changeNeuronVisibility({
neuronIds,
visibility,
identity: mockIdentity,
});

expect(mockGovernanceCanister.setVisibility).toHaveBeenCalledTimes(3);
neuronIds.forEach((neuronId) => {
expect(mockGovernanceCanister.setVisibility).toHaveBeenCalledWith(
neuronId,
visibility
);
});
});

it("throws error when changing visibility fails", async () => {
expect(mockGovernanceCanister.setVisibility).not.toHaveBeenCalled();

const error = new Error("Visibility change failed");
mockGovernanceCanister.setVisibility.mockRejectedValue(error);

await expect(
changeNeuronVisibility({
neuronIds,
visibility,
identity: mockIdentity,
})
).rejects.toThrow(error);

expect(mockGovernanceCanister.setVisibility).toHaveBeenCalledTimes(3);
});
});
});
Loading

0 comments on commit 009d8af

Please sign in to comment.