Skip to content

Commit db3d2f5

Browse files
authored
feat: User tags backend adjustments (GSoC) (PalisadoesFoundation#2460)
* add organization userTags connection * add tests * fix formatting issue * adjustments and queries for tag screens * fix linting
1 parent 10a5203 commit db3d2f5

17 files changed

+372
-31
lines changed

schema.graphql

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1514,6 +1514,8 @@ type Query {
15141514
getNoteById(id: ID!): Note!
15151515
getPledgesByUserId(orderBy: PledgeOrderByInput, userId: ID!, where: PledgeWhereInput): [FundraisingCampaignPledge]
15161516
getPlugins: [Plugin]
1517+
getUserTag(id: ID!): UserTag
1518+
getUserTagAncestors(id: ID!): [UserTag]
15171519
getVenueByOrgId(first: Int, orderBy: VenueOrderByInput, orgId: ID!, skip: Int, where: VenueWhereInput): [Venue]
15181520
getlanguage(lang_code: String!): [Translation]
15191521
groupChatById(id: ID!): GroupChat
@@ -1799,9 +1801,9 @@ input UpdateUserPasswordInput {
17991801
}
18001802

18011803
input UpdateUserTagInput {
1802-
_id: ID!
18031804
name: String!
1804-
tagColor: String!
1805+
tagColor: String
1806+
tagId: ID!
18051807
}
18061808

18071809
scalar Upload
@@ -1939,7 +1941,7 @@ type UserTag {
19391941
type UserTagsConnection {
19401942
edges: [UserTagsConnectionEdge!]!
19411943
pageInfo: DefaultConnectionPageInfo!
1942-
totalCount: PositiveInt
1944+
totalCount: Int
19431945
}
19441946

19451947
"""A default connection edge on the UserTag type for UserTagsConnection."""
@@ -1987,6 +1989,7 @@ input UserWhereInput {
19871989
type UsersConnection {
19881990
edges: [UsersConnectionEdge!]!
19891991
pageInfo: DefaultConnectionPageInfo!
1992+
totalCount: Int
19901993
}
19911994

19921995
"""A default connection edge on the User type for UsersConnection."""

src/resolvers/Mutation/assignUserTag.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,32 @@ export const assignUserTag: MutationResolvers["assignUserTag"] = async (
152152
);
153153
}
154154

155-
// Assign the tag
156-
await TagUser.create({
157-
...args.input,
158-
});
155+
// assign all the ancestor tags
156+
const allAncestorTags = [tag._id];
157+
let currentTag = tag;
158+
while (currentTag?.parentTagId) {
159+
const currentParentTag = await OrganizationTagUser.findOne({
160+
_id: currentTag.parentTagId,
161+
}).lean();
162+
163+
if (currentParentTag) {
164+
allAncestorTags.push(currentParentTag?._id);
165+
currentTag = currentParentTag;
166+
}
167+
}
168+
169+
const assigneeId = args.input.userId;
170+
171+
const tagUserDocs = allAncestorTags.map((tagId) => ({
172+
updateOne: {
173+
filter: { userId: assigneeId, tagId },
174+
update: { $setOnInsert: { userId: assigneeId, tagId } },
175+
upsert: true,
176+
setDefaultsOnInsert: true,
177+
},
178+
}));
179+
180+
await TagUser.bulkWrite(tagUserDocs);
159181

160182
return requestUser;
161183
};

src/resolvers/Mutation/unassignUserTag.ts

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,9 +145,36 @@ export const unassignUserTag: MutationResolvers["unassignUserTag"] = async (
145145
);
146146
}
147147

148+
// Get all the child tags of the current tag (including itself)
149+
// on the OrganizationTagUser model
150+
// The following implementation makes number of queries = max depth of nesting in the tag provided
151+
let allTagIds: string[] = [];
152+
let currentParents = [tag._id.toString()];
153+
154+
while (currentParents.length) {
155+
allTagIds = allTagIds.concat(currentParents);
156+
const foundTags = await OrganizationTagUser.find(
157+
{
158+
organizationId: tag.organizationId,
159+
parentTagId: {
160+
$in: currentParents,
161+
},
162+
},
163+
{
164+
_id: 1,
165+
},
166+
);
167+
currentParents = foundTags
168+
.map((tag) => tag._id.toString())
169+
.filter((id: string | null) => id);
170+
}
171+
148172
// Unassign the tag
149-
await TagUser.deleteOne({
150-
...args.input,
173+
await TagUser.deleteMany({
174+
tagId: {
175+
$in: allTagIds,
176+
},
177+
userId: args.input.userId,
151178
});
152179

153180
return requestUser;

src/resolvers/Mutation/updateUserTag.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export const updateUserTag: MutationResolvers["updateUserTag"] = async (
7474

7575
// Get the tag object
7676
const existingTag = await OrganizationTagUser.findOne({
77-
_id: args.input._id,
77+
_id: args.input.tagId,
7878
}).lean();
7979

8080
if (!existingTag) {
@@ -130,7 +130,7 @@ export const updateUserTag: MutationResolvers["updateUserTag"] = async (
130130
// Update the title of the tag and return it
131131
return await OrganizationTagUser.findOneAndUpdate(
132132
{
133-
_id: args.input._id,
133+
_id: args.input.tagId,
134134
},
135135
{
136136
name: args.input.name,

src/resolvers/Organization/userTags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ export const userTags: OrganizationResolvers["userTags"] = async (
7272
OrganizationTagUser.find({
7373
...filter,
7474
organizationId: parent._id,
75+
parentTagId: null,
7576
})
7677
.sort(sort)
7778
.limit(parsedArgs.limit)

src/resolvers/Query/getUserTag.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { OrganizationTagUser } from "../../models";
2+
import { errors, requestContext } from "../../libraries";
3+
import type { QueryResolvers } from "../../types/generatedGraphQLTypes";
4+
import { TAG_NOT_FOUND } from "../../constants";
5+
6+
export const getUserTag: QueryResolvers["getUserTag"] = async (
7+
_parent,
8+
args,
9+
) => {
10+
const userTag = await OrganizationTagUser.findById(args.id).lean();
11+
12+
if (!userTag) {
13+
throw new errors.NotFoundError(
14+
requestContext.translate(TAG_NOT_FOUND.MESSAGE),
15+
TAG_NOT_FOUND.CODE,
16+
TAG_NOT_FOUND.PARAM,
17+
);
18+
}
19+
20+
return userTag;
21+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { OrganizationTagUser } from "../../models";
2+
import { errors, requestContext } from "../../libraries";
3+
import type { QueryResolvers } from "../../types/generatedGraphQLTypes";
4+
import { TAG_NOT_FOUND } from "../../constants";
5+
6+
export const getUserTagAncestors: QueryResolvers["getUserTagAncestors"] =
7+
async (_parent, args) => {
8+
let currentTag = await OrganizationTagUser.findById(args.id).lean();
9+
10+
const tagAncestors = [currentTag];
11+
12+
while (currentTag?.parentTagId) {
13+
const currentParent = await OrganizationTagUser.findById(
14+
currentTag.parentTagId,
15+
).lean();
16+
17+
if (currentParent) {
18+
tagAncestors.push(currentParent);
19+
currentTag = currentParent;
20+
}
21+
}
22+
23+
if (!currentTag) {
24+
throw new errors.NotFoundError(
25+
requestContext.translate(TAG_NOT_FOUND.MESSAGE),
26+
TAG_NOT_FOUND.CODE,
27+
TAG_NOT_FOUND.PARAM,
28+
);
29+
}
30+
31+
return tagAncestors.reverse();
32+
};

src/resolvers/Query/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import { getFundraisingCampaigns } from "./getFundraisingCampaigns";
3434
import { getPledgesByUserId } from "./getPledgesByUserId";
3535
import { getPlugins } from "./getPlugins";
3636
import { getlanguage } from "./getlanguage";
37+
import { getUserTag } from "./getUserTag";
38+
import { getUserTagAncestors } from "./getUserTagAncestors";
3739
import { me } from "./me";
3840
import { myLanguage } from "./myLanguage";
3941
import { organizations } from "./organizations";
@@ -84,6 +86,8 @@ export const Query: QueryResolvers = {
8486
getNoteById,
8587
getlanguage,
8688
getPlugins,
89+
getUserTag,
90+
getUserTagAncestors,
8791
isSampleOrganization,
8892
me,
8993
myLanguage,

src/typeDefs/inputs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -490,8 +490,8 @@ export const inputs = gql`
490490
}
491491
492492
input UpdateUserTagInput {
493-
_id: ID!
494-
tagColor: String!
493+
tagId: ID!
494+
tagColor: String
495495
name: String!
496496
}
497497

src/typeDefs/queries.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ export const queries = gql`
125125
126126
getNoteById(id: ID!): Note!
127127
128+
getUserTag(id: ID!): UserTag
129+
130+
getUserTagAncestors(id: ID!): [UserTag]
131+
128132
getAllNotesForAgendaItem(agendaItemId: ID!): [Note]
129133
130134
advertisementsConnection(

src/typeDefs/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ export const types = gql`
742742
type UserTagsConnection {
743743
edges: [UserTagsConnectionEdge!]!
744744
pageInfo: DefaultConnectionPageInfo!
745-
totalCount: PositiveInt
745+
totalCount: Int
746746
}
747747
748748
"""
@@ -759,6 +759,7 @@ export const types = gql`
759759
type UsersConnection {
760760
edges: [UsersConnectionEdge!]!
761761
pageInfo: DefaultConnectionPageInfo!
762+
totalCount: Int
762763
}
763764
764765
"""

src/types/generatedGraphQLTypes.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2306,6 +2306,8 @@ export type Query = {
23062306
getNoteById: Note;
23072307
getPledgesByUserId?: Maybe<Array<Maybe<FundraisingCampaignPledge>>>;
23082308
getPlugins?: Maybe<Array<Maybe<Plugin>>>;
2309+
getUserTag?: Maybe<UserTag>;
2310+
getUserTagAncestors?: Maybe<Array<Maybe<UserTag>>>;
23092311
getVenueByOrgId?: Maybe<Array<Maybe<Venue>>>;
23102312
getlanguage?: Maybe<Array<Maybe<Translation>>>;
23112313
groupChatById?: Maybe<GroupChat>;
@@ -2521,6 +2523,16 @@ export type QueryGetPledgesByUserIdArgs = {
25212523
};
25222524

25232525

2526+
export type QueryGetUserTagArgs = {
2527+
id: Scalars['ID']['input'];
2528+
};
2529+
2530+
2531+
export type QueryGetUserTagAncestorsArgs = {
2532+
id: Scalars['ID']['input'];
2533+
};
2534+
2535+
25242536
export type QueryGetVenueByOrgIdArgs = {
25252537
first?: InputMaybe<Scalars['Int']['input']>;
25262538
orderBy?: InputMaybe<VenueOrderByInput>;
@@ -2908,9 +2920,9 @@ export type UpdateUserPasswordInput = {
29082920
};
29092921

29102922
export type UpdateUserTagInput = {
2911-
_id: Scalars['ID']['input'];
29122923
name: Scalars['String']['input'];
2913-
tagColor: Scalars['String']['input'];
2924+
tagColor?: InputMaybe<Scalars['String']['input']>;
2925+
tagId: Scalars['ID']['input'];
29142926
};
29152927

29162928
export type User = {
@@ -3084,7 +3096,7 @@ export type UserTagsConnection = {
30843096
__typename?: 'UserTagsConnection';
30853097
edges: Array<UserTagsConnectionEdge>;
30863098
pageInfo: DefaultConnectionPageInfo;
3087-
totalCount?: Maybe<Scalars['PositiveInt']['output']>;
3099+
totalCount?: Maybe<Scalars['Int']['output']>;
30883100
};
30893101

30903102
/** A default connection edge on the UserTag type for UserTagsConnection. */
@@ -3133,6 +3145,7 @@ export type UsersConnection = {
31333145
__typename?: 'UsersConnection';
31343146
edges: Array<UsersConnectionEdge>;
31353147
pageInfo: DefaultConnectionPageInfo;
3148+
totalCount?: Maybe<Scalars['Int']['output']>;
31363149
};
31373150

31383151
/** A default connection edge on the User type for UsersConnection. */
@@ -4600,6 +4613,8 @@ export type QueryResolvers<ContextType = any, ParentType extends ResolversParent
46004613
getNoteById?: Resolver<ResolversTypes['Note'], ParentType, ContextType, RequireFields<QueryGetNoteByIdArgs, 'id'>>;
46014614
getPledgesByUserId?: Resolver<Maybe<Array<Maybe<ResolversTypes['FundraisingCampaignPledge']>>>, ParentType, ContextType, RequireFields<QueryGetPledgesByUserIdArgs, 'userId'>>;
46024615
getPlugins?: Resolver<Maybe<Array<Maybe<ResolversTypes['Plugin']>>>, ParentType, ContextType>;
4616+
getUserTag?: Resolver<Maybe<ResolversTypes['UserTag']>, ParentType, ContextType, RequireFields<QueryGetUserTagArgs, 'id'>>;
4617+
getUserTagAncestors?: Resolver<Maybe<Array<Maybe<ResolversTypes['UserTag']>>>, ParentType, ContextType, RequireFields<QueryGetUserTagAncestorsArgs, 'id'>>;
46034618
getVenueByOrgId?: Resolver<Maybe<Array<Maybe<ResolversTypes['Venue']>>>, ParentType, ContextType, RequireFields<QueryGetVenueByOrgIdArgs, 'orgId'>>;
46044619
getlanguage?: Resolver<Maybe<Array<Maybe<ResolversTypes['Translation']>>>, ParentType, ContextType, RequireFields<QueryGetlanguageArgs, 'lang_code'>>;
46054620
groupChatById?: Resolver<Maybe<ResolversTypes['GroupChat']>, ParentType, ContextType, RequireFields<QueryGroupChatByIdArgs, 'id'>>;
@@ -4784,7 +4799,7 @@ export type UserTagResolvers<ContextType = any, ParentType extends ResolversPare
47844799
export type UserTagsConnectionResolvers<ContextType = any, ParentType extends ResolversParentTypes['UserTagsConnection'] = ResolversParentTypes['UserTagsConnection']> = {
47854800
edges?: Resolver<Array<ResolversTypes['UserTagsConnectionEdge']>, ParentType, ContextType>;
47864801
pageInfo?: Resolver<ResolversTypes['DefaultConnectionPageInfo'], ParentType, ContextType>;
4787-
totalCount?: Resolver<Maybe<ResolversTypes['PositiveInt']>, ParentType, ContextType>;
4802+
totalCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
47884803
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
47894804
};
47904805

@@ -4797,6 +4812,7 @@ export type UserTagsConnectionEdgeResolvers<ContextType = any, ParentType extend
47974812
export type UsersConnectionResolvers<ContextType = any, ParentType extends ResolversParentTypes['UsersConnection'] = ResolversParentTypes['UsersConnection']> = {
47984813
edges?: Resolver<Array<ResolversTypes['UsersConnectionEdge']>, ParentType, ContextType>;
47994814
pageInfo?: Resolver<ResolversTypes['DefaultConnectionPageInfo'], ParentType, ContextType>;
4815+
totalCount?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
48004816
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
48014817
};
48024818

0 commit comments

Comments
 (0)