From 6ab1256c4a78ceda6eadebd79d270e776151d897 Mon Sep 17 00:00:00 2001 From: Ashley Terstriep <60187543+aterstriep@users.noreply.github.com> Date: Wed, 4 Dec 2024 11:58:42 -0600 Subject: [PATCH 01/10] Fix ID bug Removed duplicate ID on container in previous PR. Caused a bug where mention suggestions box does not render correctly. Replacing duplicate ID prop for now. --- src/components/MentionTextArea/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/MentionTextArea/index.tsx b/src/components/MentionTextArea/index.tsx index a670cb8a5a..5044544cea 100644 --- a/src/components/MentionTextArea/index.tsx +++ b/src/components/MentionTextArea/index.tsx @@ -212,6 +212,7 @@ const MentionTextArea = React.forwardRef( return ( <> Date: Wed, 4 Dec 2024 12:28:39 -0600 Subject: [PATCH 02/10] Rename reviewers query --- ...view.ts => GetSystemIntakeGRBReviewers.ts} | 2 +- src/gql/gen/graphql.ts | 38 +++++++++---------- .../GRBReviewerForm/AddReviewerFromEua.tsx | 4 +- .../GRBReview/GRBReviewerForm/index.test.tsx | 22 +++++------ .../GRBReview/GRBReviewerForm/index.tsx | 4 +- .../GovernanceReviewTeam/GRBReview/index.tsx | 6 +-- src/views/GovernanceReviewTeam/index.tsx | 4 +- 7 files changed, 40 insertions(+), 40 deletions(-) rename src/gql/apolloGQL/grbReview/{GetSystemIntakeGRBReview.ts => GetSystemIntakeGRBReviewers.ts} (86%) diff --git a/src/gql/apolloGQL/grbReview/GetSystemIntakeGRBReview.ts b/src/gql/apolloGQL/grbReview/GetSystemIntakeGRBReviewers.ts similarity index 86% rename from src/gql/apolloGQL/grbReview/GetSystemIntakeGRBReview.ts rename to src/gql/apolloGQL/grbReview/GetSystemIntakeGRBReviewers.ts index f17ea45400..9f0a9461f8 100644 --- a/src/gql/apolloGQL/grbReview/GetSystemIntakeGRBReview.ts +++ b/src/gql/apolloGQL/grbReview/GetSystemIntakeGRBReviewers.ts @@ -4,7 +4,7 @@ import SystemIntakeGRBReviewer from './SystemIntakeGRBReviewer'; export default gql(/* GraphQL */ ` ${SystemIntakeGRBReviewer} - query GetSystemIntakeGRBReview($id: UUID!) { + query GetSystemIntakeGRBReviewers($id: UUID!) { systemIntake(id: $id) { id grbReviewStartedAt diff --git a/src/gql/gen/graphql.ts b/src/gql/gen/graphql.ts index 1cf80816b9..da22b128bb 100644 --- a/src/gql/gen/graphql.ts +++ b/src/gql/gen/graphql.ts @@ -3278,12 +3278,12 @@ export type GetSystemIntakeGRBDiscussionsQueryVariables = Exact<{ export type GetSystemIntakeGRBDiscussionsQuery = { __typename: 'Query', systemIntake?: { __typename: 'SystemIntake', id: UUID, grbDiscussions: Array<{ __typename: 'SystemIntakeGRBReviewDiscussion', initialPost: { __typename: 'SystemIntakeGRBReviewDiscussionPost', id: UUID, content: HTML, votingRole?: SystemIntakeGRBReviewerVotingRole | null, grbRole?: SystemIntakeGRBReviewerRole | null, systemIntakeID: UUID, createdAt: Time, createdByUserAccount: { __typename: 'UserAccount', id: UUID, commonName: string } }, replies: Array<{ __typename: 'SystemIntakeGRBReviewDiscussionPost', id: UUID, content: HTML, votingRole?: SystemIntakeGRBReviewerVotingRole | null, grbRole?: SystemIntakeGRBReviewerRole | null, systemIntakeID: UUID, createdAt: Time, createdByUserAccount: { __typename: 'UserAccount', id: UUID, commonName: string } }> }> } | null }; -export type GetSystemIntakeGRBReviewQueryVariables = Exact<{ +export type GetSystemIntakeGRBReviewersQueryVariables = Exact<{ id: Scalars['UUID']['input']; }>; -export type GetSystemIntakeGRBReviewQuery = { __typename: 'Query', systemIntake?: { __typename: 'SystemIntake', id: UUID, grbReviewStartedAt?: Time | null, grbReviewers: Array<{ __typename: 'SystemIntakeGRBReviewer', id: UUID, grbRole: SystemIntakeGRBReviewerRole, votingRole: SystemIntakeGRBReviewerVotingRole, userAccount: { __typename: 'UserAccount', id: UUID, username: string, commonName: string, email: string } }> } | null }; +export type GetSystemIntakeGRBReviewersQuery = { __typename: 'Query', systemIntake?: { __typename: 'SystemIntake', id: UUID, grbReviewStartedAt?: Time | null, grbReviewers: Array<{ __typename: 'SystemIntakeGRBReviewer', id: UUID, grbRole: SystemIntakeGRBReviewerRole, votingRole: SystemIntakeGRBReviewerVotingRole, userAccount: { __typename: 'UserAccount', id: UUID, username: string, commonName: string, email: string } }> } | null }; export type SystemIntakeWithReviewRequestedFragment = { __typename: 'SystemIntake', id: UUID, requestName?: string | null, requesterName?: string | null, requesterComponent?: string | null, grbDate?: Time | null }; @@ -3853,8 +3853,8 @@ export type GetSystemIntakeGRBDiscussionsQueryHookResult = ReturnType; export type GetSystemIntakeGRBDiscussionsSuspenseQueryHookResult = ReturnType; export type GetSystemIntakeGRBDiscussionsQueryResult = Apollo.QueryResult; -export const GetSystemIntakeGRBReviewDocument = gql` - query GetSystemIntakeGRBReview($id: UUID!) { +export const GetSystemIntakeGRBReviewersDocument = gql` + query GetSystemIntakeGRBReviewers($id: UUID!) { systemIntake(id: $id) { id grbReviewStartedAt @@ -3866,37 +3866,37 @@ export const GetSystemIntakeGRBReviewDocument = gql` ${SystemIntakeGRBReviewerFragmentDoc}`; /** - * __useGetSystemIntakeGRBReviewQuery__ + * __useGetSystemIntakeGRBReviewersQuery__ * - * To run a query within a React component, call `useGetSystemIntakeGRBReviewQuery` and pass it any options that fit your needs. - * When your component renders, `useGetSystemIntakeGRBReviewQuery` returns an object from Apollo Client that contains loading, error, and data properties + * To run a query within a React component, call `useGetSystemIntakeGRBReviewersQuery` and pass it any options that fit your needs. + * When your component renders, `useGetSystemIntakeGRBReviewersQuery` returns an object from Apollo Client that contains loading, error, and data properties * you can use to render your UI. * * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * * @example - * const { data, loading, error } = useGetSystemIntakeGRBReviewQuery({ + * const { data, loading, error } = useGetSystemIntakeGRBReviewersQuery({ * variables: { * id: // value for 'id' * }, * }); */ -export function useGetSystemIntakeGRBReviewQuery(baseOptions: Apollo.QueryHookOptions & ({ variables: GetSystemIntakeGRBReviewQueryVariables; skip?: boolean; } | { skip: boolean; }) ) { +export function useGetSystemIntakeGRBReviewersQuery(baseOptions: Apollo.QueryHookOptions & ({ variables: GetSystemIntakeGRBReviewersQueryVariables; skip?: boolean; } | { skip: boolean; }) ) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useQuery(GetSystemIntakeGRBReviewDocument, options); + return Apollo.useQuery(GetSystemIntakeGRBReviewersDocument, options); } -export function useGetSystemIntakeGRBReviewLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { +export function useGetSystemIntakeGRBReviewersLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions) { const options = {...defaultOptions, ...baseOptions} - return Apollo.useLazyQuery(GetSystemIntakeGRBReviewDocument, options); + return Apollo.useLazyQuery(GetSystemIntakeGRBReviewersDocument, options); } -export function useGetSystemIntakeGRBReviewSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions) { +export function useGetSystemIntakeGRBReviewersSuspenseQuery(baseOptions?: Apollo.SkipToken | Apollo.SuspenseQueryHookOptions) { const options = baseOptions === Apollo.skipToken ? baseOptions : {...defaultOptions, ...baseOptions} - return Apollo.useSuspenseQuery(GetSystemIntakeGRBReviewDocument, options); + return Apollo.useSuspenseQuery(GetSystemIntakeGRBReviewersDocument, options); } -export type GetSystemIntakeGRBReviewQueryHookResult = ReturnType; -export type GetSystemIntakeGRBReviewLazyQueryHookResult = ReturnType; -export type GetSystemIntakeGRBReviewSuspenseQueryHookResult = ReturnType; -export type GetSystemIntakeGRBReviewQueryResult = Apollo.QueryResult; +export type GetSystemIntakeGRBReviewersQueryHookResult = ReturnType; +export type GetSystemIntakeGRBReviewersLazyQueryHookResult = ReturnType; +export type GetSystemIntakeGRBReviewersSuspenseQueryHookResult = ReturnType; +export type GetSystemIntakeGRBReviewersQueryResult = Apollo.QueryResult; export const GetSystemIntakesWithReviewRequestedDocument = gql` query GetSystemIntakesWithReviewRequested { systemIntakesWithReviewRequested { @@ -5034,7 +5034,7 @@ export const TypedCreateSystemIntakeGRBReviewersDocument = {"kind":"Document","d export const TypedDeleteSystemIntakeGRBReviewerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"DeleteSystemIntakeGRBReviewer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DeleteSystemIntakeGRBReviewerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"deleteSystemIntakeGRBReviewer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const TypedgetGRBReviewersComparisonsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getGRBReviewersComparisons"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"compareGRBReviewersByIntakeID"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requestName"}},{"kind":"Field","name":{"kind":"Name","value":"reviewers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grbRole"}},{"kind":"Field","name":{"kind":"Name","value":"votingRole"}},{"kind":"Field","name":{"kind":"Name","value":"userAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"commonName"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}},{"kind":"Field","name":{"kind":"Name","value":"isCurrentReviewer"}}]}}]}}]}}]} as unknown as DocumentNode; export const TypedGetSystemIntakeGRBDiscussionsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSystemIntakeGRBDiscussions"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"systemIntake"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grbDiscussions"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SystemIntakeGRBReviewDiscussion"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SystemIntakeGRBReviewDiscussionPost"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SystemIntakeGRBReviewDiscussionPost"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"content"}},{"kind":"Field","name":{"kind":"Name","value":"votingRole"}},{"kind":"Field","name":{"kind":"Name","value":"grbRole"}},{"kind":"Field","name":{"kind":"Name","value":"createdByUserAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"commonName"}}]}},{"kind":"Field","name":{"kind":"Name","value":"systemIntakeID"}},{"kind":"Field","name":{"kind":"Name","value":"createdAt"}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SystemIntakeGRBReviewDiscussion"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SystemIntakeGRBReviewDiscussion"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"initialPost"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SystemIntakeGRBReviewDiscussionPost"}}]}},{"kind":"Field","name":{"kind":"Name","value":"replies"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SystemIntakeGRBReviewDiscussionPost"}}]}}]}}]} as unknown as DocumentNode; -export const TypedGetSystemIntakeGRBReviewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSystemIntakeGRBReview"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"systemIntake"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grbReviewStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"grbReviewers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grbRole"}},{"kind":"Field","name":{"kind":"Name","value":"votingRole"}},{"kind":"Field","name":{"kind":"Name","value":"userAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"commonName"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]} as unknown as DocumentNode; +export const TypedGetSystemIntakeGRBReviewersDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSystemIntakeGRBReviewers"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"id"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UUID"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"systemIntake"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"id"},"value":{"kind":"Variable","name":{"kind":"Name","value":"id"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grbReviewStartedAt"}},{"kind":"Field","name":{"kind":"Name","value":"grbReviewers"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grbRole"}},{"kind":"Field","name":{"kind":"Name","value":"votingRole"}},{"kind":"Field","name":{"kind":"Name","value":"userAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"commonName"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]} as unknown as DocumentNode; export const TypedGetSystemIntakesWithReviewRequestedDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GetSystemIntakesWithReviewRequested"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"systemIntakesWithReviewRequested"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SystemIntakeWithReviewRequested"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SystemIntakeWithReviewRequested"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SystemIntake"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"requestName"}},{"kind":"Field","name":{"kind":"Name","value":"requesterName"}},{"kind":"Field","name":{"kind":"Name","value":"requesterComponent"}},{"kind":"Field","name":{"kind":"Name","value":"grbDate"}}]}}]} as unknown as DocumentNode; export const TypedStartGRBReviewDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"StartGRBReview"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"StartGRBReviewInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"startGRBReview"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const TypedUpdateSystemIntakeGRBReviewerDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"UpdateSystemIntakeGRBReviewer"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"UpdateSystemIntakeGRBReviewerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateSystemIntakeGRBReviewer"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"SystemIntakeGRBReviewer"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"grbRole"}},{"kind":"Field","name":{"kind":"Name","value":"votingRole"}},{"kind":"Field","name":{"kind":"Name","value":"userAccount"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"username"}},{"kind":"Field","name":{"kind":"Name","value":"commonName"}},{"kind":"Field","name":{"kind":"Name","value":"email"}}]}}]}}]} as unknown as DocumentNode; diff --git a/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/AddReviewerFromEua.tsx b/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/AddReviewerFromEua.tsx index f2fa86fa1d..85993a6d58 100644 --- a/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/AddReviewerFromEua.tsx +++ b/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/AddReviewerFromEua.tsx @@ -5,7 +5,7 @@ import { ErrorMessage } from '@hookform/error-message'; import { yupResolver } from '@hookform/resolvers/yup'; import { Button, Dropdown, Form, FormGroup } from '@trussworks/react-uswds'; import { - GetSystemIntakeGRBReviewDocument, + GetSystemIntakeGRBReviewersDocument, SystemIntakeGRBReviewerFragment, useUpdateSystemIntakeGRBReviewerMutation } from 'gql/gen/graphql'; @@ -51,7 +51,7 @@ const AddReviewerFromEua = ({ const [updateGRBReviewer] = useUpdateSystemIntakeGRBReviewerMutation({ refetchQueries: [ { - query: GetSystemIntakeGRBReviewDocument, + query: GetSystemIntakeGRBReviewersDocument, variables: { id: systemId } } ] diff --git a/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.test.tsx b/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.test.tsx index 67c11b7bae..7f913b9170 100644 --- a/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.test.tsx +++ b/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.test.tsx @@ -9,9 +9,9 @@ import { GetGRBReviewersComparisonsDocument, GetGRBReviewersComparisonsQuery, GetGRBReviewersComparisonsQueryVariables, - GetSystemIntakeGRBReviewDocument, - GetSystemIntakeGRBReviewQuery, - GetSystemIntakeGRBReviewQueryVariables, + GetSystemIntakeGRBReviewersDocument, + GetSystemIntakeGRBReviewersQuery, + GetSystemIntakeGRBReviewersQueryVariables, SystemIntakeGRBReviewerFragment, SystemIntakeGRBReviewerRole, SystemIntakeGRBReviewerVotingRole, @@ -133,14 +133,14 @@ const updateSystemIntakeGRBReviewerQuery: MockedQuery< } }; -const getSystemIntakeGRBReviewQuery = ( +const getSystemIntakeGRBReviewersQuery = ( reviewer?: SystemIntakeGRBReviewerFragment ): MockedQuery< - GetSystemIntakeGRBReviewQuery, - GetSystemIntakeGRBReviewQueryVariables + GetSystemIntakeGRBReviewersQuery, + GetSystemIntakeGRBReviewersQueryVariables > => ({ request: { - query: GetSystemIntakeGRBReviewDocument, + query: GetSystemIntakeGRBReviewersDocument, variables: { id: systemIntake.id } @@ -191,8 +191,8 @@ describe('GRB reviewer form', () => { cedarContactsQuery('Je'), cedarContactsQuery('Jerry Seinfeld'), createSystemIntakeGRBReviewersQuery, - getSystemIntakeGRBReviewQuery(), - getSystemIntakeGRBReviewQuery(grbReviewer) + getSystemIntakeGRBReviewersQuery(), + getSystemIntakeGRBReviewersQuery(grbReviewer) ]} > @@ -279,8 +279,8 @@ describe('GRB reviewer form', () => { getGRBReviewersComparisonsQuery, cedarContactsQuery(contactLabel), updateSystemIntakeGRBReviewerQuery, - getSystemIntakeGRBReviewQuery(grbReviewer), - getSystemIntakeGRBReviewQuery(updatedGRBReviewer) + getSystemIntakeGRBReviewersQuery(grbReviewer), + getSystemIntakeGRBReviewersQuery(updatedGRBReviewer) ]} > diff --git a/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.tsx b/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.tsx index dff06c2757..eee6a2063e 100644 --- a/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.tsx +++ b/src/views/GovernanceReviewTeam/GRBReview/GRBReviewerForm/index.tsx @@ -3,7 +3,7 @@ import { Trans, useTranslation } from 'react-i18next'; import { useHistory, useParams } from 'react-router-dom'; import { Grid, Icon } from '@trussworks/react-uswds'; import { - GetSystemIntakeGRBReviewDocument, + GetSystemIntakeGRBReviewersDocument, SystemIntakeGRBReviewerFragment, useCreateSystemIntakeGRBReviewersMutation } from 'gql/gen/graphql'; @@ -41,7 +41,7 @@ const GRBReviewerForm = ({ }>(); const [mutate] = useCreateSystemIntakeGRBReviewersMutation({ - refetchQueries: [GetSystemIntakeGRBReviewDocument] + refetchQueries: [GetSystemIntakeGRBReviewersDocument] }); const createGRBReviewers = (reviewers: GRBReviewerFields[]) => diff --git a/src/views/GovernanceReviewTeam/GRBReview/index.tsx b/src/views/GovernanceReviewTeam/GRBReview/index.tsx index 23ec2cd731..3c5ddabb5c 100644 --- a/src/views/GovernanceReviewTeam/GRBReview/index.tsx +++ b/src/views/GovernanceReviewTeam/GRBReview/index.tsx @@ -12,7 +12,7 @@ import { ModalHeading } from '@trussworks/react-uswds'; import { - GetSystemIntakeGRBReviewDocument, + GetSystemIntakeGRBReviewersDocument, SystemIntakeGRBReviewerFragment, useDeleteSystemIntakeGRBReviewerMutation, useStartGRBReviewMutation @@ -80,7 +80,7 @@ const GRBReview = ({ const { showMessage } = useMessage(); const [mutate] = useDeleteSystemIntakeGRBReviewerMutation({ - refetchQueries: [GetSystemIntakeGRBReviewDocument] + refetchQueries: [GetSystemIntakeGRBReviewersDocument] }); const [startGRBReview] = useStartGRBReviewMutation({ @@ -91,7 +91,7 @@ const GRBReview = ({ }, refetchQueries: [ { - query: GetSystemIntakeGRBReviewDocument, + query: GetSystemIntakeGRBReviewersDocument, variables: { id } } ] diff --git a/src/views/GovernanceReviewTeam/index.tsx b/src/views/GovernanceReviewTeam/index.tsx index a06c90d064..269f9bd579 100644 --- a/src/views/GovernanceReviewTeam/index.tsx +++ b/src/views/GovernanceReviewTeam/index.tsx @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import { useSelector } from 'react-redux'; import { Route, Switch, useParams } from 'react-router-dom'; -import { useGetSystemIntakeGRBReviewQuery } from 'gql/gen/graphql'; +import { useGetSystemIntakeGRBReviewersQuery } from 'gql/gen/graphql'; import { useFlags } from 'launchdarkly-react-client-sdk'; import PageLoading from 'components/PageLoading'; @@ -24,7 +24,7 @@ const GovernanceReviewTeam = () => { id: string; }>(); - const { data, loading } = useGetSystemIntakeGRBReviewQuery({ + const { data, loading } = useGetSystemIntakeGRBReviewersQuery({ variables: { id } From 06ae4cab6cb62d50440e93feead4949828c61b2b Mon Sep 17 00:00:00 2001 From: Ashley Terstriep <60187543+aterstriep@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:53:44 -0600 Subject: [PATCH 03/10] Replace mock mention suggestions with grbReviewers --- .../MentionTextArea/MentionList.tsx | 4 +- src/components/MentionTextArea/index.tsx | 45 +++++-------------- src/types/discussions.ts | 13 ++++++ src/views/DiscussionBoard/Discussion.test.tsx | 2 + src/views/DiscussionBoard/Discussion.tsx | 7 ++- src/views/DiscussionBoard/StartDiscussion.tsx | 7 ++- .../components/DiscussionForm.tsx | 5 ++- src/views/DiscussionBoard/index.tsx | 37 ++++++++++++--- .../GRBReview/Discussions.test.tsx | 6 +-- .../GRBReview/Discussions.tsx | 13 +++++- .../GovernanceReviewTeam/GRBReview/index.tsx | 1 + 11 files changed, 89 insertions(+), 51 deletions(-) diff --git a/src/components/MentionTextArea/MentionList.tsx b/src/components/MentionTextArea/MentionList.tsx index 4570d4b57b..0190cb6132 100644 --- a/src/components/MentionTextArea/MentionList.tsx +++ b/src/components/MentionTextArea/MentionList.tsx @@ -38,7 +38,7 @@ const MentionList = forwardRef((props: any, ref) => { if (item) { props.command({ - id: item.username, + 'data-id-db': item.id, label: item.displayName, 'tag-type': item.tagType }); @@ -93,7 +93,7 @@ const MentionList = forwardRef((props: any, ref) => { props.items?.map((item: any, index: any) => ( - )) - ) : ( -
{t('noResults')}
- )} - - ); -}); + })); + + return ( +
+ {props.items?.length ? ( + props.items?.map((item, index) => ( + + )) + ) : ( +
{t('noResults')}
+ )} +
+ ); + } +); export default MentionList; diff --git a/src/components/MentionTextArea/index.tsx b/src/components/MentionTextArea/index.tsx index 17d3b39cac..c1c251a9d4 100644 --- a/src/components/MentionTextArea/index.tsx +++ b/src/components/MentionTextArea/index.tsx @@ -1,11 +1,14 @@ import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import Document from '@tiptap/extension-document'; -import Mention from '@tiptap/extension-mention'; +import Mention, { MentionOptions } from '@tiptap/extension-mention'; import Paragraph from '@tiptap/extension-paragraph'; import Text from '@tiptap/extension-text'; import { EditorContent, + EditorEvents, + Node, + NodeViewProps, NodeViewWrapper, ReactNodeViewRenderer, useEditor @@ -15,17 +18,17 @@ import classNames from 'classnames'; import Alert from 'components/shared/Alert'; import IconButton from 'components/shared/IconButton'; -import { MentionSuggestion } from 'types/discussions'; +import { MentionAttributes, MentionSuggestion } from 'types/discussions'; import suggestion from './suggestion'; -import { getMentions } from './util'; +import getMentions from './util'; import './index.scss'; -/* The rendered Mention after selected from MentionList -This component can be any react jsx component, but must be wrapped in -Attrs of selected mention are accessed through node prop */ -const MentionComponent = ({ node }: { node: any }) => { +/** The rendered Mention after selected from MentionList */ +// This component can be any react jsx component, but must be wrapped in +const MentionComponent = ({ node }: NodeViewProps) => { + // Get attributes of selected mention const { label } = node.attrs; if (!label) return null; @@ -37,9 +40,14 @@ const MentionComponent = ({ node }: { node: any }) => { ); }; -/* Extended TipTap Mention class with additional attributes -Additionally sets a addNodeView to render custo JSX as mention */ -const CustomMention = Mention.extend({ +/** + * Extended TipTap Mention class with additional attributes + * + * Additionally sets a addNodeView to render custo JSX as mention + */ +const CustomMention: Node< + MentionOptions +> = Mention.extend({ atom: true, selectable: true, addAttributes() { @@ -165,7 +173,9 @@ const MentionTextArea = React.forwardRef( } }, // Sets an alert if a mention is selected, and users/teams will be emailed - onSelectionUpdate: ({ editor: input }: any) => { + onSelectionUpdate: ({ + editor: input + }: EditorEvents['selectionUpdate']) => { setTagAlert(!!getMentions(input?.getJSON()).length); }, content diff --git a/src/components/MentionTextArea/suggestion.ts b/src/components/MentionTextArea/suggestion.ts index d105b77ad8..970a841120 100644 --- a/src/components/MentionTextArea/suggestion.ts +++ b/src/components/MentionTextArea/suggestion.ts @@ -1,36 +1,53 @@ import { ReactRenderer } from '@tiptap/react'; -import tippy from 'tippy.js'; +import { SuggestionOptions } from '@tiptap/suggestion'; +import tippy, { GetReferenceClientRect, Instance } from 'tippy.js'; + +import { + MentionAttributes, + MentionListOnKeyDown, + MentionSuggestion, + MentionSuggestionProps +} from 'types/discussions'; import MentionList, { SuggestionLoading } from './MentionList'; /* Returns the current textarea/RTE editor dimension to append the Mentionslist dropdown MentionList should have the same width as this parent clientRect */ -const getClientRect = (props: any) => { - const editorID = props.editor.options.editorProps.attributes.id; - const elem = document.getElementById(editorID); - const rect = elem?.getBoundingClientRect(); - const mentionRect = props.clientRect(); +const getClientRect = ({ + editor, + clientRect +}: MentionSuggestionProps): GetReferenceClientRect => { + const { element } = editor.options; + const rect = element.getBoundingClientRect(); + const mentionRect = clientRect?.(); return () => new DOMRect( rect?.left, - mentionRect.y, - mentionRect.width, - mentionRect.height + mentionRect?.y, + mentionRect?.width, + mentionRect?.height ); }; -const suggestion = { +const suggestion: Omit< + SuggestionOptions, + 'editor' +> = { allowSpaces: true, render: () => { - let reactRenderer: any; - let spinner: any; - let popup: any; + let reactRenderer: ReactRenderer< + MentionListOnKeyDown, + MentionSuggestionProps + >; + + let spinner: Partial; + let popup: Partial; return { // If we had async initial data - load a spinning symbol until onStart gets called // We have hardcoded in memory data for current implementation, doesn't currently get called - onBeforeStart: (props: any) => { + onBeforeStart: props => { if (!props.clientRect) { return; } @@ -40,7 +57,7 @@ const suggestion = { editor: props.editor }); - spinner = tippy('body', { + [spinner] = tippy('body', { getReferenceClientRect: getClientRect(props), appendTo: props.editor.options.element, content: reactRenderer.element, @@ -52,19 +69,19 @@ const suggestion = { }, // Render any available suggestions when mention trigger is first called - @ - onStart: (props: any) => { + onStart: props => { if (!props.clientRect) { return; } - spinner[0].hide(); + spinner.hide?.(); reactRenderer = new ReactRenderer(MentionList, { props, editor: props.editor }); - popup = tippy('body', { + [popup] = tippy('body', { getReferenceClientRect: getClientRect(props), appendTo: props.editor.options.element, content: reactRenderer.element, @@ -76,46 +93,46 @@ const suggestion = { }, // When async data/suggestions return, hide the spinner and show the updated list - onUpdate(props: any) { + onUpdate: props => { reactRenderer.updateProps(props); if (!props.clientRect) { return; } - popup[0].setProps({ + popup.setProps?.({ getReferenceClientRect: getClientRect(props) }); - spinner[0].setProps({ + + spinner.setProps?.({ getReferenceClientRect: getClientRect(props) }); - spinner[0].hide(); + spinner.hide?.(); - popup[0].show(); + popup.show?.(); }, // If a valid character key, render the spinner until onUpdate gets called to rerender updated list - onKeyDown(props: any) { + onKeyDown: props => { if (props.event.key === 'Escape') { - popup[0].hide(); - spinner[0].hide(); + popup.hide?.(); + spinner.hide?.(); return true; } if (props.event.key.length === 1 || props.event.key === 'Backspace') { - popup[0].hide(); - - spinner[0].show(); + popup.hide?.(); + spinner.show?.(); } - return reactRenderer.ref?.onKeyDown(props); + return !!reactRenderer?.ref && reactRenderer.ref.onKeyDown(props); }, onExit() { - popup[0].destroy(); - spinner[0].destroy(); + popup.destroy?.(); + spinner.destroy?.(); reactRenderer.destroy(); } }; diff --git a/src/components/MentionTextArea/util.tsx b/src/components/MentionTextArea/util.tsx index 3fb38d603f..add243683e 100644 --- a/src/components/MentionTextArea/util.tsx +++ b/src/components/MentionTextArea/util.tsx @@ -1,15 +1,27 @@ -// Possible Util to extract only mentions from content -// eslint-disable-next-line import/prefer-default-export -export const getMentions = (data: any) => { - const mentions: any = []; - - data?.content?.forEach((para: any) => { - para?.content?.forEach((content: any) => { - if (content?.type === 'mention') { - mentions.push(content?.attrs); +import { JSONContent } from '@tiptap/core'; + +/** + * Returns array of mentions from Tiptap input JSON data + * + * @example getMentions(input?.getJSON()) + */ +const getMentions = < + /** Optional type param for mention attributes if return array needs to be typed */ + MentionAttrsType extends Record = Record +>( + data: JSONContent +): MentionAttrsType[] => { + const mentions: MentionAttrsType[] = []; + + data?.content?.forEach(paragraph => { + paragraph?.content?.forEach(content => { + if (content?.attrs && content?.type === 'mention') { + mentions.push(content.attrs as MentionAttrsType); } }); }); return mentions; }; + +export default getMentions; diff --git a/src/types/discussions.ts b/src/types/discussions.ts index b995c47c3a..c8bbdc65bc 100644 --- a/src/types/discussions.ts +++ b/src/types/discussions.ts @@ -1,7 +1,9 @@ +import { SuggestionProps } from '@tiptap/suggestion'; import { TagType } from 'gql/gen/graphql'; import { AlertProps } from 'components/shared/Alert'; +/** Error and success alerts for the discussion form */ export type DiscussionAlert = | (Omit & { message: string }) | null; @@ -16,3 +18,24 @@ export type MentionSuggestion = displayName: string; id: string; }; + +/** HTML attributes used for rendering mentions */ +export type MentionAttributes = { + /** Text displayed within mention `span` tag */ + label: string; + /** UUID for `USER_ACCOUNT` tag types */ + 'data-id-db': string; + 'tag-type': TagType; +}; + +/** Suggestion props for use within Tiptap configurations */ +export type MentionSuggestionProps = SuggestionProps< + MentionSuggestion, + MentionAttributes +>; + +/** `MentionList` component forwarded ref attributes */ +export type MentionListOnKeyDown = { + /** onKeyDown handler for rendering the suggestions popup and loading spinner */ + onKeyDown: ({ event }: { event: KeyboardEvent }) => boolean; +}; From 95775c7624bc97262da368c89ef79ffdd48a076f Mon Sep 17 00:00:00 2001 From: Ashley Terstriep <60187543+aterstriep@users.noreply.github.com> Date: Tue, 10 Dec 2024 00:21:46 -0600 Subject: [PATCH 06/10] Add `data-label` attr for rendering mentions --- pkg/sanitization/tagged_html.go | 2 +- src/components/MentionTextArea/MentionList.tsx | 1 + src/types/discussions.ts | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/sanitization/tagged_html.go b/pkg/sanitization/tagged_html.go index b9a884fd82..2673af6e93 100644 --- a/pkg/sanitization/tagged_html.go +++ b/pkg/sanitization/tagged_html.go @@ -31,6 +31,6 @@ func createTaggedHTMLPolicy() *bluemonday.Policy { policy := bluemonday.NewPolicy() // rules for tags policy.AllowElements("span", "p") - policy.AllowAttrs("data-type", "class", "tag-type", "data-id-db").OnElements("span") + policy.AllowAttrs("data-type", "class", "tag-type", "data-id-db", "data-label").OnElements("span") return policy } diff --git a/src/components/MentionTextArea/MentionList.tsx b/src/components/MentionTextArea/MentionList.tsx index 32b0f4de68..3db1a7c55f 100644 --- a/src/components/MentionTextArea/MentionList.tsx +++ b/src/components/MentionTextArea/MentionList.tsx @@ -47,6 +47,7 @@ const MentionList = forwardRef( props.command({ 'tag-type': item.tagType, label: item.displayName, + 'data-label': item.displayName, 'data-id-db': item.tagType === TagType.USER_ACCOUNT ? item.id : '' }); } diff --git a/src/types/discussions.ts b/src/types/discussions.ts index c8bbdc65bc..0e939d5304 100644 --- a/src/types/discussions.ts +++ b/src/types/discussions.ts @@ -23,6 +23,8 @@ export type MentionSuggestion = export type MentionAttributes = { /** Text displayed within mention `span` tag */ label: string; + /** Label attribute for rendering mentions */ + 'data-label': string; /** UUID for `USER_ACCOUNT` tag types */ 'data-id-db': string; 'tag-type': TagType; From 543ad8a759421ca18ac57a4172b4a3f5c34d6e35 Mon Sep 17 00:00:00 2001 From: Ashley Terstriep <60187543+aterstriep@users.noreply.github.com> Date: Wed, 11 Dec 2024 09:18:29 -0600 Subject: [PATCH 07/10] Snapshot --- .../MentionTextArea/__snapshots__/index.test.tsx.snap | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/MentionTextArea/__snapshots__/index.test.tsx.snap b/src/components/MentionTextArea/__snapshots__/index.test.tsx.snap index 81ff351e47..92b1570e5f 100644 --- a/src/components/MentionTextArea/__snapshots__/index.test.tsx.snap +++ b/src/components/MentionTextArea/__snapshots__/index.test.tsx.snap @@ -4,6 +4,7 @@ exports[`MentionTextArea component > renders the component to view text 1`] = `
renders the editable text area component 1`
Date: Wed, 11 Dec 2024 11:41:32 -0600 Subject: [PATCH 08/10] Fix no results translation and padding --- src/components/MentionTextArea/MentionList.tsx | 4 ++-- src/i18n/en-US/general.ts | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/MentionTextArea/MentionList.tsx b/src/components/MentionTextArea/MentionList.tsx index 3db1a7c55f..4fde7ad184 100644 --- a/src/components/MentionTextArea/MentionList.tsx +++ b/src/components/MentionTextArea/MentionList.tsx @@ -35,7 +35,7 @@ const scrollIntoView = () => { /** Renders the list of suggestions within `MentionTextArea` */ const MentionList = forwardRef( (props, ref) => { - const { t } = useTranslation('discussionsMisc'); + const { t } = useTranslation('general'); const [selectedIndex, setSelectedIndex] = useState(0); @@ -113,7 +113,7 @@ const MentionList = forwardRef( )) ) : ( -
{t('noResults')}
+ {t('noResults')} )}
); diff --git a/src/i18n/en-US/general.ts b/src/i18n/en-US/general.ts index aa24b0f750..ca365b9012 100644 --- a/src/i18n/en-US/general.ts +++ b/src/i18n/en-US/general.ts @@ -11,6 +11,7 @@ const general = { remove: 'Remove', pageLoading: 'Loading the page', loadingResults: 'Loading results', + noResults: 'No results', noInfoToDisplay: 'No information to display', noDataAvailable: 'No data available', readMore: 'Read more', From f5f3ec0a523533b29788f1f5463a783ca94c090b Mon Sep 17 00:00:00 2001 From: Ashley Terstriep <60187543+aterstriep@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:25:05 -0600 Subject: [PATCH 09/10] Display most recent discussion --- .../GRBReview/Discussions.tsx | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx b/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx index 8fb9d8afc5..cb5277df38 100644 --- a/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx +++ b/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx @@ -10,6 +10,7 @@ import { import Alert from 'components/shared/Alert'; import CollapsableLink from 'components/shared/CollapsableLink'; import IconButton from 'components/shared/IconButton'; +import Spinner from 'components/Spinner'; import useDiscussionParams from 'hooks/useDiscussionParams'; import DiscussionBoard from 'views/DiscussionBoard'; import DiscussionPost from 'views/DiscussionBoard/components/DiscussionPost'; @@ -30,7 +31,7 @@ const Discussions = ({ const { pushDiscussionQuery } = useDiscussionParams(); - const { data } = useGetSystemIntakeGRBDiscussionsQuery({ + const { data, loading } = useGetSystemIntakeGRBDiscussionsQuery({ variables: { id: systemIntakeID } }); @@ -43,7 +44,9 @@ const Discussions = ({ ).length; const recentDiscussion = - grbDiscussions.length > 0 ? grbDiscussions[0] : undefined; + grbDiscussions.length > 0 + ? grbDiscussions[grbDiscussions.length - 1] + : undefined; return ( <> @@ -143,11 +146,16 @@ const Discussions = ({

{t('general.mostRecentActivity')}

- + + {loading ? ( + + ) : ( + + )} ) : ( // If no discussions, show alert From 16e038f69d685ea2f706dc284c49ec77118878ed Mon Sep 17 00:00:00 2001 From: Ashley Terstriep <60187543+aterstriep@users.noreply.github.com> Date: Wed, 11 Dec 2024 13:32:32 -0600 Subject: [PATCH 10/10] Update recent activity to consider replies --- src/components/MentionTextArea/index.tsx | 2 +- src/components/MentionTextArea/util.tsx | 54 ++++++++++++++++++- .../GRBReview/Discussions.tsx | 11 ++-- 3 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/components/MentionTextArea/index.tsx b/src/components/MentionTextArea/index.tsx index c1c251a9d4..61e86c2573 100644 --- a/src/components/MentionTextArea/index.tsx +++ b/src/components/MentionTextArea/index.tsx @@ -21,7 +21,7 @@ import IconButton from 'components/shared/IconButton'; import { MentionAttributes, MentionSuggestion } from 'types/discussions'; import suggestion from './suggestion'; -import getMentions from './util'; +import { getMentions } from './util'; import './index.scss'; diff --git a/src/components/MentionTextArea/util.tsx b/src/components/MentionTextArea/util.tsx index add243683e..e3cf2973ab 100644 --- a/src/components/MentionTextArea/util.tsx +++ b/src/components/MentionTextArea/util.tsx @@ -5,7 +5,7 @@ import { JSONContent } from '@tiptap/core'; * * @example getMentions(input?.getJSON()) */ -const getMentions = < +export const getMentions = < /** Optional type param for mention attributes if return array needs to be typed */ MentionAttrsType extends Record = Record >( @@ -24,4 +24,56 @@ const getMentions = < return mentions; }; +/** Generic discussion type with only `createdAt` props */ +interface DiscussionTimestamps { + initialPost: { + createdAt: string; + }; + replies: { createdAt: string }[]; +} + +/** Compare initialPost with replies and find the most recent `createdAt` value */ +const getMostRecentTimestamp = ({ + initialPost, + replies +}: DiscussionType) => { + if (replies.length === 0) return initialPost.createdAt; + + return replies.reduce( + (latest, current) => { + return current.createdAt > latest.createdAt ? current : latest; + }, + // Start with the initialPost + initialPost + // Return the `createdAt` value + ).createdAt; +}; + +/** + * Find and return the discussion object with the most recent activity + * + * Returns undefined if discussions array is empty + */ +export const getMostRecentDiscussion = < + DiscussionType extends DiscussionTimestamps +>( + discussions: DiscussionType[] +): DiscussionType | undefined => { + if (discussions.length === 0) return undefined; + + return discussions.reduce((mostRecentDiscussion, currentDiscussion) => { + /** Latest createdAt value for current discussion */ + const currentDiscussionCreatedAt = + getMostRecentTimestamp(currentDiscussion); + + // Latest createdAt value for most recent discussion + const mostRecentDiscussionCreatedAt = + getMostRecentTimestamp(mostRecentDiscussion); + + return currentDiscussionCreatedAt > mostRecentDiscussionCreatedAt + ? currentDiscussion + : mostRecentDiscussion; + }); +}; + export default getMentions; diff --git a/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx b/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx index cb5277df38..c985c078b2 100644 --- a/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx +++ b/src/views/GovernanceReviewTeam/GRBReview/Discussions.tsx @@ -3,10 +3,12 @@ import { Trans, useTranslation } from 'react-i18next'; import { Button, Icon } from '@trussworks/react-uswds'; import classNames from 'classnames'; import { + SystemIntakeGRBReviewDiscussionFragment, SystemIntakeGRBReviewerFragment, useGetSystemIntakeGRBDiscussionsQuery } from 'gql/gen/graphql'; +import { getMostRecentDiscussion } from 'components/MentionTextArea/util'; import Alert from 'components/shared/Alert'; import CollapsableLink from 'components/shared/CollapsableLink'; import IconButton from 'components/shared/IconButton'; @@ -35,7 +37,8 @@ const Discussions = ({ variables: { id: systemIntakeID } }); - const grbDiscussions = data?.systemIntake?.grbDiscussions; + const grbDiscussions: SystemIntakeGRBReviewDiscussionFragment[] | undefined = + data?.systemIntake?.grbDiscussions; if (!grbDiscussions) return null; @@ -43,10 +46,8 @@ const Discussions = ({ discussion => discussion.replies.length === 0 ).length; - const recentDiscussion = - grbDiscussions.length > 0 - ? grbDiscussions[grbDiscussions.length - 1] - : undefined; + /** Discussion with latest activity - either when discussion was created or latest reply */ + const recentDiscussion = getMostRecentDiscussion(grbDiscussions); return ( <>