Skip to content

Commit

Permalink
Feat: Use new and fewer endpoints after participation (#1951)
Browse files Browse the repository at this point in the history
# Motivation

Use new and fewer endpoints to load data after participation.

loadSnsSummary loaded data that didn't change after participation.

# Changes

* New sns api function `querySnsLifecycle`.
* Remove `loadSnsSummary`.
* New sns service `loadSnsLifecycle`.
* Use `loadSnsTotalCommitment` and `loadSnsLifecycle` instead of
`loadSnsSummary` after participation.
* New method `updateLifecycle` in `snsQueryStore`.

# Tests

* Test for new sns api function `querySnsLifecycle`.
* Remove tests for `loadSnsSummary`.
* New test for sns service `loadSnsLifecycle`.
* Test new method `updateLifecycle` in `snsQueryStore`.
  • Loading branch information
lmuntaner authored Feb 24, 2023
1 parent 8f5aacf commit dc6c8a6
Show file tree
Hide file tree
Showing 11 changed files with 271 additions and 90 deletions.
32 changes: 31 additions & 1 deletion frontend/src/lib/api/sns.api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { IcrcAccount } from "@dfinity/ledger";
import { Principal } from "@dfinity/principal";
import type {
SnsGetDerivedStateResponse,
SnsGetLifecycleResponse,
SnsNeuron,
SnsNeuronId,
SnsSwapBuyerState,
Expand Down Expand Up @@ -261,12 +262,41 @@ export const querySnsDerivedState = async ({
await getDerivedState({});

logWithTimestamp(
`Getting Sns ${rootCanisterId} swap commitment certified:${certified} done.`
`Getting Sns ${rootCanisterId} swap derived state certified:${certified} done.`
);

return derivedState;
};

export const querySnsLifecycle = async ({
rootCanisterId,
identity,
certified,
}: {
rootCanisterId: QueryRootCanisterId;
identity: Identity;
certified: boolean;
}): Promise<SnsGetLifecycleResponse | undefined> => {
logWithTimestamp(
`Getting Sns ${rootCanisterId} sale lifecycle certified:${certified} call...`
);

const { getLifecycle }: SnsWrapper = await wrapper({
rootCanisterId,
identity,
certified,
});

const lifecycleResponse: SnsGetLifecycleResponse | undefined =
await getLifecycle({});

logWithTimestamp(
`Getting Sns ${rootCanisterId} sale lifecycle certified:${certified} done.`
);

return lifecycleResponse;
};

export const querySnsNeurons = async ({
identity,
rootCanisterId,
Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -698,6 +698,7 @@
"list_swap_commitments": "There was an unexpected error while loading the commitments of all deployed projects.",
"load_swap_commitment": "There was an unexpected error while loading the commitment of the project.",
"load_sale_total_commitments": "There was an unexpected error while loading the commitments of the project.",
"load_sale_lifecycle": "There was an unexpected error while loading the status of the project.",
"load_parameters": "There was an unexpected error while loading the parameters of the project.",
"sns_remove_hotkey": "There was an error removing the hotkey.",
"sns_split_neuron": "There was an error while splitting the neuron.",
Expand Down
19 changes: 3 additions & 16 deletions frontend/src/lib/pages/ProjectDetail.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { AppPath } from "$lib/constants/routes.constants";
import { layoutTitleStore } from "$lib/stores/layout.store";
import {
loadSnsSummary,
loadSnsLifecycle,
loadSnsSwapCommitment,
loadSnsTotalCommitment,
} from "$lib/services/sns.services";
Expand All @@ -31,19 +31,8 @@
$: if (nonNullish(rootCanisterId) && isSignedIn($authStore.identity)) {
loadCommitment(rootCanisterId);
loadTotalCommitments(rootCanisterId);
}
const loadSummary = (rootCanisterId: string) =>
loadSnsSummary({
rootCanisterId,
onError: () => {
// Set to not found
$projectDetailStore.summary = undefined;
goBack();
},
});
const loadCommitment = (rootCanisterId: string) =>
loadSnsSwapCommitment({
rootCanisterId,
Expand All @@ -54,17 +43,15 @@
},
});
const loadTotalCommitments = (rootCanisterId: string) =>
loadSnsTotalCommitment({ rootCanisterId });
const reload = async () => {
if (rootCanisterId === undefined || rootCanisterId === null) {
// We cannot reload data for an undefined rootCanisterd but we silent the error here because it most probably means that the user has already navigated away of the detail route
return;
}
await Promise.all([
loadSummary(rootCanisterId),
loadSnsTotalCommitment({ rootCanisterId }),
loadSnsLifecycle({ rootCanisterId }),
loadCommitment(rootCanisterId),
]);
};
Expand Down
82 changes: 39 additions & 43 deletions frontend/src/lib/services/sns.services.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import {
querySnsDerivedState,
querySnsMetadata,
querySnsLifecycle,
querySnsSwapCommitment,
querySnsSwapCommitments,
querySnsSwapState,
} from "$lib/api/sns.api";
import {
snsQueryStore,
Expand All @@ -12,12 +11,15 @@ import {
} from "$lib/stores/sns.store";
import { toastsError } from "$lib/stores/toasts.store";
import type { SnsSwapCommitment } from "$lib/types/sns";
import type { QuerySnsMetadata, QuerySnsSwapState } from "$lib/types/sns.query";
import { toToastError } from "$lib/utils/error.utils";
import { getSwapCanisterAccount } from "$lib/utils/sns.utils";
import type { AccountIdentifier } from "@dfinity/nns";
import type { Principal } from "@dfinity/principal";
import type { SnsGetDerivedStateResponse } from "@dfinity/sns";
import type {
SnsGetDerivedStateResponse,
SnsGetLifecycleResponse,
} from "@dfinity/sns";
import { fromNullable, nonNullish } from "@dfinity/utils";
import { get } from "svelte/store";
import { getAuthenticatedIdentity } from "./auth.services";
import { queryAndUpdate } from "./utils.services";
Expand Down Expand Up @@ -83,45 +85,6 @@ export const loadSnsSwapCommitments = async (): Promise<void> => {
});
};

/** Combined request: querySnsSummary + querySnsSwapState */
export const loadSnsSummary = async ({
rootCanisterId,
onError,
}: {
rootCanisterId: string;
onError: () => void;
}) =>
queryAndUpdate<
[QuerySnsMetadata | undefined, QuerySnsSwapState | undefined],
unknown
>({
request: ({ certified, identity }) =>
Promise.all([
querySnsMetadata({
rootCanisterId,
identity,
certified,
}),
querySnsSwapState({ rootCanisterId, identity, certified }),
]),
onLoad: ({ response: data }) =>
snsQueryStore.updateData({ data, rootCanisterId }),
onError: ({ error: err, certified, identity }) => {
console.error(err);
if (certified || identity.getPrincipal().isAnonymous()) {
toastsError(
toToastError({
err,
fallbackErrorLabelKey: "error__sns.load_summary",
})
);

onError();
}
},
logMessage: "Syncing Sns summary",
});

export const loadSnsSwapCommitment = async ({
rootCanisterId,
onError,
Expand Down Expand Up @@ -187,6 +150,39 @@ export const loadSnsTotalCommitment = async ({
logMessage: "Syncing Sns swap commitment",
});

export const loadSnsLifecycle = async ({
rootCanisterId,
}: {
rootCanisterId: string;
}) =>
queryAndUpdate<SnsGetLifecycleResponse | undefined, unknown>({
request: ({ certified, identity }) =>
querySnsLifecycle({
rootCanisterId,
identity,
certified,
}),
onLoad: ({ response: lifecycleResponse }) => {
const lifecycle = fromNullable(lifecycleResponse?.lifecycle ?? []);
if (nonNullish(lifecycle)) {
snsQueryStore.updateLifecycle({ lifecycle, rootCanisterId });
}
},
onError: ({ error: err, certified }) => {
console.error(err);

if (certified) {
toastsError(
toToastError({
err,
fallbackErrorLabelKey: "error__sns.load_sale_lifecycle",
})
);
}
},
logMessage: "Syncing Sns lifecycle",
});

export const getSwapAccount = async (
swapCanisterId: Principal
): Promise<AccountIdentifier> => {
Expand Down
45 changes: 43 additions & 2 deletions frontend/src/lib/stores/sns.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { ProposalStatus, type ProposalInfo } from "@dfinity/nns";
import type {
SnsGetDerivedStateResponse,
SnsSwapDerivedState,
SnsSwapLifecycle,
} from "@dfinity/sns";
import { fromNullable, isNullish } from "@dfinity/utils";
import { fromNullable, isNullish, nonNullish } from "@dfinity/utils";
import { derived, writable, type Readable } from "svelte/store";

// ************** Proposals for Launchpad **************
Expand Down Expand Up @@ -86,6 +87,10 @@ export interface SnsQueryStore extends Readable<SnsQueryStoreData> {
derivedState: SnsGetDerivedStateResponse;
rootCanisterId: string;
}) => void;
updateLifecycle: (swap: {
lifecycle: SnsSwapLifecycle;
rootCanisterId: string;
}) => void;
}

/**
Expand Down Expand Up @@ -182,7 +187,7 @@ const initSnsQueryStore = (): SnsQueryStore => {
* Updates only the derived state of a sale.
*
* @param {Object} params
* @param {QuerySnsSwapState} params.swapData new swap data.
* @param {SnsGetDerivedStateResponse} params.derivedState new derived state.
* @param {string} params.rootCanisterId canister id in text format.
*/
updateDerivedState({
Expand Down Expand Up @@ -216,6 +221,42 @@ const initSnsQueryStore = (): SnsQueryStore => {
),
}));
},

/**
* Updates only the lifecycle of a sale.
*
* @param {Object} params
* @param {SnsSwapLifecycle} params.lifecycle new lifecycle.
* @param {string} params.rootCanisterId canister id in text format.
*/
updateLifecycle({
lifecycle,
rootCanisterId,
}: {
lifecycle: SnsSwapLifecycle;
rootCanisterId: string;
}) {
update((data: SnsQueryStoreData) => ({
metadata: data?.metadata ?? [],
swaps: (data?.swaps ?? []).map((swapData): QuerySnsSwapState => {
if (swapData.rootCanisterId === rootCanisterId) {
const swap = fromNullable(swapData.swap);
if (nonNullish(swap)) {
return {
...swapData,
swap: [
{
...swap,
lifecycle,
},
],
};
}
}
return swapData;
}),
}));
},
};
};

