Skip to content

Commit

Permalink
feat: Add selector for site transfer screen
Browse files Browse the repository at this point in the history
  • Loading branch information
David Code Howard committed Oct 24, 2023
1 parent fe41d24 commit 07f71bd
Show file tree
Hide file tree
Showing 3 changed files with 109 additions and 5 deletions.
63 changes: 60 additions & 3 deletions src/selectors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ import {
Project,
ProjectMembership,
} from 'terraso-client-shared/project/projectSlice';
import { selectProjectMembershipsWithUsers } from 'terraso-client-shared/selectors';
import {
selectProjectMembershipsWithUsers,
selectProjectsWithTransferrableSites,
} from 'terraso-client-shared/selectors';
import { Site } from 'terraso-client-shared/site/siteSlice';
import { SerializableSet } from 'terraso-client-shared/store/utils';
import { createStore } from 'terraso-client-shared/tests/utils';
import { v4 as uuidv4 } from 'uuid';

Expand All @@ -30,20 +35,43 @@ const generateUser = () => {
const generateProject = (
memberships: ProjectMembership[] = [],
privacy?: ProjectPrivacy,
sites: Site[] = [],
): Project => {
const id = uuidv4();
const siteSet: SerializableSet = {};
for (let site of sites) {
site.projectId = id;
siteSet[site.id] = true;
}
return {
id,
name: id,
privacy: privacy ?? 'PRIVATE',
description: '',
updatedAt: '2023-10-12',
sites: {},
sites: siteSet,
archived: false,
memberships: keyBy(memberships, 'id'),
};
};

const generateSite = (project: Project): Site => {
const id = uuidv4();
const site: Site = {
projectId: project.id,
ownerId: undefined,
id,
name: 'Test Site',
latitude: 0,
longitude: 0,
privacy: 'PRIVATE',
archived: false,
updatedAt: '2023-10-24',
};
project.sites[site.id] = true;
return site;
};

const generateMembership = (userId: string, userRole: UserRole) => {
return { id: uuidv4(), userId, userRole };
};
Expand All @@ -62,7 +90,7 @@ const keyBy = <T, Index extends keyof T>(
);
};

function initState(projects: Project[], users: User[]) {
function initState(projects: Project[], users: User[], sites: Site[] = []) {
return merge(
{
account: {
Expand All @@ -71,6 +99,9 @@ function initState(projects: Project[], users: User[]) {
project: {
projects: keyBy(projects, 'id'),
},
site: {
sites: keyBy(sites, 'id'),
},
},
{ account: { ...accountInitialState } },
);
Expand Down Expand Up @@ -116,3 +147,29 @@ test('not found project returns empty membership', () => {
);
expect(memberships).toStrictEqual([]);
});

test('can access all projects with role', () => {
const user = generateUser();
const project1 = generateProject([generateMembership(user.id, 'manager')]);
const project2 = generateProject([
generateMembership(user.id, 'contributor'),
]);
const site1 = generateSite(project1);
const site2 = generateSite(project2);

const store = createStore(
initState([project1, project2], [user], [site1, site2]),
);
const pairs = selectProjectsWithTransferrableSites(
store.getState(),
'manager',
);
expect(pairs).toStrictEqual([
{
projectId: project1.id,
projectName: project1.name,
siteId: site1.id,
siteName: site1.name,
},
]);
});
42 changes: 40 additions & 2 deletions src/selectors.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { createSelector } from '@reduxjs/toolkit';
import { User } from 'terraso-client-shared/account/accountSlice';
import { UserRole } from 'terraso-client-shared/graphqlSchema/graphql';
import { ProjectMembership } from 'terraso-client-shared/project/projectSlice';
import { type SharedState } from 'terraso-client-shared/store/store';

import { ProjectMembership } from './project/projectSlice';
import { exists, filterValues, mapValues } from 'terraso-client-shared/utils';

const selectProjectMemberships = (state: SharedState, projectId: string) =>
state.project.projects[projectId]?.memberships ?? [];
Expand All @@ -16,3 +17,40 @@ export const selectProjectMembershipsWithUsers = createSelector(
.filter(memb => memb.userId in users)
.map(memb => [memb, users[memb.userId]] as [ProjectMembership, User]),
);

const selectProjects = (state: SharedState) => state.project.projects;

const selectSites = (state: SharedState) => state.site.sites;

const selectUserRole = (_state: SharedState, userRole: UserRole) => userRole;

const selectProjectsWithUserRole = createSelector(
[selectProjects, selectUserRole],
(projects, userRole) =>
filterValues(projects, project =>
exists(
mapValues(project.memberships, membership => membership.userRole),
userRole,
),
),
);

export const selectProjectsWithTransferrableSites = createSelector(
[selectProjectsWithUserRole, selectSites],
(projects, sites) => {
return projects.flatMap(project =>
Object.keys(project.sites)
.filter(siteId => siteId in project.sites)
.map(siteId => {
const joinedSite = sites[siteId];

return {
projectId: project.id,
projectName: project.name,
siteId: joinedSite.id,
siteName: joinedSite.name,
};
}),
);
},
);
9 changes: 9 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
export const fromEntries = <K extends string | number, V>(entries: [K, V][]) =>
Object.fromEntries(entries) as Record<K, V>;

export const filterValues = <T>(obj: Record<any, T>, f: (arg: T) => boolean) =>
Object.values(obj).filter(f);

export const exists = <T>(haystack: T[], needle: T) =>
haystack.filter(item => item === needle).length > 0;

export const mapValues = <T, U>(obj: Record<any, T>, f: (arg: T) => U) =>
Object.values(obj).map(f);

0 comments on commit 07f71bd

Please sign in to comment.