Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
## Added
- `Custom Question` additions [#929]
- Added new `CustomQuestionEdit` page
- Added new `QuestionCustomize` page
- Added new `CustomQuestionNew` page
- Added new `AddCustomQuestion`, `UpdateCustomQuestion`, `RemoveCustomQuestion`, `AddQuestionCustomization`, `UpdateQuestionCustomization`, `RemoveQuestionCustomization` mutations
- Added `CustomQuestion` and `QuestionCustomizationByVersionedQuestion` mutations
- Added new routes for Custom Queries to `utils/routes.ts`
- Added `CustomSectionEdit`, and `CreateCustomSectionPage` for adding and editing Custom Sections [#928]
- Added new `/template/customizations/[templateCustomizationOverview]` page [#927]
- Added new `CustomizedTemplate` components directory and new `CustomizedQuestionEdit` and `CustomizedSectionEdit` components [#927]
Expand All @@ -13,6 +20,14 @@
- Added related works project overview page [#700]

## Updated
- `Custom Question` updates [#929]
- Moved `addQuestionMutation` out of `QuestionView` so that `QuestionView` can be shared for both `BASE` and `CUSTOM` questions
- Updated `QuestionTypeSelectPage` to include the `addQuestionMutation` and `getDisplayOrder` and pass those to `QuestionAdd`
- Added use of `Loading` component in `TemplateCreatePage`
- Added `fetchPolicy` of `cache-and-network` to `TemplateCustomiationOverview` query so that we get updated data when user returns from editing section or question
- Updated form components with `isDisabled` prop so that users customizing existing questions can see a disabled version of the form fields
- Updated `QuestionAdd` to accept more props and be more flexible for shared use with Custom Questions
- Updated `QuestionView` to accept more props like `orgGuidance` so it can be shared with Custom Questions
- Updated `ReposSelector` and `RepoSelectorForAnswer` components to use the new `Re3RepositoryTypesListDocument`, `Re3SubjectListDocument`, and `Re3byUrIsDocument` queries [#113]
- Updated `ResearchOutputAnswerComponent` so that we don't get a duplicate `save` CTAs when on the `SingleResearchOutputComponent` page [#113]
- Updated `SectionCustomizePage` component for customizing existing sections [#928]
Expand Down
4 changes: 2 additions & 2 deletions app/[locale]/account/profile/__tests__/page.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const mockUserData = {
me: {
givenName: 'John',
surName: 'Doe',
affiliation: { name: 'Test Institution', uri: 'test-uri' },
affiliation: { name: 'Test Institution', uri: 'test-uri', displayName: 'Test Institution' },
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the affiliationName.displayName to the MeQuery for the QuestionPreview so that we could be explicit about which Org added the guidance, therefore I was required to add it to some unit test mocks.

emails: [{ id: '1', email: '[email protected]', isPrimary: true, isConfirmed: true }],
languageId: 'en',
},
Expand Down Expand Up @@ -222,7 +222,7 @@ describe('ProfilePage', () => {
me: {
givenName: 'John',
surName: 'Doe',
affiliation: { name: '', uri: '' },
affiliation: { name: '', uri: '', displayName: '' },
emails: [{ id: '1', email: '[email protected]', isPrimary: true, isConfirmed: true }],
languageId: 'en',
},
Expand Down
6 changes: 6 additions & 0 deletions app/[locale]/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import Loading from '@/components/Loading';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added this new component just in case, so page loads can fall back to this if it takes longer than 300ms

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. This is a good idea


{/** Next.js only shows this if it takes longer than ~300ms to load */ }
export default function LocaleLoading() {
return <Loading />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"affiliation": {
"id": 1,
"name": "California Digital Library",
"displayName": "California Digital Library",
"searchName": "California Digital Library | cdlib.org | CDL ",
"uri": "https://ror.org/03yrm5c26",
"__typename": "Affiliation"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ describe("CommentList", () => {
role: UserRole.Admin,
emails: [],
errors: {},
affiliation: { id: 1, name: "Test Org", searchName: "test-org", uri: "https://test.org" }
affiliation: { id: 1, name: "Test Org", searchName: "test-org", uri: "https://test.org", displayName: "Test Org" }
}
},
planOwners: [1, 7], // Current user and admin are plan owners
Expand Down Expand Up @@ -178,7 +178,7 @@ describe("CommentList", () => {
role: UserRole.Admin,
emails: [],
errors: {},
affiliation: { id: 1, name: "Test Org", searchName: "test-org", uri: "https://test.org" }
affiliation: { id: 1, name: "Test Org", searchName: "test-org", uri: "https://test.org", displayName: "Test Org" }
};

const props = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ const createMockMeQuery = (overrides = {}) => ({
affiliation: {
id: 1,
name: "Test Organization",
displayName: "Test Organization",
searchName: "test-organization",
uri: "https://test.org"
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ describe('useComments', () => {
id: 1,
name: "Test Organization",
searchName: "test-organization",
uri: "https://test.org"
uri: "https://test.org",
displayName: "Test Organization"
},
}
};
Expand Down
2 changes: 1 addition & 1 deletion app/[locale]/template/[templateId]/q/[q_slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ import {
import {
isOptionsType,
getOverrides,
} from './hooks/useEditQuestion';
} from '@/app/hooks/useEditQuestion';
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the useEditQuestion to a shared location since the new Custom Question pages use it too

import styles from './questionEdit.module.scss';

const QuestionEdit = () => {
Expand Down
27 changes: 26 additions & 1 deletion app/[locale]/template/[templateId]/q/new/__tests__/page.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,17 @@ import { useParams, useRouter, useSearchParams } from 'next/navigation';
import { useQueryStep } from '@/app/[locale]/template/[templateId]/q/new/utils';
import QuestionTypeSelectPage from "../page";
import { mockScrollIntoView, mockScrollTo } from "@/__mocks__/common";

import { useMutation, useQuery } from '@apollo/client/react';
expect.extend(toHaveNoViolations);

jest.mock('@apollo/client/react', () => ({
useQuery: jest.fn(),
useMutation: jest.fn(),
}));

const mockUseQuery = jest.mocked(useQuery);
const mockUseMutation = jest.mocked(useMutation);

jest.mock('next/navigation', () => ({
useParams: jest.fn(),
useRouter: jest.fn(),
Expand Down Expand Up @@ -76,6 +84,23 @@ describe("QuestionTypeSelectPage", () => {
} as unknown as ReturnType<typeof useSearchParams>;
});

mockUseMutation.mockImplementation(() => [
jest.fn().mockResolvedValue({ data: {} }),
{ loading: false, error: undefined }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
] as any);

mockUseQuery.mockImplementation(() => ({
data: {
questions: [
{ displayOrder: 1 },
{ displayOrder: 2 },
]
},
loading: false,
error: undefined,
/* eslint-disable @typescript-eslint/no-explicit-any */
}) as any);

});

Expand Down
64 changes: 64 additions & 0 deletions app/[locale]/template/[templateId]/q/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ import {
Text
} from "react-aria-components";


Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This page was updated to pass in new props to the shared QuestionAdd, because it is now shared with the Custom Question pages

// GraphQL
import { useMutation, useQuery } from '@apollo/client/react';
import {
AddQuestionDocument,
QuestionsDisplayOrderDocument,
} from '@/generated/graphql';


// Components
import PageHeader from "@/components/PageHeader";
import { ContentContainer, LayoutContainer, } from '@/components/Container';
Expand All @@ -30,6 +39,17 @@ import { QuestionFormatInterface } from '@/app/types';
import styles from './newQuestion.module.scss';
import { getQuestionTypes } from "@/utils/questionTypeHandlers";

const TemplateQuestionBreadcrumbs = ({ templateId, sectionId, Global }: { templateId: string; sectionId: string; Global: (key: string, values?: Record<string, string | number>) => string }) => {
return (
<Breadcrumbs>
<Breadcrumb><Link href={routePath('projects.index')}>{Global('breadcrumbs.home')}</Link></Breadcrumb>
<Breadcrumb><Link href={routePath('template.index', { templateId })}>{Global('breadcrumbs.templates')}</Link></Breadcrumb>
<Breadcrumb><Link href={routePath('template.show', { templateId })}>{Global('breadcrumbs.editTemplate')}</Link></Breadcrumb>
<Breadcrumb><Link href={routePath('template.q.new', { templateId }, { section_id: sectionId, step: 1 })}>{Global('breadcrumbs.selectQuestionType')}</Link></Breadcrumb>
<Breadcrumb>{Global('breadcrumbs.question')}</Breadcrumb>
</Breadcrumbs >
)
}

const QuestionTypeSelectPage: React.FC = () => {
// Get templateId param
Expand Down Expand Up @@ -58,6 +78,37 @@ const QuestionTypeSelectPage: React.FC = () => {
const Global = useTranslations('Global');
const QuestionTypeSelect = useTranslations('QuestionTypeSelectPage');

// Initialize add and update question mutations
const [addQuestionMutation] = useMutation(AddQuestionDocument);

// Query request for questions to calculate max displayOrder
const { data: questionDisplayOrders } = useQuery(QuestionsDisplayOrderDocument, {
variables: {
sectionId: Number(sectionId)
},
skip: !sectionId
})

// Calculate the display order of the new question based on the last displayOrder number
const getDisplayOrder = useCallback(() => {
if (!questionDisplayOrders?.questions?.length) {
return 1;
}

// Filter out null/undefined questions and handle missing displayOrder
const validDisplayOrders = questionDisplayOrders.questions
.map(q => q?.displayOrder)
.filter((order): order is number => typeof order === 'number');

if (validDisplayOrders.length === 0) {
return 1;
}

const maxDisplayOrder = Math.max(...validDisplayOrders);
return maxDisplayOrder + 1;
}, [questionDisplayOrders]);


// Handle the selection of a question type
const handleSelect = (
{
Expand Down Expand Up @@ -247,6 +298,19 @@ const QuestionTypeSelectPage: React.FC = () => {
questionName={selectedQuestionType?.questionName ?? null}
questionJSON={selectedQuestionType?.questionJSON ?? ''}
sectionId={sectionId ? sectionId : ''}
breadcrumbs={<TemplateQuestionBreadcrumbs templateId={templateId} sectionId={sectionId} Global={Global} />}
backUrl={routePath('template.q.new', { templateId }, { section_id: sectionId, step: 1 })}
successUrl={routePath('template.show', { templateId })}
onSave={async (commonFields) => {
const input = {
templateId: Number(templateId),
sectionId: Number(sectionId),
displayOrder: getDisplayOrder(),
isDirty: true,
...commonFields,
};
await addQuestionMutation({ variables: { input } });
}}
/>
</>
)}
Expand Down
2 changes: 1 addition & 1 deletion app/[locale]/template/create/__tests__/page.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ describe('TemplateCreatePage', () => {
(useQueryStep as jest.Mock).mockReturnValue(null); // No step in query initially

render(<TemplateCreatePage />);
expect(screen.getByText('...messaging.loading')).toBeInTheDocument();
expect(screen.getByText('messaging.loading')).toBeInTheDocument();
});

it('should render step 1 form when step is 1', () => {
Expand Down
3 changes: 2 additions & 1 deletion app/[locale]/template/create/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import FormInput from '@/components/Form/FormInput';

import { debounce } from '@/hooks/debounce';
import { useQueryStep } from '@/app/[locale]/template/create/useQueryStep';
import Loading from '@/components/Loading';

const TemplateCreatePage: React.FC = () => {
const router = useRouter();
Expand Down Expand Up @@ -68,7 +69,7 @@ const TemplateCreatePage: React.FC = () => {

// TODO: Need to implement a shared loading component
if (step === null) {
return <div>...{Global('messaging.loading')}</div>
return <Loading />;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add Loading component here

}

return (
Expand Down
Loading