Expand Down
1 change: 1 addition & 0 deletions frontend/src/lib/types/i18n.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,7 @@ interface I18nError__sns {
list_swap_commitments: string;
load_swap_commitment: string;
load_sale_total_commitments: string;
load_sale_lifecycle: string;
load_parameters: string;
sns_remove_hotkey: string;
sns_split_neuron: string;
Expand Down
23 changes: 22 additions & 1 deletion frontend/src/tests/lib/api/sns.api.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
increaseStakeNeuron,
queryAllSnsMetadata,
querySnsDerivedState,
querySnsLifecycle,
querySnsMetadata,
querySnsNeuron,
querySnsNeurons,
Expand All @@ -21,7 +22,11 @@ import {
} from "$lib/proxy/api.import.proxy";
import type { HttpAgent } from "@dfinity/agent";
import { LedgerCanister, type SnsWasmCanisterOptions } from "@dfinity/nns";
import type { SnsNeuronId } from "@dfinity/sns";
import {
SnsSwapLifecycle,
type SnsGetLifecycleResponse,
type SnsNeuronId,
} from "@dfinity/sns";
import { arrayOfNumberToUint8Array } from "@dfinity/utils";
import mock from "jest-mock-extended/lib/Mock";
import { mockIdentity, mockPrincipal } from "../../mocks/auth.store.mock";
Expand Down Expand Up @@ -63,10 +68,15 @@ describe("sns-api", () => {
sns_tokens_per_icp: [1],
buyer_total_icp_e8s: [BigInt(1_000_000_000)],
};
const lifecycleResponse: SnsGetLifecycleResponse = {
lifecycle: [SnsSwapLifecycle.Open],
decentralization_sale_open_timestamp_seconds: [BigInt(1)],
};
const notifyParticipationSpy = jest.fn().mockResolvedValue(undefined);
const mockUserCommitment = createBuyersState(BigInt(100_000_000));
const getUserCommitmentSpy = jest.fn().mockResolvedValue(mockUserCommitment);
const getDerivedStateSpy = jest.fn().mockResolvedValue(derivedState);
const getLifecycleSpy = jest.fn().mockResolvedValue(lifecycleResponse);
const ledgerCanisterMock = mock<LedgerCanister>();
const queryNeuronsSpy = jest.fn().mockResolvedValue([mockSnsNeuron]);
const getNeuronSpy = jest.fn().mockResolvedValue(mockSnsNeuron);
Expand Down Expand Up @@ -105,6 +115,7 @@ describe("sns-api", () => {
queryNeuron: queryNeuronSpy,
increaseStakeNeuron: increaseStakeNeuronSpy,
getDerivedState: getDerivedStateSpy,
getLifecycle: getLifecycleSpy,
})
);
});
Expand Down Expand Up @@ -180,6 +191,16 @@ describe("sns-api", () => {
expect(receivedData).toEqual(derivedState);
});

it("should return lifecycle state", async () => {
const receivedData = await querySnsLifecycle({
rootCanisterId: rootCanisterIdMock.toText(),
identity: mockIdentity,
certified: false,
});
expect(getLifecycleSpy).toBeCalled();
expect(receivedData).toEqual(lifecycleResponse);
});

it("should query sns neurons", async () => {
const neurons = await querySnsNeurons({
identity: mockIdentity,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {

jest.mock("$lib/services/sns.services", () => {
return {
loadSnsSummary: jest.fn().mockResolvedValue(Promise.resolve()),
loadSnsSwapStateStore: jest.fn().mockResolvedValue(Promise.resolve()),
};
});
Expand Down
Loading

0 comments on commit dc6c8a6

Please sign in to comment.