Skip to content

Commit

Permalink
feat: issue #113 author field population
Browse files Browse the repository at this point in the history
  • Loading branch information
cyp3rius committed Apr 15, 2022
1 parent 092cc25 commit 8e3006d
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 43 deletions.
2 changes: 1 addition & 1 deletion __mocks__/initSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export = (config: any = {}, toStore: boolean = false, database: any = {}) => {
const parseValues = (values: any[], args: any = {}) => values.map(_ => {
const { select = [] } = args;
if (!isEmpty(select)) {
return pick(_, [...select, 'threadOf', 'authorUser']); // Relation fields can't be "unselected"
return pick(_, [...select, 'threadOf']);
}
return _;
})
Expand Down
34 changes: 34 additions & 0 deletions admin/src/components/Avatar/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/**
*
* Avatar
*
*/

// @ts-nocheck

import React from "react";
import PropTypes from "prop-types";
import { isObject } from "lodash";
import { Avatar, Initials } from "@strapi/design-system/Avatar";
import { renderInitials } from "../../utils";

const UserAvatar = ({
avatar,
name,
}) => {
if (avatar) {
let image = avatar;
if (isObject(avatar)) {
image = avatar?.formats?.thumbnail.url || avatar.url
}
return <Avatar src={image} alt={name} />
}
return <Initials>{renderInitials(name)}</Initials>
};

UserAvatar.propTypes = {
avatar: PropTypes.oneOfType(PropTypes.string, PropTypes.object).isRequired,
name: PropTypes.string,
};

export default UserAvatar;
11 changes: 3 additions & 8 deletions admin/src/components/DiscussionThreadItemFooter/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@
import React from "react";
import PropTypes from "prop-types";
import { useIntl } from "react-intl";
import { Avatar, Initials } from "@strapi/design-system/Avatar";
import { Box } from "@strapi/design-system/Box";
import { Typography } from "@strapi/design-system/Typography";
import { renderInitials } from "../../utils";
import {
DiscussionThreadItemFooterMeta,
DiscussionThreadItemFooterStyled,
} from "./styles";
import UserAvatar from "../Avatar";

const DiscussionThreadItemFooter = ({
children,
Expand All @@ -35,11 +34,7 @@ const DiscussionThreadItemFooter = ({
return (
<DiscussionThreadItemFooterStyled as={Box} paddingTop={2} direction="row">
<DiscussionThreadItemFooterMeta size={3} horizontal>
{avatar ? (
<Avatar src={avatar} alt={name} />
) : (
<Initials>{renderInitials(name)}</Initials>
)}
<UserAvatar avatar={avatar} name={name} />
<Typography variant="pi" fontWeight="bold" textColor="neutral800">
{name}
</Typography>
Expand All @@ -57,7 +52,7 @@ DiscussionThreadItemFooter.propTypes = {
id: PropTypes.oneOfType(PropTypes.string, PropTypes.number).isRequired,
name: PropTypes.string.isRequired,
email: PropTypes.string,
avatar: PropTypes.string,
avatar: PropTypes.oneOfType(PropTypes.string, PropTypes.object),
}).isRequired,
createdAt: PropTypes.string.isRequired,
updatedAt: PropTypes.string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import PropTypes from "prop-types";
import { useIntl } from "react-intl";
import { useMutation, useQueryClient } from "react-query";
import { isNil, isEmpty } from "lodash";
import { Avatar, Initials } from "@strapi/design-system/Avatar";
import { Flex } from "@strapi/design-system/Flex";
import { IconButton } from "@strapi/design-system/IconButton";
import { Link } from "@strapi/design-system/Link";
Expand All @@ -18,7 +17,6 @@ import {
getMessage,
getUrl,
handleAPIError,
renderInitials,
resolveCommentStatus,
resolveCommentStatusColor,
} from "../../../../utils";
Expand All @@ -31,6 +29,7 @@ import DiscussionThreadItemApprovalFlowActions from "../../../../components/Disc
import StatusBadge from "../../../../components/StatusBadge";
import { IconButtonGroupStyled } from "../../../../components/IconButton/styles";
import DiscussionThreadItemReviewAction from "../../../../components/DiscussionThreadItemReviewAction";
import UserAvatar from "../../../../components/Avatar";

const DiscoverTableRow = ({
config,
Expand Down Expand Up @@ -154,11 +153,7 @@ const DiscoverTableRow = ({
</Td>
<Td>
<Stack size={2} horizontal>
{item.author?.avatar ? (
<Avatar src={item.author?.avatar} alt={item.author.name} />
) : (
<Initials>{renderInitials(item.author.name)}</Initials>
)}
<UserAvatar avatar={item.author?.avatar} name={item.author.name} />
<Typography textColor="neutral800" variant="pi">
{item.author.name}
</Typography>
Expand Down
38 changes: 34 additions & 4 deletions server/controllers/utils/parsers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,50 @@ export const flatInput = <T, TKeys = keyof T>(
pagination?: ToBeFixed,
fields?: StrapiRequestQueryFieldsClause<OnlyStrings<TKeys>>,
) => {
const filters = query?.filters || query;
const { populate = {}, filters } = query;
const orOperator = (filters?.$or || []).filter(
(_: ToBeFixed) => !Object.keys(_).includes("removed")
);

let basePopulate = {
...populate,
};

let threadOfPopulate = {
threadOf: {
populate: {
authorUser: true,
...populate,
},
},
};

// Cover case when someone wants to populate author instead of authorUser
if (populate.author) {
const { author, ...restPopulate } = populate;
basePopulate = {
...restPopulate,
authorUser: author
};
threadOfPopulate = {
threadOf: {
populate: {
authorUser: author,
...restPopulate,
},
},
};
}

return {
query: {
...filters,
$or: [...orOperator, { removed: { $null: true } }, { removed: false }],
related: relation,
},
populate: {
threadOf: {
populate: { authorUser: true },
},
...basePopulate,
...threadOfPopulate,
},
pagination,
sort,
Expand Down
29 changes: 26 additions & 3 deletions server/services/__tests__/common.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,9 +393,15 @@ describe("Test Comments service functions utils", () => {
content: "IKL",
threadOf: 2,
related,
authorId: 1,
authorName: "Joe Doe",
authorEmail: "[email protected]",
authorUser: {
id: 1,
username: "Joe Doe",
email: "[email protected]",
avatar: {
id: 1,
url: 'http://example.com'
}
}
},
];
const relatedEntity = { id: 1, title: "Test", uid: collection };
Expand Down Expand Up @@ -435,6 +441,23 @@ describe("Test Comments service functions utils", () => {
expect(Object.keys(filterOutUndefined(result.data[3]))).toHaveLength(6);
});

test("Should return structure with populated avatar field", async () => {
const result = await getPluginService<IServiceCommon>(
"common"
).findAllFlat({
query: { related },
populate: {
authorUser: {
populate: { avatar: true },
},
}
}, relatedEntity);
expect(result).toHaveProperty("data");
expect(result).not.toHaveProperty("meta");
expect(result.data.length).toBe(4);
expect(result).toHaveProperty(["data", 3, "author", "avatar", "url"], db[3].authorUser.avatar.url);
});

test("Should return structure with pagination", async () => {
const result = await getPluginService<IServiceCommon>(
"common"
Expand Down
14 changes: 9 additions & 5 deletions server/services/admin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export = ({ strapi }: StrapiContext): IServiceAdmin => ({
.findMany({
...params,
populate: {
authorUser: true,
authorUser: { populate: { avatar: true } },
threadOf: true,
reports: {
where: {
Expand All @@ -185,7 +185,7 @@ export = ({ strapi }: StrapiContext): IServiceAdmin => ({
const result = entities
.map((_) =>
filterOurResolvedReports(
this.getCommonService().sanitizeCommentEntity(_)
this.getCommonService().sanitizeCommentEntity(_, { avatar: true })
)
)
.map((_) =>
Expand Down Expand Up @@ -226,10 +226,14 @@ export = ({ strapi }: StrapiContext): IServiceAdmin => ({

const defaultPopulate = {
populate: {
authorUser: true,
authorUser: {
populate: { avatar: true }
},
threadOf: {
populate: {
authorUser: true,
authorUser: {
populate: { avatar: true }
},
...reportsPopulation,
},
},
Expand Down Expand Up @@ -288,7 +292,7 @@ export = ({ strapi }: StrapiContext): IServiceAdmin => ({
const selectedEntity = this.getCommonService().sanitizeCommentEntity({
...entity,
threadOf: entity.threadOf || null,
});
}, { avatar: true });

return {
entity: relatedEntity,
Expand Down
11 changes: 7 additions & 4 deletions server/services/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
StrapiResponseMeta,
StrapiPaginatedResponse,
StrapiDBQueryArgs,
StringMap,
} from "strapi-typed";
import {
CommentsPluginConfig,
Expand Down Expand Up @@ -240,7 +241,7 @@ export = ({ strapi }: StrapiContext): IServiceCommon => ({
threadOf: parsedThreadOf || _.threadOf || null,
gotThread: (threadedItem?.itemsInTread || 0) > 0,
threadFirstItemId: threadedItem?.firstThreadItemId,
});
}, populate?.authorUser?.populate);
});

return {
Expand Down Expand Up @@ -386,14 +387,16 @@ export = ({ strapi }: StrapiContext): IServiceCommon => ({
}
},

sanitizeCommentEntity(entity: Comment): Comment {
sanitizeCommentEntity(entity: Comment, populate?: StringMap<boolean | Array<string> | StringMap<unknown>>): Comment {
const fieldsToPopulate = isArray(populate) ? populate : Object.keys(populate || {});

return {
...buildAuthorModel({
...entity,
threadOf: isObject(entity.threadOf)
? buildAuthorModel(entity.threadOf)
? buildAuthorModel(entity.threadOf, fieldsToPopulate)
: entity.threadOf,
}),
}, fieldsToPopulate),
};
},

Expand Down
18 changes: 11 additions & 7 deletions server/services/utils/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export const convertContentTypeNameToSlug = (str: string): string => {
: plainConversion;
};

export const buildAuthorModel = (item: Comment): Comment => {
export const buildAuthorModel = (item: Comment, fieldsToPopulate: Array<string> = []): Comment => {
const {
authorUser,
authorId,
Expand All @@ -105,12 +105,16 @@ export const buildAuthorModel = (item: Comment): Comment => {
} = item;
let author: CommentAuthor = {} as CommentAuthor;
if (authorUser) {
author = {
id: authorUser.id,
name: authorUser.username,
email: authorUser.email,
avatar: authorUser.avatar,
};
author = fieldsToPopulate
.reduce((prev, curr) => ({
...prev,
[curr]: authorUser[curr],
}), {
id: authorUser.id,
name: authorUser.username,
email: authorUser.email,
avatar: authorUser.avatar,
});
} else if (authorId) {
author = {
id: authorId,
Expand Down
8 changes: 5 additions & 3 deletions types/contentTypes.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { StrapiUser } from "strapi-typed";
import { StrapiUser, StringMap } from "strapi-typed";

export type Id = number | string;

export type Comment = {
export type Comment<TAuthor = CommentAuthor> = {
id: Id;
content: string;
author?: CommentAuthor | undefined;
author?: TAuthor | undefined;
children?: Array<Comment>;
reports?: Array<CommentReport>;
threadOf: Comment | number | null;
Expand Down Expand Up @@ -38,6 +38,8 @@ export type CommentAuthorPartial = {
authorUser?: StrapiUser;
};

export type CommentAuthorResolved = CommentAuthor & StringMap<unknown>;

export type CommentReport = {
id: Id;
related: Comment | Id;
Expand Down
2 changes: 1 addition & 1 deletion types/services.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export interface IServiceCommon {
fieldName: string,
value: any
): Promise<boolean>;
sanitizeCommentEntity(entity: Comment): Comment;
sanitizeCommentEntity(entity: Comment, populate?: StringMap<boolean | Array<string> | StringMap<unknown>>): Comment;
isValidUserContext(user?: any): boolean;
parseRelationString(
relation: string
Expand Down

0 comments on commit 8e3006d

Please sign in to comment.