Skip to content

Commit

Permalink
NNS1-3504: Split actionable-proposals.derived.ts (#6127)
Browse files Browse the repository at this point in the history
# Motivation

We want to sort the selectable universes based on their number of
actionable proposals.
For this `selectable-universes.derived.ts` needs to depend on
`actionableProposalCountStore` from `actionable-proposals.derived.ts`.
But this causes a circular dependency because of other derived stores in
`actionable-proposals.derived.ts` which already depend on
`selectable-universes.derived.ts`.

# Changes

1. Move the derived stores in `actionable-proposals.derived.ts` that
depend on `selectable-universes.derived.ts` to a new file named
`actionable-universes.derived.ts`.
2. Update imports.

# Tests

1. Move tests for the moved stores to a corresponding test file.

# Todos

- [ ] Add entry to changelog (if necessary).
not necessary
  • Loading branch information
dskloetd authored Jan 9, 2025
1 parent 8e3c2e6 commit d7aa761
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 186 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
import SkeletonDetails from "$lib/components/ui/SkeletonDetails.svelte";
import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
import { AppPath } from "$lib/constants/routes.constants";
import {
actionableProposalsActiveStore,
actionableProposalsNavigationIdsStore,
} from "$lib/derived/actionable-proposals.derived";
import { actionableProposalsActiveStore } from "$lib/derived/actionable-proposals.derived";
import { actionableProposalsNavigationIdsStore } from "$lib/derived/actionable-universes.derived";
import { pageStore } from "$lib/derived/page.derived";
import { filteredProposals } from "$lib/derived/proposals.derived";
import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import {
actionableSnsProposalsByUniverseStore,
type ActionableSnsProposalsByUniverseData,
} from "$lib/derived/actionable-proposals.derived";
} from "$lib/derived/actionable-universes.derived";
let actionableUniverses: ActionableSnsProposalsByUniverseData[] = [];
$: actionableUniverses = $actionableSnsProposalsByUniverseStore.filter(
Expand Down
54 changes: 1 addition & 53 deletions frontend/src/lib/derived/actionable-proposals.derived.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,16 @@ import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
import { AppPath } from "$lib/constants/routes.constants";
import { authSignedInStore } from "$lib/derived/auth.derived";
import { pageStore } from "$lib/derived/page.derived";
import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived";
import { snsProjectsCommittedStore } from "$lib/derived/sns/sns-projects.derived";
import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store";
import { actionableProposalsSegmentStore } from "$lib/stores/actionable-proposals-segment.store";
import {
actionableSnsProposalsStore,
failedActionableSnsesStore,
} from "$lib/stores/actionable-sns-proposals.store";
import type { ProposalsNavigationId } from "$lib/types/proposals";
import type { Universe } from "$lib/types/universe";
import { isSelectedPath } from "$lib/utils/navigation.utils";
import { mapEntries } from "$lib/utils/utils";
import type { SnsProposalData } from "@dfinity/sns";
import { fromDefinedNullable, isNullish, nonNullish } from "@dfinity/utils";
import { isNullish, nonNullish } from "@dfinity/utils";
import { derived, type Readable } from "svelte/store";

export interface ActionableProposalCountData {
Expand Down Expand Up @@ -65,28 +61,6 @@ export const actionableProposalTotalCountStore: Readable<number> = derived(
Object.values(map).reduce((acc: number, count) => acc + (count ?? 0), 0)
);

export interface ActionableSnsProposalsByUniverseData {
universe: Universe;
proposals: SnsProposalData[];
}

/** A store that contains sns universes with actionable support and their actionable proposals
* in the same order as they are displayed in the UI. */
export const actionableSnsProposalsByUniverseStore: Readable<
Array<ActionableSnsProposalsByUniverseData>
> = derived(
[selectableUniversesStore, actionableSnsProposalsStore],
([universes, actionableSnsProposals]) =>
universes
.filter(({ canisterId }) =>
nonNullish(actionableSnsProposals[canisterId])
)
.map((universe) => ({
universe,
proposals: actionableSnsProposals[universe.canisterId].proposals,
}))
);

/** A store that returns true when all ‘Actionable Proposals’ have been loaded.
*/
export const actionableProposalsLoadedStore: Readable<boolean> = derived(
Expand All @@ -103,29 +77,3 @@ export const actionableProposalsLoadedStore: Readable<boolean> = derived(
committedSnsProjects.length ===
Object.keys(snsProposals).length + failedSnses.length
);

// Generate list of ProposalsNavigationId using universes to provide correct order
// of proposals in the UI.
export const actionableProposalsNavigationIdsStore: Readable<
Array<ProposalsNavigationId>
> = derived(
[
selectableUniversesStore,
actionableNnsProposalsStore,
actionableSnsProposalsStore,
],
([universes, nnsProposals, actionableSnsProposals]) =>
universes
.map(({ canisterId }) =>
canisterId === OWN_CANISTER_ID_TEXT
? (nnsProposals.proposals ?? []).map(({ id }) => ({
universe: OWN_CANISTER_ID_TEXT,
proposalId: id as bigint,
}))
: (actionableSnsProposals[canisterId]?.proposals ?? []).map((aa) => ({
universe: canisterId,
proposalId: fromDefinedNullable(aa.id).id,
}))
)
.flatMap((ids) => (nonNullish(ids) ? ids : []))
);
58 changes: 58 additions & 0 deletions frontend/src/lib/derived/actionable-universes.derived.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { OWN_CANISTER_ID_TEXT } from "$lib/constants/canister-ids.constants";
import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store";
import { actionableSnsProposalsStore } from "$lib/stores/actionable-sns-proposals.store";
import type { ProposalsNavigationId } from "$lib/types/proposals";
import type { Universe } from "$lib/types/universe";
import type { SnsProposalData } from "@dfinity/sns";
import { fromDefinedNullable, nonNullish } from "@dfinity/utils";
import { derived, type Readable } from "svelte/store";

import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived";

export interface ActionableSnsProposalsByUniverseData {
universe: Universe;
proposals: SnsProposalData[];
}

/** A store that contains sns universes with actionable support and their actionable proposals
* in the same order as they are displayed in the UI. */
export const actionableSnsProposalsByUniverseStore: Readable<
Array<ActionableSnsProposalsByUniverseData>
> = derived(
[selectableUniversesStore, actionableSnsProposalsStore],
([universes, actionableSnsProposals]) =>
universes
.filter(({ canisterId }) =>
nonNullish(actionableSnsProposals[canisterId])
)
.map((universe) => ({
universe,
proposals: actionableSnsProposals[universe.canisterId].proposals,
}))
);

// Generate list of ProposalsNavigationId using universes to provide correct order
// of proposals in the UI.
export const actionableProposalsNavigationIdsStore: Readable<
Array<ProposalsNavigationId>
> = derived(
[
selectableUniversesStore,
actionableNnsProposalsStore,
actionableSnsProposalsStore,
],
([universes, nnsProposals, actionableSnsProposals]) =>
universes
.map(({ canisterId }) =>
canisterId === OWN_CANISTER_ID_TEXT
? (nnsProposals.proposals ?? []).map(({ id }) => ({
universe: OWN_CANISTER_ID_TEXT,
proposalId: id as bigint,
}))
: (actionableSnsProposals[canisterId]?.proposals ?? []).map((aa) => ({
universe: canisterId,
proposalId: fromDefinedNullable(aa.id).id,
}))
)
.flatMap((ids) => (nonNullish(ids) ? ids : []))
);
6 changes: 2 additions & 4 deletions frontend/src/lib/pages/SnsProposalDetail.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
import SnsProposalSystemInfoSection from "$lib/components/sns-proposals/SnsProposalSystemInfoSection.svelte";
import SnsProposalVotingSection from "$lib/components/sns-proposals/SnsProposalVotingSection.svelte";
import SkeletonDetails from "$lib/components/ui/SkeletonDetails.svelte";
import {
actionableProposalsActiveStore,
actionableProposalsNavigationIdsStore,
} from "$lib/derived/actionable-proposals.derived";
import { actionableProposalsActiveStore } from "$lib/derived/actionable-proposals.derived";
import { actionableProposalsNavigationIdsStore } from "$lib/derived/actionable-universes.derived";
import { authSignedInStore } from "$lib/derived/auth.derived";
import { pageStore } from "$lib/derived/page.derived";
import { selectableUniversesStore } from "$lib/derived/selectable-universes.derived";
Expand Down
126 changes: 2 additions & 124 deletions frontend/src/tests/lib/derived/actionable-proposals.derived.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import {
actionableProposalTotalCountStore,
actionableProposalsActiveStore,
actionableProposalsLoadedStore,
actionableProposalsNavigationIdsStore,
actionableSnsProposalsByUniverseStore,
} from "$lib/derived/actionable-proposals.derived";
import { actionableNnsProposalsStore } from "$lib/stores/actionable-nns-proposals.store";
import { actionableProposalsSegmentStore } from "$lib/stores/actionable-proposals-segment.store";
Expand All @@ -19,29 +17,13 @@ import { page } from "$mocks/$app/stores";
import { resetIdentity, setNoIdentity } from "$tests/mocks/auth.store.mock";
import { mockProposalInfo } from "$tests/mocks/proposal.mock";
import { principal } from "$tests/mocks/sns-projects.mock";
import {
createSnsProposal,
mockSnsProposal,
} from "$tests/mocks/sns-proposals.mock";
import { mockSnsProposal } from "$tests/mocks/sns-proposals.mock";
import { resetSnsProjects, setSnsProjects } from "$tests/utils/sns.test-utils";
import { runResolvedPromises } from "$tests/utils/timers.test-utils";
import type { ProposalInfo } from "@dfinity/nns";
import { Principal } from "@dfinity/principal";
import {
SnsProposalDecisionStatus,
SnsProposalRewardStatus,
SnsSwapLifecycle,
type SnsProposalData,
} from "@dfinity/sns";
import { SnsSwapLifecycle } from "@dfinity/sns";
import { get } from "svelte/store";

describe("actionable proposals derived stores", () => {
const createProposal = (proposalId: bigint): SnsProposalData =>
createSnsProposal({
status: SnsProposalDecisionStatus.PROPOSAL_DECISION_STATUS_OPEN,
rewardStatus: SnsProposalRewardStatus.PROPOSAL_REWARD_STATUS_ACCEPT_VOTES,
proposalId,
});
const principal0 = principal(0);
const principal1 = principal(1);
const principal2 = principal(2);
Expand Down Expand Up @@ -183,46 +165,6 @@ describe("actionable proposals derived stores", () => {
});
});

describe("actionableSnsProposalsByUniverseStore", () => {
const proposals0 = [createProposal(0n)];
const proposals1 = [createProposal(1n)];

it("should return snses with proposals", async () => {
expect(get(actionableSnsProposalsByUniverseStore)).toEqual([]);

setSnsProjects([
{
lifecycle: SnsSwapLifecycle.Committed,
rootCanisterId: principal0,
},
{
lifecycle: SnsSwapLifecycle.Committed,
rootCanisterId: principal1,
},
]);

expect(get(actionableSnsProposalsByUniverseStore)).toEqual([]);

actionableSnsProposalsStore.set({
rootCanisterId: principal0,
proposals: proposals0,
});
actionableSnsProposalsStore.set({
rootCanisterId: principal1,
proposals: proposals1,
});

expect(
get(actionableSnsProposalsByUniverseStore).map(
({ universe: { canisterId }, proposals }) => [canisterId, proposals]
)
).toEqual([
[principal0.toText(), proposals0],
[principal1.toText(), proposals1],
]);
});
});

describe("actionableProposalsLoadedStore", () => {
it("should return true when all actionable proposals are loaded", async () => {
expect(get(actionableProposalsLoadedStore)).toEqual(false);
Expand Down Expand Up @@ -299,68 +241,4 @@ describe("actionable proposals derived stores", () => {
expect(get(actionableProposalsLoadedStore)).toEqual(true);
});
});

describe("actionableProposalsNavigationIdsStore", () => {
it("should return navigation IDs", async () => {
expect(get(actionableProposalsNavigationIdsStore)).toEqual([]);

setSnsProjects([
{
lifecycle: SnsSwapLifecycle.Committed,
rootCanisterId: Principal.fromText("g3pce-2iaae"),
},
{
lifecycle: SnsSwapLifecycle.Committed,
rootCanisterId: Principal.fromText("f7crg-kabae"),
},
]);
// Add Sns proposals in reverse order to test that the universe order is used.
actionableSnsProposalsStore.set({
rootCanisterId: Principal.fromText("f7crg-kabae"),
proposals: [createProposal(1n), createProposal(0n)],
});
actionableSnsProposalsStore.set({
rootCanisterId: Principal.fromText("g3pce-2iaae"),
proposals: [createProposal(3n), createProposal(2n)],
});
actionableNnsProposalsStore.setProposals([
{
...mockProposalInfo,
id: 2n,
},
{
...mockProposalInfo,
id: 1n,
},
]);
await runResolvedPromises();

expect(get(actionableProposalsNavigationIdsStore)).toEqual([
{
universe: OWN_CANISTER_ID_TEXT,
proposalId: 2n,
},
{
universe: OWN_CANISTER_ID_TEXT,
proposalId: 1n,
},
{
proposalId: 3n,
universe: "g3pce-2iaae",
},
{
proposalId: 2n,
universe: "g3pce-2iaae",
},
{
proposalId: 1n,
universe: "f7crg-kabae",
},
{
proposalId: 0n,
universe: "f7crg-kabae",
},
]);
});
});
});
Loading

0 comments on commit d7aa761

Please sign in to comment.