From be224d16fc0a30d5cb5b226e2227771981b6d334 Mon Sep 17 00:00:00 2001 From: pepermao Date: Tue, 30 Jul 2024 19:45:57 +0200 Subject: [PATCH 1/7] Added embedding field in verification request schema Created embeddings while creating the verification request Implemented the recommendations section in the verification request workflow Simplified user interface and added a drawer in the verification request workflow to manage the group Created migration to add the embedding field --- ...ng-field-in-verification-request-schema.ts | 30 +++++ public/locales/en/verificationRequest.json | 6 +- public/locales/pt/verificationRequest.json | 6 +- .../dto/create-verification-request-dto.ts | 5 + .../schemas/verification-request.schema.ts | 3 + .../verification-request.controller.ts | 11 ++ .../verification-request.service.ts | 111 +++++++++++++++--- .../Claim/CreateClaim/CreateClaimView.tsx | 63 ++-------- .../SentenceReport/SentenceReportCard.tsx | 2 +- .../VerificationRequestDisplay.tsx | 61 ---------- .../ManageVerificationRequestGroup.tsx | 31 +++++ .../VerificationRequestAlert.tsx | 16 ++- .../VerificationRequestCard.tsx | 10 +- .../VerificationRequestDisplay.style.tsx | 7 +- .../VerificationRequestDisplay.tsx | 69 +++++++++++ .../VerificationRequestDrawer.tsx | 55 +++++++++ .../VerificationRequestProvider.tsx | 90 ++++++++++++++ ...erificationRequestRecommedations.style.tsx | 39 ++++++ .../VerificationRequestRecommendations.tsx | 54 +++++++++ .../verification-request-review-page.tsx | 23 ++-- 20 files changed, 549 insertions(+), 143 deletions(-) create mode 100644 migrations/20240729172331-add-embedding-field-in-verification-request-schema.ts delete mode 100644 src/components/SentenceReport/VerificationRequestDisplay.tsx create mode 100644 src/components/VerificationRequest/ManageVerificationRequestGroup.tsx rename src/components/{SentenceReport => VerificationRequest}/VerificationRequestDisplay.style.tsx (73%) create mode 100644 src/components/VerificationRequest/VerificationRequestDisplay.tsx create mode 100644 src/components/VerificationRequest/VerificationRequestDrawer.tsx create mode 100644 src/components/VerificationRequest/VerificationRequestProvider.tsx create mode 100644 src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx create mode 100644 src/components/VerificationRequest/VerificationRequestRecommendations.tsx diff --git a/migrations/20240729172331-add-embedding-field-in-verification-request-schema.ts b/migrations/20240729172331-add-embedding-field-in-verification-request-schema.ts new file mode 100644 index 000000000..5330f6934 --- /dev/null +++ b/migrations/20240729172331-add-embedding-field-in-verification-request-schema.ts @@ -0,0 +1,30 @@ +import { Db } from "mongodb"; +import { OpenAIEmbeddings } from "@langchain/openai"; + +export async function up(db: Db) { + const embeddings = new OpenAIEmbeddings(); + const verificationRequestCursor = await db + .collection("verificationrequests") + .find(); + + while (await verificationRequestCursor.hasNext()) { + const verificationRequest = await verificationRequestCursor.next(); + + const embedding = await embeddings.embedQuery( + verificationRequest.content + ); + + await db + .collection("verificationrequests") + .updateOne( + { _id: verificationRequest._id }, + { $set: { embedding } } + ); + } +} + +export async function down(db: Db) { + await db + .collection("verificationrequests") + .updateMany({}, { $unset: { embedding: "" } }); +} diff --git a/public/locales/en/verificationRequest.json b/public/locales/en/verificationRequest.json index 5bce0292a..79db676fd 100644 --- a/public/locales/en/verificationRequest.json +++ b/public/locales/en/verificationRequest.json @@ -2,9 +2,13 @@ "verificationRequestListHeader": "Verification Requests", "verificationRequestTitle": "Verification Request", "manageVerificationRequests": "Manage selected verification requests", + "noVerificationRequestsMessage": "No verification requests", + "manageVerificationRequestsGroup": "Manage verification request's group", "createClaimFromVerificationRequest": "No claim was created with this verification request", "openVerificationRequestClaimLabel": "A claim created was related to this verification request", "openVerificationRequestClaimButton": "Open claim", "agroupVerificationRequest": "Related verification requests", - "openVerificationRequest": "Open" + "openVerificationRequest": "Open", + "recommendationTitle": "Recommedations", + "addInGroupButton": "Add in group" } \ No newline at end of file diff --git a/public/locales/pt/verificationRequest.json b/public/locales/pt/verificationRequest.json index 76d7c0f3a..bfef1ec41 100644 --- a/public/locales/pt/verificationRequest.json +++ b/public/locales/pt/verificationRequest.json @@ -2,9 +2,13 @@ "verificationRequestListHeader": "Denúncias", "verificationRequestTitle": "Denúncia", "manageVerificationRequests": "Gerenciar denúncias selecionadas", + "noVerificationRequestsMessage": "Nenhuma denúncia", + "manageVerificationRequestsGroup": "Gerenciar grupo de denúncias", "createClaimFromVerificationRequest": "Nenhuma afirmação foi criada com esta denúncia", "openVerificationRequestClaimLabel": "Uma afirmação criada foi relacionada com essa denúncia", "openVerificationRequestClaimButton": "Abrir afirmação", "agroupVerificationRequest": "Denúncias relacionadas", - "openVerificationRequest": "Abrir" + "openVerificationRequest": "Abrir", + "recommendationTitle": "Recomendações", + "addInGroupButton": "Adicionar no grupo" } \ No newline at end of file diff --git a/server/verification-request/dto/create-verification-request-dto.ts b/server/verification-request/dto/create-verification-request-dto.ts index 0dae5221d..f0290ed14 100644 --- a/server/verification-request/dto/create-verification-request-dto.ts +++ b/server/verification-request/dto/create-verification-request-dto.ts @@ -20,4 +20,9 @@ export class CreateVerificationRequestDTO { @IsOptional() @ApiProperty() data_hash: string; + + @IsOptional() + @IsArray() + @ApiProperty() + embedding?: number[]; } diff --git a/server/verification-request/schemas/verification-request.schema.ts b/server/verification-request/schemas/verification-request.schema.ts index c4f7f3d63..32186aba2 100644 --- a/server/verification-request/schemas/verification-request.schema.ts +++ b/server/verification-request/schemas/verification-request.schema.ts @@ -28,6 +28,9 @@ export class VerificationRequest { @Prop({ required: false, type: Boolean }) isSensitive: boolean; + + @Prop({ required: true, type: [Number] }) + embedding: number[]; } export const VerificationRequestSchema = diff --git a/server/verification-request/verification-request.controller.ts b/server/verification-request/verification-request.controller.ts index 61bfd7ea7..c7a476f97 100644 --- a/server/verification-request/verification-request.controller.ts +++ b/server/verification-request/verification-request.controller.ts @@ -136,12 +136,23 @@ export class VerificationRequestController { dataHash ); + const recommendationFilter = + verificationRequest.group.content.map((v: any) => v._id) || []; + + const recommendations = + await this.verificationRequestService.findSimilarRequests( + verificationRequest.content, + recommendationFilter, + 5 + ); + await this.viewService.getNextServer().render( req, res, "/verification-request-review-page", Object.assign(parsedUrl.query, { reviewTask, + recommendations, sitekey: this.configService.get("recaptcha_sitekey"), hideDescriptions: {}, websocketUrl: this.configService.get("websocketUrl"), diff --git a/server/verification-request/verification-request.service.ts b/server/verification-request/verification-request.service.ts index 28d39e80c..a5eebac40 100644 --- a/server/verification-request/verification-request.service.ts +++ b/server/verification-request/verification-request.service.ts @@ -9,6 +9,7 @@ import { InjectModel } from "@nestjs/mongoose"; import { GroupService } from "../group/group.service"; import { CreateVerificationRequestDTO } from "./dto/create-verification-request-dto"; import { UpdateVerificationRequestDTO } from "./dto/update-verification-request.dto"; +import { OpenAIEmbeddings } from "@langchain/openai"; const md5 = require("md5"); @Injectable() @@ -21,7 +22,7 @@ export class VerificationRequestService { ) {} async listAll({ page, pageSize, order }): Promise { - return this.VerificationRequestModel.find({}) + return this.VerificationRequestModel.find({}, { embedding: 0 }) .skip(page * parseInt(pageSize, 10)) .limit(parseInt(pageSize, 10)) .sort({ _id: order }) @@ -36,12 +37,15 @@ export class VerificationRequestService { async findAll(verifiedRequestQuery: { searchContent: string; }): Promise { - return this.VerificationRequestModel.find({ - content: { - $regex: verifiedRequestQuery.searchContent || "", - $options: "i", + return this.VerificationRequestModel.find( + { + content: { + $regex: verifiedRequestQuery.searchContent || "", + $options: "i", + }, }, - }); + { embedding: 0 } + ); } /** @@ -50,26 +54,30 @@ export class VerificationRequestService { * @returns the verification request document */ async getById(verificationRequestId: string): Promise { - return this.VerificationRequestModel.findById( - verificationRequestId - ).populate("group"); + return this.VerificationRequestModel.findById(verificationRequestId, { + embedding: 0, + }).populate("group"); } /** * Creates a new verification request document + * Executes the createEmbed function to store the verification request content embedding * For each sources in verification request, creates a new source document * @param verificationRequest verificationRequestBody * @returns the verification request document */ - create( + async create( verificationRequest: CreateVerificationRequestDTO ): Promise { try { verificationRequest.data_hash = md5(verificationRequest.content); + verificationRequest.embedding = await this.createEmbedContent( + verificationRequest.content + ); const newVerificationRequest = new this.VerificationRequestModel( verificationRequest ); - if (verificationRequest.sources.length) { + if (verificationRequest?.sources?.length) { for (const source of verificationRequest.sources) { this.sourceService.create({ href: source, @@ -93,9 +101,10 @@ export class VerificationRequestService { async findByDataHash( data_hash: string ): Promise { - return this.VerificationRequestModel.findOne({ data_hash }).populate( - "group" - ); + return this.VerificationRequestModel.findOne( + { data_hash }, + { embedding: 0 } + ).populate("group"); } /** @@ -282,4 +291,78 @@ export class VerificationRequestService { count(query) { return this.VerificationRequestModel.countDocuments().where(query); } + + /** + * Creates an embedding based on a query parameter + * @param content verification request content + * @returns verification request content embedding + */ + createEmbedContent(content: string): Promise { + const embeddings = new OpenAIEmbeddings(); + + return embeddings.embedQuery(content); + } + + /** + * Find similar verification requests related to the embedding content + * @param content verification request content + * @param filter verification requests IDs to filter, does not recommend verification requests those are part of the same group + * @param pageSize limit of documents + * @returns Verification requests with the similarity score greater than 0.80 + */ + async findSimilarRequests( + content, + filter, + pageSize + ): Promise { + const queryEmbedding = await this.createEmbedContent(content); + const filterIds = filter.map((verificationRequestId) => + Types.ObjectId(verificationRequestId) + ); + + return await this.VerificationRequestModel.aggregate([ + { $match: { _id: { $nin: filterIds } } }, + { + $addFields: { + similarity: { + $reduce: { + input: { + $zip: { + inputs: ["$embedding", queryEmbedding], + }, + }, + initialValue: 0, + in: { + $add: [ + "$$value", + { + $multiply: [ + { $arrayElemAt: ["$$this", 0] }, + { $arrayElemAt: ["$$this", 1] }, + ], + }, + ], + }, + }, + }, + }, + }, + { + $match: { + similarity: { $gte: 0.8, $type: "number" }, + }, + }, + { + $project: { + embedding: 0, + }, + }, + { + $sort: { similarity: -1 }, + }, + { + $limit: parseInt(pageSize), + }, + ]); + } } diff --git a/src/components/Claim/CreateClaim/CreateClaimView.tsx b/src/components/Claim/CreateClaim/CreateClaimView.tsx index 73cf1b696..714de2a46 100644 --- a/src/components/Claim/CreateClaim/CreateClaimView.tsx +++ b/src/components/Claim/CreateClaim/CreateClaimView.tsx @@ -17,14 +17,11 @@ import ClaimSelectPersonality from "./ClaimSelectPersonality"; import ClaimSelectType from "./ClaimSelectType"; import ClaimUploadImage from "./ClaimUploadImage"; import { CreateClaimHeader } from "./CreateClaimHeader"; -import colors from "../../../styles/colors"; -import LargeDrawer from "../../LargeDrawer"; -import VerificationRequestCard from "../../VerificationRequest/VerificationRequestCard"; -import AletheiaButton from "../../Button"; -import { DeleteOutlined } from "@ant-design/icons"; import { CreateClaimEvents } from "../../../machines/createClaim/types"; import verificationRequestApi from "../../../api/verificationRequestApi"; import { useTranslation } from "next-i18next"; +import VerificationRequestDrawer from "../../VerificationRequest/VerificationRequestDrawer"; +import ManageVerificationRequestGroup from "../../VerificationRequest/ManageVerificationRequestGroup"; const CreateClaimView = () => { const { t } = useTranslation(); @@ -81,27 +78,12 @@ const CreateClaimView = () => { {!isLoading && claimData?.group && claimData?.group?.content?.length > 0 && ( - setOpen(true)} - role="button" - aria-pressed="false" - tabIndex={0} - onKeyDown={(e) => { - if (e.key === "Enter" || e.key === "Space") { - setOpen(true); - e.preventDefault(); - } - }} - style={{ - color: colors.lightBlueMain, - textDecoration: "underline", - cursor: "pointer", - }} - > - {t( + + openDrawer={() => setOpen(true)} + /> )} {showPersonality && !!claimData.personalities?.length && ( @@ -117,34 +99,13 @@ const CreateClaimView = () => { {addUnattributed && } - - -

{t("verificationRequest:verificationRequestTitle")}

- {claimData?.group ? ( - claimData.group.content.map(({ _id, content }) => ( - onRemove(_id)} - loading={isLoading} - > - - , - ]} - /> - )) - ) : ( - <> - )} - -
+ isLoading={isLoading} + onCloseDrawer={onCloseDrawer} + onRemove={onRemove} + /> ); }; diff --git a/src/components/SentenceReport/SentenceReportCard.tsx b/src/components/SentenceReport/SentenceReportCard.tsx index 1affe5275..780f4deb0 100644 --- a/src/components/SentenceReport/SentenceReportCard.tsx +++ b/src/components/SentenceReport/SentenceReportCard.tsx @@ -11,7 +11,7 @@ import { ReviewTaskTypeEnum } from "../../machines/reviewTask/enums"; import { ReviewTaskMachineContext } from "../../machines/reviewTask/ReviewTaskMachineProvider"; import ClaimSummaryDisplay from "./ClaimSummaryDisplay"; import SourceSummaryDisplay from "./SourceSummaryDisplay"; -import VerificationRequestDisplay from "./VerificationRequestDisplay"; +import VerificationRequestDisplay from "../VerificationRequest/VerificationRequestDisplay"; const { Title } = Typography; diff --git a/src/components/SentenceReport/VerificationRequestDisplay.tsx b/src/components/SentenceReport/VerificationRequestDisplay.tsx deleted file mode 100644 index 1ba6cf5ed..000000000 --- a/src/components/SentenceReport/VerificationRequestDisplay.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import { Col, Typography } from "antd"; -import React from "react"; -import VerificationRequestCard from "../VerificationRequest/VerificationRequestCard"; -import { useTranslation } from "next-i18next"; -import VerificationRequestCardDisplayStyle from "./VerificationRequestDisplay.style"; -import { useAppSelector } from "../../store/store"; -import VerificationRequestAlert from "../VerificationRequest/VerificationRequestAlert"; - -const VerificationRequestDisplay = ({ content }) => { - const { t } = useTranslation(); - const { vw } = useAppSelector((state) => state); - const { content: contentText, group } = content; - const verificationRequestGroup = group?.content?.filter( - (c) => c._id !== content._id - ); - - return ( - - - - - - {t("verificationRequest:verificationRequestTitle")} - - - - {!vw.xs && ( - - - {t("verificationRequest:agroupVerificationRequest")} - -
- {verificationRequestGroup?.map(({ content }) => ( -
- -
- ))} -
- - )} -
- ); -}; - -export default VerificationRequestDisplay; diff --git a/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx b/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx new file mode 100644 index 000000000..5d91d89d9 --- /dev/null +++ b/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import colors from "../../styles/colors"; + +const ManageVerificationRequestGroup = ({ label, openDrawer }) => { + const onKeyDown = (e) => { + if (e.key === "Enter" || e.key === "Space") { + e.preventDefault(); + openDrawer(); + } + }; + + return ( + + {label} + + ); +}; + +export default ManageVerificationRequestGroup; diff --git a/src/components/VerificationRequest/VerificationRequestAlert.tsx b/src/components/VerificationRequest/VerificationRequestAlert.tsx index 36eb6c670..3c2221008 100644 --- a/src/components/VerificationRequest/VerificationRequestAlert.tsx +++ b/src/components/VerificationRequest/VerificationRequestAlert.tsx @@ -22,9 +22,21 @@ const VerificationRequestAlert = ({ targetId, verificationRequestId }) => { message: !targetId ? ( t("verificationRequest:createClaimFromVerificationRequest") ) : ( -
+
{t("verificationRequest:openVerificationRequestClaimLabel")} diff --git a/src/components/VerificationRequest/VerificationRequestCard.tsx b/src/components/VerificationRequest/VerificationRequestCard.tsx index 519cddac4..ebee764e6 100644 --- a/src/components/VerificationRequest/VerificationRequestCard.tsx +++ b/src/components/VerificationRequest/VerificationRequestCard.tsx @@ -7,9 +7,10 @@ const VerificationRequestCard = ({ content, actions = [], expandable = true, + style = {}, }) => { return ( - + 1 ? "space-around" : "flex-end", + alignItems: "center", + flexWrap: "wrap", width: "100%", }} > diff --git a/src/components/SentenceReport/VerificationRequestDisplay.style.tsx b/src/components/VerificationRequest/VerificationRequestDisplay.style.tsx similarity index 73% rename from src/components/SentenceReport/VerificationRequestDisplay.style.tsx rename to src/components/VerificationRequest/VerificationRequestDisplay.style.tsx index 9cbd2f07a..7c8411f2e 100644 --- a/src/components/SentenceReport/VerificationRequestDisplay.style.tsx +++ b/src/components/VerificationRequest/VerificationRequestDisplay.style.tsx @@ -2,7 +2,10 @@ import styled from "styled-components"; import { queries } from "../../styles/mediaQueries"; import { Row } from "antd"; -const VerificationRequestCardDisplayStyle = styled(Row)` +const VerificationRequestDisplayStyle = styled(Row)` + display: flex; + gap: 16px; + .cta-create-claim { display: flex; gap: 32px; @@ -18,4 +21,4 @@ const VerificationRequestCardDisplayStyle = styled(Row)` } `; -export default VerificationRequestCardDisplayStyle; +export default VerificationRequestDisplayStyle; diff --git a/src/components/VerificationRequest/VerificationRequestDisplay.tsx b/src/components/VerificationRequest/VerificationRequestDisplay.tsx new file mode 100644 index 000000000..52cc74f89 --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestDisplay.tsx @@ -0,0 +1,69 @@ +import { Col, Typography } from "antd"; +import React, { useContext, useState } from "react"; +import VerificationRequestCard from "./VerificationRequestCard"; +import { useTranslation } from "next-i18next"; +import VerificationRequestDisplayStyle from "./VerificationRequestDisplay.style"; +import { useAppSelector } from "../../store/store"; +import VerificationRequestAlert from "./VerificationRequestAlert"; +import VerificationRequestRecommendations from "./VerificationRequestRecommendations"; +import { VerificationRequestContext } from "./VerificationRequestProvider"; +import VerificationRequestDrawer from "./VerificationRequestDrawer"; +import ManageVerificationRequestGroup from "./ManageVerificationRequestGroup"; + +const VerificationRequestDisplay = ({ content }) => { + const { t } = useTranslation(); + const { vw } = useAppSelector((state) => state); + const [open, setOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const { group, onRemoveVerificationRequest } = useContext( + VerificationRequestContext + ); + const { content: contentText } = content; + const verificationRequestGroup = group?.content?.filter( + (c) => c._id !== content._id + ); + + const onRemove = (id) => { + setIsLoading(true); + onRemoveVerificationRequest(id).then(() => setIsLoading(false)); + }; + + return ( + + + + + + {t("verificationRequest:verificationRequestTitle")} + + + {!vw.xs && verificationRequestGroup?.length > 0 && ( + setOpen(true)} + /> + )} + + + + + setOpen(false)} + onRemove={onRemove} + /> + + ); +}; + +export default VerificationRequestDisplay; diff --git a/src/components/VerificationRequest/VerificationRequestDrawer.tsx b/src/components/VerificationRequest/VerificationRequestDrawer.tsx new file mode 100644 index 000000000..594cfb5ec --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestDrawer.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import LargeDrawer from "../LargeDrawer"; +import { Col, Typography } from "antd"; +import colors from "../../styles/colors"; +import VerificationRequestCard from "./VerificationRequestCard"; +import AletheiaButton from "../Button"; +import { DeleteOutlined } from "@ant-design/icons"; +import { useTranslation } from "next-i18next"; + +const VerificationRequestDrawer = ({ + groupContent, + open, + onCloseDrawer, + isLoading, + onRemove, +}) => { + const { t } = useTranslation(); + + return ( + + + + {t("verificationRequest:verificationRequestTitle")}s + + {groupContent.length > 0 ? ( + groupContent.map(({ _id, content }) => ( + onRemove(_id)} + loading={isLoading} + > + + , + ]} + /> + )) + ) : ( + + {t("verificationRequest:noVerificationRequestsMessage")} + + )} + + + ); +}; + +export default VerificationRequestDrawer; diff --git a/src/components/VerificationRequest/VerificationRequestProvider.tsx b/src/components/VerificationRequest/VerificationRequestProvider.tsx new file mode 100644 index 000000000..c8f9312c1 --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestProvider.tsx @@ -0,0 +1,90 @@ +import { createContext, useState } from "react"; +import verificationRequestApi from "../../api/verificationRequestApi"; + +interface IVerificationRequestContext { + recommendations?: any; + addRecommendation?: any; + group?: any; + removeFromGroup?: any; + onRemoveVerificationRequest?: any; +} + +export const VerificationRequestContext = + createContext({}); + +export const VerificationRequestProvider = ({ + verificationRequest, + baseRecommendations, + children, +}) => { + const [recommendations, setRecommendations] = useState(baseRecommendations); + const [group, setGroup] = useState(verificationRequest.group); + + const addRecommendation = (verificationRequestLiked) => { + const groupContent = group.content.filter( + (v) => v._id !== verificationRequest._id + ); + + verificationRequestApi.updateVerificationRequest( + verificationRequest._id, + { + group: [...groupContent, verificationRequestLiked], + } + ); + setRecommendations((prev) => + prev.filter((v) => v._id !== verificationRequestLiked._id) + ); + addInGroup(verificationRequestLiked); + }; + + const addInGroup = (newVerificationRequest) => { + setGroup((prev) => ({ + ...prev, + content: [...prev.content, newVerificationRequest], + })); + }; + + const removeFromGroup = (verificationRequestId) => { + const contentGroup = group.content.filter( + (verificationRequest) => + verificationRequest?._id !== verificationRequestId + ); + + setGroup((prev) => ({ + ...prev, + content: contentGroup, + })); + }; + + const onRemoveVerificationRequest = async (id) => { + try { + await verificationRequestApi.removeVerificationRequestFromGroup( + id, + { + group: group._id, + } + ); + + removeFromGroup(id); + } catch (e) { + console.error( + "Error while removing verification request from group", + e + ); + } + }; + + return ( + + {children} + + ); +}; diff --git a/src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx b/src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx new file mode 100644 index 000000000..8d19df118 --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx @@ -0,0 +1,39 @@ +import styled from "styled-components"; +import colors from "../../styles/colors"; + +const VerificationRequestRecommendationsStyled = styled.section` + width: 100%; + + .recommendation-list { + padding: 16px 0; + display: flex; + flex-direction: row; + overflow-x: auto; + gap: 16px; + } + + .recommendation-list::-webkit-scrollbar { + height: 4px; + width: 4px; + background: ${colors.grayTertiary}; + } + + .recommendation-list::-webkit-scrollbar-thumb { + background: ${colors.blackTertiary}; + border-radius: 4px; + } + + .recommendation-list::-moz-scrollbar { + height: 4px; + width: 4px; + background: ${colors.grayTertiary}; + } + + .recommendation-list::-mz-scrollbar { + height: 4px; + width: 4px; + background: ${colors.grayTertiary}; + } +`; + +export default VerificationRequestRecommendationsStyled; diff --git a/src/components/VerificationRequest/VerificationRequestRecommendations.tsx b/src/components/VerificationRequest/VerificationRequestRecommendations.tsx new file mode 100644 index 000000000..9d1527a8d --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestRecommendations.tsx @@ -0,0 +1,54 @@ +import React, { useContext } from "react"; +import VerificationRequestCard from "./VerificationRequestCard"; +import { Col, Typography } from "antd"; +import AletheiaButton from "../Button"; +import { useTranslation } from "next-i18next"; +import { VerificationRequestContext } from "./VerificationRequestProvider"; +import VerificationRequestRecommendationsStyled from "./VerificationRequestRecommedations.style"; + +const VerificationRequestRecommendations = () => { + const { t } = useTranslation(); + const { recommendations, addRecommendation } = useContext( + VerificationRequestContext + ); + + return ( + <> + {recommendations?.length > 0 && ( + + + {t("verificationRequest:recommendationTitle")} + +
+ {recommendations?.length > 0 && + recommendations.map((verificationRequest) => ( + + + addRecommendation( + verificationRequest + ) + } + > + {t( + "verificationRequest:recommendationTitle" + )} + , + ]} + /> + + ))} +
+
+ )} + + ); +}; + +export default VerificationRequestRecommendations; diff --git a/src/pages/verification-request-review-page.tsx b/src/pages/verification-request-review-page.tsx index 4b97f513d..b7ff37645 100644 --- a/src/pages/verification-request-review-page.tsx +++ b/src/pages/verification-request-review-page.tsx @@ -11,18 +11,21 @@ import { useSetAtom } from "jotai"; import { currentNameSpace } from "../atoms/namespace"; import ClaimReviewView from "../components/ClaimReview/ClaimReviewView"; import { ReviewTaskTypeEnum } from "../machines/reviewTask/enums"; +import { VerificationRequestProvider } from "../components/VerificationRequest/VerificationRequestProvider"; export interface SourceReviewPageProps { - verificationRequest: any; + verificationRequest: any; //TODO: Create Verification Request type sitekey: string; reviewTask: any; hideDescriptions: object; websocketUrl: string; nameSpace: string; + recommendations: any[]; } const SourceReviewPage: NextPage = (props) => { - const { verificationRequest, sitekey, hideDescriptions } = props; + const { verificationRequest, sitekey, hideDescriptions, recommendations } = + props; const dispatch = useDispatch(); const setCurrentNameSpace = useSetAtom(currentNameSpace); setCurrentNameSpace(props.nameSpace as NameSpaceEnum); @@ -37,11 +40,16 @@ const SourceReviewPage: NextPage = (props) => { baseReportModel={props?.reviewTask?.reportModel} reviewTaskType={ReviewTaskTypeEnum.VerificationRequest} > - + + + @@ -56,6 +64,7 @@ export async function getServerSideProps({ query, locale, locales, req }) { verificationRequest: JSON.parse( JSON.stringify(query.verificationRequest) ), + recommendations: JSON.parse(JSON.stringify(query.recommendations)), reviewTask: JSON.parse(JSON.stringify(query.reviewTask)), sitekey: query.sitekey, hideDescriptions: JSON.parse( From 64f9d4c51a63da7ad492c96986946f507ba83be7 Mon Sep 17 00:00:00 2001 From: pepermao Date: Tue, 30 Jul 2024 23:16:20 +0200 Subject: [PATCH 2/7] Created search verification request component Removed fetchInput form field --- public/locales/en/verificationRequest.json | 6 +- public/locales/pt/verificationRequest.json | 6 +- .../verification-request.service.ts | 14 ++- src/api/verificationRequestApi.ts | 32 +++++-- .../fieldLists/verificationRequestForm.ts | 8 -- src/components/Form/InputSearch.tsx | 6 +- .../ManageVerificationRequestGroup.tsx | 7 +- .../VerificationRequestDisplay.tsx | 33 ++----- .../VerificationRequestMainContent.tsx | 35 ++++++++ ...erificationRequestRecommedations.style.tsx | 26 +++--- .../VerificationRequestRecommendations.tsx | 41 ++------- .../VerificationRequestResultList.tsx | 62 +++++++++++++ .../VerificationRequestSearch.tsx | 87 +++++++++++++++++++ src/machines/reviewTask/context.ts | 1 - src/machines/reviewTask/events.ts | 1 - src/machines/reviewTask/reviewTaskMachine.ts | 20 ++--- 16 files changed, 273 insertions(+), 112 deletions(-) create mode 100644 src/components/VerificationRequest/VerificationRequestMainContent.tsx create mode 100644 src/components/VerificationRequest/VerificationRequestResultList.tsx create mode 100644 src/components/VerificationRequest/VerificationRequestSearch.tsx diff --git a/public/locales/en/verificationRequest.json b/public/locales/en/verificationRequest.json index 79db676fd..cba4fc312 100644 --- a/public/locales/en/verificationRequest.json +++ b/public/locales/en/verificationRequest.json @@ -10,5 +10,9 @@ "agroupVerificationRequest": "Related verification requests", "openVerificationRequest": "Open", "recommendationTitle": "Recommedations", - "addInGroupButton": "Add in group" + "addInGroupButton": "Add in group", + "alreadyInGroupMessage": "Already in group", + "searchResultsTitle": "Results", + "noResultsMessage": "No results", + "searchPlaceholder": "Search verification requests" } \ No newline at end of file diff --git a/public/locales/pt/verificationRequest.json b/public/locales/pt/verificationRequest.json index bfef1ec41..ba119548f 100644 --- a/public/locales/pt/verificationRequest.json +++ b/public/locales/pt/verificationRequest.json @@ -10,5 +10,9 @@ "agroupVerificationRequest": "Denúncias relacionadas", "openVerificationRequest": "Abrir", "recommendationTitle": "Recomendações", - "addInGroupButton": "Adicionar no grupo" + "addInGroupButton": "Adicionar no grupo", + "alreadyInGroupMessage": "Já adicionado ao grupo", + "searchResultsTitle": "Resultados", + "noResultsMessage": "Nenhum resultado", + "searchPlaceholder": "Busque denúncias" } \ No newline at end of file diff --git a/server/verification-request/verification-request.service.ts b/server/verification-request/verification-request.service.ts index a5eebac40..3ad41ba27 100644 --- a/server/verification-request/verification-request.service.ts +++ b/server/verification-request/verification-request.service.ts @@ -21,8 +21,18 @@ export class VerificationRequestService { private groupService: GroupService ) {} - async listAll({ page, pageSize, order }): Promise { - return this.VerificationRequestModel.find({}, { embedding: 0 }) + async listAll({ + content, + page, + pageSize, + order, + }): Promise { + const query: any = {}; + if (content) { + query.content = { $regex: content, $options: "i" }; + } + + return this.VerificationRequestModel.find(query, { embedding: 0 }) .skip(page * parseInt(pageSize, 10)) .limit(parseInt(pageSize, 10)) .sort({ _id: order }) diff --git a/src/api/verificationRequestApi.ts b/src/api/verificationRequestApi.ts index 61e3d19d7..1ad21237b 100644 --- a/src/api/verificationRequestApi.ts +++ b/src/api/verificationRequestApi.ts @@ -1,17 +1,24 @@ import axios from "axios"; -import { NameSpaceEnum } from "../types/Namespace"; +import { ActionTypes } from "../store/types"; + +interface SearchOptions { + searchText?: string; + page?: number; + pageSize?: number; + order?: string; +} const request = axios.create({ withCredentials: true, baseURL: `/api/verification-request`, }); -const get = (options) => { +const get = (options: SearchOptions, dispatch = null) => { const params = { + content: options.searchText, page: options.page ? options.page - 1 : 0, order: options.order || "asc", pageSize: options.pageSize ? options.pageSize : 10, - nameSpace: options?.nameSpace || NameSpaceEnum.Main, }; return request @@ -23,11 +30,22 @@ const get = (options) => { totalVerificationRequests, } = response.data; - return { - data: verificationRequests, - total: totalVerificationRequests, + if (!dispatch) { + return { + data: verificationRequests, + total: totalVerificationRequests, + totalPages, + }; + } + + dispatch({ + type: ActionTypes.SEARCH_RESULTS, + results: verificationRequests, + }); + dispatch({ + type: ActionTypes.SET_TOTAL_PAGES, totalPages, - }; + }); }) .catch((err) => { console.log(err); diff --git a/src/components/ClaimReview/form/fieldLists/verificationRequestForm.ts b/src/components/ClaimReview/form/fieldLists/verificationRequestForm.ts index 7c2cb2a4c..cd8384dc2 100644 --- a/src/components/ClaimReview/form/fieldLists/verificationRequestForm.ts +++ b/src/components/ClaimReview/form/fieldLists/verificationRequestForm.ts @@ -13,14 +13,6 @@ export const fetchVerificationRequestList = async (content) => { }; const verificationRequestForm: FormField[] = [ - createFormField({ - fieldName: "group", - type: "inputSearch", - i18nKey: "group", - required: false, - extraProps: { dataLoader: fetchVerificationRequestList }, - }), - createFormField({ fieldName: "isSensitive", type: "textbox", diff --git a/src/components/Form/InputSearch.tsx b/src/components/Form/InputSearch.tsx index 14375c2e9..767834615 100644 --- a/src/components/Form/InputSearch.tsx +++ b/src/components/Form/InputSearch.tsx @@ -8,7 +8,8 @@ const InputSearchStyled = styled(Input.Search)` display: none; } span.ant-input-affix-wrapper { - background: ${colors.lightGray}; + background: ${({ backgroundColor }) => + backgroundColor ? backgroundColor : colors.lightGray}; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.25); border-radius: 4px; &:focus-within { @@ -16,7 +17,8 @@ const InputSearchStyled = styled(Input.Search)` } } input.ant-input { - background: ${colors.lightGray}; + background: ${({ backgroundColor }) => + backgroundColor ? backgroundColor : colors.lightGray}; color: ${colors.blackSecondary}; &::placeholder { color: ${colors.blackSecondary}; diff --git a/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx b/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx index 5d91d89d9..1959410b5 100644 --- a/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx +++ b/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx @@ -1,7 +1,11 @@ import React from "react"; import colors from "../../styles/colors"; -const ManageVerificationRequestGroup = ({ label, openDrawer }) => { +const ManageVerificationRequestGroup = ({ + label, + openDrawer, + suffix = null, +}) => { const onKeyDown = (e) => { if (e.key === "Enter" || e.key === "Space") { e.preventDefault(); @@ -24,6 +28,7 @@ const ManageVerificationRequestGroup = ({ label, openDrawer }) => { }} > {label} + {suffix && suffix} ); }; diff --git a/src/components/VerificationRequest/VerificationRequestDisplay.tsx b/src/components/VerificationRequest/VerificationRequestDisplay.tsx index 52cc74f89..3402a6af5 100644 --- a/src/components/VerificationRequest/VerificationRequestDisplay.tsx +++ b/src/components/VerificationRequest/VerificationRequestDisplay.tsx @@ -1,18 +1,13 @@ -import { Col, Typography } from "antd"; import React, { useContext, useState } from "react"; -import VerificationRequestCard from "./VerificationRequestCard"; -import { useTranslation } from "next-i18next"; import VerificationRequestDisplayStyle from "./VerificationRequestDisplay.style"; -import { useAppSelector } from "../../store/store"; import VerificationRequestAlert from "./VerificationRequestAlert"; import VerificationRequestRecommendations from "./VerificationRequestRecommendations"; import { VerificationRequestContext } from "./VerificationRequestProvider"; import VerificationRequestDrawer from "./VerificationRequestDrawer"; -import ManageVerificationRequestGroup from "./ManageVerificationRequestGroup"; +import VerificationRequestMainContent from "./VerificationRequestMainContent"; +import VerificationRequestSearch from "./VerificationRequestSearch"; const VerificationRequestDisplay = ({ content }) => { - const { t } = useTranslation(); - const { vw } = useAppSelector((state) => state); const [open, setOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); const { group, onRemoveVerificationRequest } = useContext( @@ -35,23 +30,13 @@ const VerificationRequestDisplay = ({ content }) => { verificationRequestId={content._id} /> - - - {t("verificationRequest:verificationRequestTitle")} - - - {!vw.xs && verificationRequestGroup?.length > 0 && ( - setOpen(true)} - /> - )} - + setOpen(true)} + /> + + diff --git a/src/components/VerificationRequest/VerificationRequestMainContent.tsx b/src/components/VerificationRequest/VerificationRequestMainContent.tsx new file mode 100644 index 000000000..1e221e38c --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestMainContent.tsx @@ -0,0 +1,35 @@ +import { Typography } from "antd"; +import React from "react"; +import { useTranslation } from "next-i18next"; +import VerificationRequestCard from "./VerificationRequestCard"; +import { useAppSelector } from "../../store/store"; +import ManageVerificationRequestGroup from "./ManageVerificationRequestGroup"; + +const VerificationRequestMainContent = ({ + verificationRequestGroup, + content, + openDrawer, +}) => { + const { t } = useTranslation(); + const { vw } = useAppSelector((state) => state); + + return ( +
+ + {t("verificationRequest:verificationRequestTitle")} + + + {!vw.xs && verificationRequestGroup?.length > 0 && ( + + )} +
+ ); +}; + +export default VerificationRequestMainContent; diff --git a/src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx b/src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx index 8d19df118..db6fd3628 100644 --- a/src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx +++ b/src/components/VerificationRequest/VerificationRequestRecommedations.style.tsx @@ -1,39 +1,35 @@ import styled from "styled-components"; import colors from "../../styles/colors"; -const VerificationRequestRecommendationsStyled = styled.section` - width: 100%; +const VerificationRequestResultListStyled = styled.section` + padding: 16px 0; + display: flex; + flex-direction: row; + overflow-x: auto; + gap: 16px; - .recommendation-list { - padding: 16px 0; - display: flex; - flex-direction: row; - overflow-x: auto; - gap: 16px; - } - - .recommendation-list::-webkit-scrollbar { + ::-webkit-scrollbar { height: 4px; width: 4px; background: ${colors.grayTertiary}; } - .recommendation-list::-webkit-scrollbar-thumb { + ::-webkit-scrollbar-thumb { background: ${colors.blackTertiary}; border-radius: 4px; } - .recommendation-list::-moz-scrollbar { + ::-moz-scrollbar { height: 4px; width: 4px; background: ${colors.grayTertiary}; } - .recommendation-list::-mz-scrollbar { + ::-mz-scrollbar { height: 4px; width: 4px; background: ${colors.grayTertiary}; } `; -export default VerificationRequestRecommendationsStyled; +export default VerificationRequestResultListStyled; diff --git a/src/components/VerificationRequest/VerificationRequestRecommendations.tsx b/src/components/VerificationRequest/VerificationRequestRecommendations.tsx index 9d1527a8d..f6eac33b0 100644 --- a/src/components/VerificationRequest/VerificationRequestRecommendations.tsx +++ b/src/components/VerificationRequest/VerificationRequestRecommendations.tsx @@ -1,51 +1,22 @@ import React, { useContext } from "react"; -import VerificationRequestCard from "./VerificationRequestCard"; -import { Col, Typography } from "antd"; -import AletheiaButton from "../Button"; +import { Typography } from "antd"; import { useTranslation } from "next-i18next"; import { VerificationRequestContext } from "./VerificationRequestProvider"; -import VerificationRequestRecommendationsStyled from "./VerificationRequestRecommedations.style"; +import VerificationRequestResultList from "./VerificationRequestResultList"; const VerificationRequestRecommendations = () => { const { t } = useTranslation(); - const { recommendations, addRecommendation } = useContext( - VerificationRequestContext - ); + const { recommendations } = useContext(VerificationRequestContext); return ( <> {recommendations?.length > 0 && ( - +
{t("verificationRequest:recommendationTitle")} -
- {recommendations?.length > 0 && - recommendations.map((verificationRequest) => ( - - - addRecommendation( - verificationRequest - ) - } - > - {t( - "verificationRequest:recommendationTitle" - )} - , - ]} - /> - - ))} -
- + +
)} ); diff --git a/src/components/VerificationRequest/VerificationRequestResultList.tsx b/src/components/VerificationRequest/VerificationRequestResultList.tsx new file mode 100644 index 000000000..f0d3c1dd9 --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestResultList.tsx @@ -0,0 +1,62 @@ +import { Col } from "antd"; +import React, { useContext } from "react"; +import VerificationRequestCard from "./VerificationRequestCard"; +import AletheiaButton from "../Button"; +import { VerificationRequestContext } from "./VerificationRequestProvider"; +import { useTranslation } from "next-i18next"; +import VerificationRequestResultListStyled from "./VerificationRequestRecommedations.style"; + +const VerificationRequestResultList = ({ results }) => { + const { t } = useTranslation(); + const { group, addRecommendation } = useContext(VerificationRequestContext); + + const checkIfIsInGroup = (verificationRequestId): boolean => { + for (const verificationRequest of group.content) { + if (verificationRequest._id === verificationRequestId) { + return true; + } + + continue; + } + + return false; + }; + + return ( + + {results?.length > 0 && + results.map((verificationRequest) => ( + + + addRecommendation(verificationRequest) + } + > + {checkIfIsInGroup(verificationRequest._id) + ? t( + "verificationRequest:alreadyInGroupMessage" + ) + : t( + "verificationRequest:addInGroupButton" + )} + , + ]} + /> + + ))} + + ); +}; + +export default VerificationRequestResultList; diff --git a/src/components/VerificationRequest/VerificationRequestSearch.tsx b/src/components/VerificationRequest/VerificationRequestSearch.tsx new file mode 100644 index 000000000..5b42ce9b0 --- /dev/null +++ b/src/components/VerificationRequest/VerificationRequestSearch.tsx @@ -0,0 +1,87 @@ +import React, { useState } from "react"; +import InputSearch from "../Form/InputSearch"; +import { SearchOutlined } from "@ant-design/icons"; +import { useAppSelector } from "../../store/store"; +import AletheiaButton, { ButtonType } from "../Button"; +import { useTranslation } from "next-i18next"; +import colors from "../../styles/colors"; +import { Col, Typography } from "antd"; +import verificationRequestApi from "../../api/verificationRequestApi"; +import { useDispatch } from "react-redux"; +import VerificationRequestResultList from "./VerificationRequestResultList"; +import Loading from "../Loading"; + +const VerificationRequestSearch = () => { + const { t } = useTranslation(); + const dispatch = useDispatch(); + const [isLoading, setIsLoading] = useState(false); + const [content, setContent] = useState(""); + + const { verificationRequests } = useAppSelector((state) => ({ + verificationRequests: state?.search?.searchResults || null, + })); + + const handleInputSearch = async () => { + if (!isLoading && content.length > 3) { + setIsLoading(true); + await verificationRequestApi.get( + { + page: 1, + pageSize: 5, + searchText: content, + }, + dispatch + ); + + setIsLoading(false); + } + }; + + return ( +
+
+ + + + + } + data-cy={"testInputSearchVerificationRequest"} + backgroundColor={colors.white} + onChange={({ target }) => setContent(target.value)} + onKeyDown={({ key }) => { + if (key === "Enter") { + handleInputSearch(); + } + }} + /> + +
+ {verificationRequests && ( + + + {t("verificationRequest:searchResultsTitle")} + + {isLoading && } + {verificationRequests.length > 0 ? ( + + ) : ( + {t("verificationRequest:noResultsMessage")} + )} + + )} +
+ ); +}; + +export default VerificationRequestSearch; diff --git a/src/machines/reviewTask/context.ts b/src/machines/reviewTask/context.ts index f1e8dc024..15e684f1f 100644 --- a/src/machines/reviewTask/context.ts +++ b/src/machines/reviewTask/context.ts @@ -41,7 +41,6 @@ const buildState = ( reviewData: { usersId: [], isSensitive: false, - group: [], rejected: false, }, review, diff --git a/src/machines/reviewTask/events.ts b/src/machines/reviewTask/events.ts index 1b738ba9a..c6a3d0a73 100644 --- a/src/machines/reviewTask/events.ts +++ b/src/machines/reviewTask/events.ts @@ -40,7 +40,6 @@ type InformativeNewsReviewData = { type RequestReviewData = { usersId: string[]; isSensitive: boolean; - group: any[]; rejected: boolean; }; diff --git a/src/machines/reviewTask/reviewTaskMachine.ts b/src/machines/reviewTask/reviewTaskMachine.ts index 9c5e38311..7fec5e194 100644 --- a/src/machines/reviewTask/reviewTaskMachine.ts +++ b/src/machines/reviewTask/reviewTaskMachine.ts @@ -42,10 +42,6 @@ export const transitionHandler = (state) => { const { reviewData, review } = state.context; const nextState = typeof value !== "string" ? Object.keys(value)[0] : value; - const shouldUpdateVerificationRequest = - (value === "published" || value === "rejectedRequest") && - reportModel === ReportModelEnum.Request; - const shouldNotUpdateReviewTask = event === Events.reject || event === Events.selectedCrossChecking || @@ -77,16 +73,12 @@ export const transitionHandler = (state) => { }; api.createReviewTask(reviewTask, t, event) - .then(async () => { - if (shouldUpdateVerificationRequest) { - const redirectUrl = `/claim/create?verificationRequest=${target}`; - await verificationRequestApi.updateVerificationRequest(target, { - ...reviewData, - }); - - if (value === "published") { - window.location.href = redirectUrl; - } + .then(() => { + if ( + reportModel === ReportModelEnum.Request && + value === "published" + ) { + window.location.href = `/claim/create?verificationRequest=${target}`; } return event === Events.goback From 90f8749766f8b64a188f7dfc82801a464e97eaae Mon Sep 17 00:00:00 2001 From: pepermao Date: Wed, 31 Jul 2024 11:58:58 +0200 Subject: [PATCH 3/7] Fixed code smells and improve typescript typing --- newrelic_agent.log | 4 + src/components/Form/InputSearch.tsx | 8 +- .../ManageVerificationRequestGroup.tsx | 2 +- .../VerificationRequestDisplay.tsx | 5 +- .../VerificationRequestProvider.tsx | 76 ++++++++++++------- .../VerificationRequestResultList.tsx | 3 +- src/machines/reviewTask/reviewTaskMachine.ts | 1 - .../verification-request-review-page.tsx | 5 +- src/types/Content.ts | 6 +- src/types/Group.ts | 4 +- src/types/VerificationRequest.ts | 7 +- 11 files changed, 75 insertions(+), 46 deletions(-) diff --git a/newrelic_agent.log b/newrelic_agent.log index b83321520..5a0174041 100644 --- a/newrelic_agent.log +++ b/newrelic_agent.log @@ -38,3 +38,7 @@ {"v":0,"level":30,"name":"newrelic","hostname":"Pepe-Zenbook-UX3404VA-Q420VA","pid":1152344,"time":"2024-05-30T22:19:11.828Z","msg":"Using New Relic for Node.js. Agent version: 11.5.0; Node version: v18.14.0."} {"v":0,"level":30,"name":"newrelic","hostname":"Pepe-Zenbook-UX3404VA-Q420VA","pid":1152344,"time":"2024-05-30T22:19:11.890Z","msg":"Using AsyncLocalContextManager"} {"v":0,"level":50,"name":"newrelic","hostname":"Pepe-Zenbook-UX3404VA-Q420VA","pid":1152344,"time":"2024-05-30T22:19:11.891Z","msg":"New Relic for Node.js was unable to bootstrap itself due to an error:","stack":"Error: New Relic requires that you name this application!\nSet app_name in your newrelic.js or newrelic.cjs file or set environment variable\nNEW_RELIC_APP_NAME. Not starting!\n at createAgent (/home/pepe/aletheia/node_modules/newrelic/index.js:160:11)\n at initialize (/home/pepe/aletheia/node_modules/newrelic/index.js:105:15)\n at Object. (/home/pepe/aletheia/node_modules/newrelic/index.js:39:3)\n at Module._compile (node:internal/modules/cjs/loader:1226:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)\n at Module.load (node:internal/modules/cjs/loader:1089:32)\n at Module._load (node:internal/modules/cjs/loader:930:12)\n at Module.require (node:internal/modules/cjs/loader:1113:19)\n at require (node:internal/modules/cjs/helpers:103:18)\n at Object. (/home/pepe/aletheia/dist/server/main.js:16:5)\n at Module._compile (node:internal/modules/cjs/loader:1226:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)\n at Module.load (node:internal/modules/cjs/loader:1089:32)\n at Module._load (node:internal/modules/cjs/loader:930:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)\n at node:internal/main/run_main_module:23:47","message":"New Relic requires that you name this application!\nSet app_name in your newrelic.js or newrelic.cjs file or set environment variable\nNEW_RELIC_APP_NAME. Not starting!"} +{"v":0,"level":30,"name":"newrelic","hostname":"Pepe-Zenbook-UX3404VA-Q420VA","pid":212209,"time":"2024-07-31T09:50:42.327Z","msg":"Unable to find configuration file. If a configuration file is desired (common for non-containerized environments), a base configuration file can be copied from /home/pepe/aletheia/node_modules/newrelic/newrelic.js and renamed to \"newrelic.js\" in the directory from which you will start your application. Attempting to start agent using environment variables."} +{"v":0,"level":30,"name":"newrelic","hostname":"Pepe-Zenbook-UX3404VA-Q420VA","pid":212209,"time":"2024-07-31T09:50:42.330Z","msg":"Using New Relic for Node.js. Agent version: 11.5.0; Node version: v18.14.0."} +{"v":0,"level":30,"name":"newrelic","hostname":"Pepe-Zenbook-UX3404VA-Q420VA","pid":212209,"time":"2024-07-31T09:50:42.380Z","msg":"Using AsyncLocalContextManager"} +{"v":0,"level":50,"name":"newrelic","hostname":"Pepe-Zenbook-UX3404VA-Q420VA","pid":212209,"time":"2024-07-31T09:50:42.381Z","msg":"New Relic for Node.js was unable to bootstrap itself due to an error:","stack":"Error: New Relic requires that you name this application!\nSet app_name in your newrelic.js or newrelic.cjs file or set environment variable\nNEW_RELIC_APP_NAME. Not starting!\n at createAgent (/home/pepe/aletheia/node_modules/newrelic/index.js:160:11)\n at initialize (/home/pepe/aletheia/node_modules/newrelic/index.js:105:15)\n at Object. (/home/pepe/aletheia/node_modules/newrelic/index.js:39:3)\n at Module._compile (node:internal/modules/cjs/loader:1226:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)\n at Module.load (node:internal/modules/cjs/loader:1089:32)\n at Module._load (node:internal/modules/cjs/loader:930:12)\n at Module.require (node:internal/modules/cjs/loader:1113:19)\n at require (node:internal/modules/cjs/helpers:103:18)\n at Object. (/home/pepe/aletheia/dist/server/main.js:16:5)\n at Module._compile (node:internal/modules/cjs/loader:1226:14)\n at Module._extensions..js (node:internal/modules/cjs/loader:1280:10)\n at Module.load (node:internal/modules/cjs/loader:1089:32)\n at Module._load (node:internal/modules/cjs/loader:930:12)\n at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:81:12)\n at node:internal/main/run_main_module:23:47","message":"New Relic requires that you name this application!\nSet app_name in your newrelic.js or newrelic.cjs file or set environment variable\nNEW_RELIC_APP_NAME. Not starting!"} diff --git a/src/components/Form/InputSearch.tsx b/src/components/Form/InputSearch.tsx index 767834615..6251ea2fb 100644 --- a/src/components/Form/InputSearch.tsx +++ b/src/components/Form/InputSearch.tsx @@ -8,8 +8,8 @@ const InputSearchStyled = styled(Input.Search)` display: none; } span.ant-input-affix-wrapper { - background: ${({ backgroundColor }) => - backgroundColor ? backgroundColor : colors.lightGray}; + background: ${({ backgroundColor = colors.lightGray }) => + backgroundColor}; box-shadow: 0px 2px 2px rgba(0, 0, 0, 0.25); border-radius: 4px; &:focus-within { @@ -17,8 +17,8 @@ const InputSearchStyled = styled(Input.Search)` } } input.ant-input { - background: ${({ backgroundColor }) => - backgroundColor ? backgroundColor : colors.lightGray}; + background: ${({ backgroundColor = colors.lightGray }) => + backgroundColor}; color: ${colors.blackSecondary}; &::placeholder { color: ${colors.blackSecondary}; diff --git a/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx b/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx index 1959410b5..37b67021a 100644 --- a/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx +++ b/src/components/VerificationRequest/ManageVerificationRequestGroup.tsx @@ -28,7 +28,7 @@ const ManageVerificationRequestGroup = ({ }} > {label} - {suffix && suffix} + {suffix} ); }; diff --git a/src/components/VerificationRequest/VerificationRequestDisplay.tsx b/src/components/VerificationRequest/VerificationRequestDisplay.tsx index 3402a6af5..e5c69d360 100644 --- a/src/components/VerificationRequest/VerificationRequestDisplay.tsx +++ b/src/components/VerificationRequest/VerificationRequestDisplay.tsx @@ -18,9 +18,10 @@ const VerificationRequestDisplay = ({ content }) => { (c) => c._id !== content._id ); - const onRemove = (id) => { + const onRemove = async (id: string) => { setIsLoading(true); - onRemoveVerificationRequest(id).then(() => setIsLoading(false)); + await onRemoveVerificationRequest(id); + setIsLoading(false); }; return ( diff --git a/src/components/VerificationRequest/VerificationRequestProvider.tsx b/src/components/VerificationRequest/VerificationRequestProvider.tsx index c8f9312c1..2c1e88e4a 100644 --- a/src/components/VerificationRequest/VerificationRequestProvider.tsx +++ b/src/components/VerificationRequest/VerificationRequestProvider.tsx @@ -1,12 +1,20 @@ -import { createContext, useState } from "react"; +import { createContext, useMemo, useState } from "react"; import verificationRequestApi from "../../api/verificationRequestApi"; +import { VerificationRequest } from "../../types/VerificationRequest"; +import { Group } from "../../types/Group"; interface IVerificationRequestContext { - recommendations?: any; - addRecommendation?: any; - group?: any; - removeFromGroup?: any; - onRemoveVerificationRequest?: any; + recommendations?: VerificationRequest[]; + addRecommendation?: (verificationRequest: VerificationRequest) => void; + group?: Group; + removeFromGroup?: (id: string) => void; + onRemoveVerificationRequest?: (id: string) => void; +} + +interface IVerificationRequestProvider { + verificationRequest: VerificationRequest; + baseRecommendations: VerificationRequest[]; + children: React.ReactNode; } export const VerificationRequestContext = @@ -16,11 +24,14 @@ export const VerificationRequestProvider = ({ verificationRequest, baseRecommendations, children, -}) => { - const [recommendations, setRecommendations] = useState(baseRecommendations); - const [group, setGroup] = useState(verificationRequest.group); +}: IVerificationRequestProvider) => { + const [recommendations, setRecommendations] = + useState(baseRecommendations); + const [group, setGroup] = useState(verificationRequest.group); - const addRecommendation = (verificationRequestLiked) => { + const addRecommendation = ( + newVerificationRequest: VerificationRequest + ): void => { const groupContent = group.content.filter( (v) => v._id !== verificationRequest._id ); @@ -28,23 +39,23 @@ export const VerificationRequestProvider = ({ verificationRequestApi.updateVerificationRequest( verificationRequest._id, { - group: [...groupContent, verificationRequestLiked], + group: [...groupContent, newVerificationRequest], } ); setRecommendations((prev) => - prev.filter((v) => v._id !== verificationRequestLiked._id) + prev.filter((v) => v._id !== newVerificationRequest._id) ); - addInGroup(verificationRequestLiked); + addInGroup(newVerificationRequest); }; - const addInGroup = (newVerificationRequest) => { + const addInGroup = (newVerificationRequest: VerificationRequest): void => { setGroup((prev) => ({ ...prev, content: [...prev.content, newVerificationRequest], })); }; - const removeFromGroup = (verificationRequestId) => { + const removeFromGroup = (verificationRequestId: string): void => { const contentGroup = group.content.filter( (verificationRequest) => verificationRequest?._id !== verificationRequestId @@ -56,16 +67,18 @@ export const VerificationRequestProvider = ({ })); }; - const onRemoveVerificationRequest = async (id) => { + const onRemoveVerificationRequest = async ( + verificationRequestId: string + ): Promise => { try { await verificationRequestApi.removeVerificationRequestFromGroup( - id, + verificationRequestId, { group: group._id, } ); - removeFromGroup(id); + removeFromGroup(verificationRequestId); } catch (e) { console.error( "Error while removing verification request from group", @@ -74,16 +87,25 @@ export const VerificationRequestProvider = ({ } }; + const value = useMemo( + () => ({ + recommendations, + group, + addRecommendation, + removeFromGroup, + onRemoveVerificationRequest, + }), + [ + recommendations, + group, + addRecommendation, + removeFromGroup, + onRemoveVerificationRequest, + ] + ); + return ( - + {children} ); diff --git a/src/components/VerificationRequest/VerificationRequestResultList.tsx b/src/components/VerificationRequest/VerificationRequestResultList.tsx index f0d3c1dd9..95df99772 100644 --- a/src/components/VerificationRequest/VerificationRequestResultList.tsx +++ b/src/components/VerificationRequest/VerificationRequestResultList.tsx @@ -15,8 +15,6 @@ const VerificationRequestResultList = ({ results }) => { if (verificationRequest._id === verificationRequestId) { return true; } - - continue; } return false; @@ -36,6 +34,7 @@ const VerificationRequestResultList = ({ results }) => { style={{ minHeight: "100%" }} actions={[ { return createMachine< diff --git a/src/pages/verification-request-review-page.tsx b/src/pages/verification-request-review-page.tsx index b7ff37645..993161e8c 100644 --- a/src/pages/verification-request-review-page.tsx +++ b/src/pages/verification-request-review-page.tsx @@ -12,15 +12,16 @@ import { currentNameSpace } from "../atoms/namespace"; import ClaimReviewView from "../components/ClaimReview/ClaimReviewView"; import { ReviewTaskTypeEnum } from "../machines/reviewTask/enums"; import { VerificationRequestProvider } from "../components/VerificationRequest/VerificationRequestProvider"; +import { VerificationRequest } from "../types/VerificationRequest"; export interface SourceReviewPageProps { - verificationRequest: any; //TODO: Create Verification Request type + verificationRequest: VerificationRequest; sitekey: string; reviewTask: any; hideDescriptions: object; websocketUrl: string; nameSpace: string; - recommendations: any[]; + recommendations: VerificationRequest[]; } const SourceReviewPage: NextPage = (props) => { diff --git a/src/types/Content.ts b/src/types/Content.ts index 92b97e68a..574c0c049 100644 --- a/src/types/Content.ts +++ b/src/types/Content.ts @@ -1,9 +1,9 @@ export interface Content { _id?: string; topics?: any[]; - type: string; + type?: string; data_hash: string; - props: any; + props?: any; content: string; - reviewTaskType: string; + reviewTaskType?: string; } diff --git a/src/types/Group.ts b/src/types/Group.ts index 30159047e..e795486f6 100644 --- a/src/types/Group.ts +++ b/src/types/Group.ts @@ -1,6 +1,6 @@ -import { VerificationnRequest } from "./VerificationRequest"; +import { VerificationRequest } from "./VerificationRequest"; export type Group = { _id: string; - content: VerificationnRequest[]; + content: VerificationRequest[]; }; diff --git a/src/types/VerificationRequest.ts b/src/types/VerificationRequest.ts index ef1de9e15..8ed0ef346 100644 --- a/src/types/VerificationRequest.ts +++ b/src/types/VerificationRequest.ts @@ -1,9 +1,12 @@ +import { Group } from "./Group"; + export type VerificationRequest = { data_hash: string; content: string; isSensitive: boolean; rejected: boolean; - group: string[]; + group: Group; date: Date; - sources: string[]; + sources?: string[]; + _id: string; }; From e9d67392b583ce3206c3b2e76d2bb4765d1ecb9f Mon Sep 17 00:00:00 2001 From: pepermao Date: Wed, 31 Jul 2024 12:08:21 +0200 Subject: [PATCH 4/7] Fixed cypress tests --- .../VerificationRequest/VerificationRequestDrawer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/VerificationRequest/VerificationRequestDrawer.tsx b/src/components/VerificationRequest/VerificationRequestDrawer.tsx index 594cfb5ec..d893d83a5 100644 --- a/src/components/VerificationRequest/VerificationRequestDrawer.tsx +++ b/src/components/VerificationRequest/VerificationRequestDrawer.tsx @@ -26,8 +26,8 @@ const VerificationRequestDrawer = ({ {t("verificationRequest:verificationRequestTitle")}s - {groupContent.length > 0 ? ( - groupContent.map(({ _id, content }) => ( + {groupContent?.length > 0 ? ( + groupContent?.map(({ _id, content }) => ( Date: Wed, 31 Jul 2024 12:30:48 +0200 Subject: [PATCH 5/7] Prevent regular users to trigger actions that needs permissions in the user interface --- .../VerificationRequestAlert.tsx | 119 +++++++++++------- .../VerificationRequestDisplay.tsx | 30 +++-- .../VerificationRequestMainContent.tsx | 24 ++-- 3 files changed, 108 insertions(+), 65 deletions(-) diff --git a/src/components/VerificationRequest/VerificationRequestAlert.tsx b/src/components/VerificationRequest/VerificationRequestAlert.tsx index 3c2221008..197231aef 100644 --- a/src/components/VerificationRequest/VerificationRequestAlert.tsx +++ b/src/components/VerificationRequest/VerificationRequestAlert.tsx @@ -1,4 +1,4 @@ -import React, { useContext } from "react"; +import React, { useContext, useMemo } from "react"; import { Col } from "antd"; import AletheiaButton from "../Button"; import { useTranslation } from "next-i18next"; @@ -6,9 +6,13 @@ import AletheiaAlert from "../AletheiaAlert"; import { ReviewTaskMachineContext } from "../../machines/reviewTask/ReviewTaskMachineProvider"; import { publishedSelector } from "../../machines/reviewTask/selectors"; import { useSelector } from "@xstate/react"; +import { useAtom } from "jotai"; +import { currentUserRole } from "../../atoms/currentUser"; +import { Roles } from "../../types/enums"; const VerificationRequestAlert = ({ targetId, verificationRequestId }) => { const { t } = useTranslation(); + const [role] = useAtom(currentUserRole); const { machineService, publishedReview } = useContext( ReviewTaskMachineContext ); @@ -16,52 +20,77 @@ const VerificationRequestAlert = ({ targetId, verificationRequestId }) => { useSelector(machineService, publishedSelector) || publishedReview?.review; - const alertProps = { - type: "warning", - showIcon: !targetId, - message: !targetId ? ( - t("verificationRequest:createClaimFromVerificationRequest") - ) : ( -
- - {t("verificationRequest:openVerificationRequestClaimLabel")} - - - {t( - "verificationRequest:openVerificationRequestClaimButton" - )} - -
- ), - description: !targetId ? ( - - {t("seo:claimCreateTitle")} - - ) : null, - }; + const shouldShowAlert = useMemo(() => { + return (!targetId && isPublished && role !== Roles.Regular) || targetId; + }, [targetId, isPublished, role]); + + const alertContent = useMemo(() => { + if (!targetId && isPublished && role !== Roles.Regular) { + return { + type: "warning", + showIcon: true, + message: t( + "verificationRequest:createClaimFromVerificationRequest" + ), + description: ( + + {t("seo:claimCreateTitle")} + + ), + }; + } + if (targetId) { + return { + type: "warning", + showIcon: false, + message: ( +
+ + {t( + "verificationRequest:openVerificationRequestClaimLabel" + )} + + + {t( + "verificationRequest:openVerificationRequestClaimButton" + )} + +
+ ), + description: null, + }; + } + return null; + }, [targetId, isPublished, role, t, verificationRequestId]); + + if (!shouldShowAlert || !alertContent) { + return null; + } return ( - {isPublished && } + + + ); }; diff --git a/src/components/VerificationRequest/VerificationRequestDisplay.tsx b/src/components/VerificationRequest/VerificationRequestDisplay.tsx index e5c69d360..59612816d 100644 --- a/src/components/VerificationRequest/VerificationRequestDisplay.tsx +++ b/src/components/VerificationRequest/VerificationRequestDisplay.tsx @@ -6,10 +6,14 @@ import { VerificationRequestContext } from "./VerificationRequestProvider"; import VerificationRequestDrawer from "./VerificationRequestDrawer"; import VerificationRequestMainContent from "./VerificationRequestMainContent"; import VerificationRequestSearch from "./VerificationRequestSearch"; +import { Roles } from "../../types/enums"; +import { currentUserRole } from "../../atoms/currentUser"; +import { useAtom } from "jotai"; const VerificationRequestDisplay = ({ content }) => { const [open, setOpen] = useState(false); const [isLoading, setIsLoading] = useState(false); + const [role] = useAtom(currentUserRole); const { group, onRemoveVerificationRequest } = useContext( VerificationRequestContext ); @@ -37,17 +41,21 @@ const VerificationRequestDisplay = ({ content }) => { openDrawer={() => setOpen(true)} /> - - - - - setOpen(false)} - onRemove={onRemove} - /> + {role !== Roles.Regular && ( + <> + + + + + setOpen(false)} + onRemove={onRemove} + /> + + )} ); }; diff --git a/src/components/VerificationRequest/VerificationRequestMainContent.tsx b/src/components/VerificationRequest/VerificationRequestMainContent.tsx index 1e221e38c..34fa0f969 100644 --- a/src/components/VerificationRequest/VerificationRequestMainContent.tsx +++ b/src/components/VerificationRequest/VerificationRequestMainContent.tsx @@ -4,6 +4,9 @@ import { useTranslation } from "next-i18next"; import VerificationRequestCard from "./VerificationRequestCard"; import { useAppSelector } from "../../store/store"; import ManageVerificationRequestGroup from "./ManageVerificationRequestGroup"; +import { useAtom } from "jotai"; +import { currentUserRole } from "../../atoms/currentUser"; +import { Roles } from "../../types/enums"; const VerificationRequestMainContent = ({ verificationRequestGroup, @@ -11,6 +14,7 @@ const VerificationRequestMainContent = ({ openDrawer, }) => { const { t } = useTranslation(); + const [role] = useAtom(currentUserRole); const { vw } = useAppSelector((state) => state); return ( @@ -19,15 +23,17 @@ const VerificationRequestMainContent = ({ {t("verificationRequest:verificationRequestTitle")} - {!vw.xs && verificationRequestGroup?.length > 0 && ( - - )} + {!vw.xs && + role !== Roles.Regular && + verificationRequestGroup?.length > 0 && ( + + )} ); }; From 3b07b0ced20bc1902ff2cf70d6171f34ec051553 Mon Sep 17 00:00:00 2001 From: pepermao Date: Fri, 2 Aug 2024 16:28:26 +0200 Subject: [PATCH 6/7] Fixed and prevent undefined errors --- .../verification-request.controller.ts | 5 +++-- .../VerificationRequest/VerificationRequestResultList.tsx | 8 +++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/verification-request/verification-request.controller.ts b/server/verification-request/verification-request.controller.ts index c7a476f97..f33ceccc0 100644 --- a/server/verification-request/verification-request.controller.ts +++ b/server/verification-request/verification-request.controller.ts @@ -136,8 +136,9 @@ export class VerificationRequestController { dataHash ); - const recommendationFilter = - verificationRequest.group.content.map((v: any) => v._id) || []; + const recommendationFilter = verificationRequest.group?.content?.map( + (v: any) => v._id + ) || [verificationRequest?._id]; const recommendations = await this.verificationRequestService.findSimilarRequests( diff --git a/src/components/VerificationRequest/VerificationRequestResultList.tsx b/src/components/VerificationRequest/VerificationRequestResultList.tsx index 95df99772..2408ea849 100644 --- a/src/components/VerificationRequest/VerificationRequestResultList.tsx +++ b/src/components/VerificationRequest/VerificationRequestResultList.tsx @@ -11,9 +11,11 @@ const VerificationRequestResultList = ({ results }) => { const { group, addRecommendation } = useContext(VerificationRequestContext); const checkIfIsInGroup = (verificationRequestId): boolean => { - for (const verificationRequest of group.content) { - if (verificationRequest._id === verificationRequestId) { - return true; + if (group?.content) { + for (const verificationRequest of group?.content) { + if (verificationRequest?._id === verificationRequestId) { + return true; + } } } From ad75805c168a1474b8c791b03679b54beb9afcb3 Mon Sep 17 00:00:00 2001 From: pepermao Date: Fri, 2 Aug 2024 16:40:49 +0200 Subject: [PATCH 7/7] Fixed code smells --- .../VerificationRequest/VerificationRequestResultList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/VerificationRequest/VerificationRequestResultList.tsx b/src/components/VerificationRequest/VerificationRequestResultList.tsx index 2408ea849..233277eb2 100644 --- a/src/components/VerificationRequest/VerificationRequestResultList.tsx +++ b/src/components/VerificationRequest/VerificationRequestResultList.tsx @@ -11,8 +11,8 @@ const VerificationRequestResultList = ({ results }) => { const { group, addRecommendation } = useContext(VerificationRequestContext); const checkIfIsInGroup = (verificationRequestId): boolean => { - if (group?.content) { - for (const verificationRequest of group?.content) { + if (group && Array.isArray(group.content)) { + for (const verificationRequest of group.content) { if (verificationRequest?._id === verificationRequestId) { return true; }