Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

216 provide templates when creating new entries unique carer language #233

Merged
Show file tree
Hide file tree
Changes from 11 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
8 changes: 8 additions & 0 deletions .changeset/flat-zoos-rescue.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@quassel/frontend": patch
"@quassel/backend": patch
"@quassel/mockup": patch
"@quassel/ui": patch
---

Allow selecting templates when entering calendar entries
2 changes: 1 addition & 1 deletion apps/backend/src/research/entries/entries.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Test, TestingModule } from "@nestjs/testing";
import { EntriesService } from "./entries.service";
import { getRepositoryToken } from "@mikro-orm/nestjs";
import { Entry } from "./entry.entity";
import { EntityManager } from "@mikro-orm/core";
import { EntityManager } from "@mikro-orm/postgresql";

describe("EntriesService", () => {
let service: EntriesService;
Expand Down
34 changes: 33 additions & 1 deletion apps/backend/src/research/entries/entries.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { EntityRepository, EntityManager, UniqueConstraintViolationException, FilterQuery } from "@mikro-orm/core";
import { EntityRepository, UniqueConstraintViolationException, FilterQuery } from "@mikro-orm/core";
import { InjectRepository } from "@mikro-orm/nestjs";
import { Injectable, UnprocessableEntityException } from "@nestjs/common";
import { EntryCreationDto, EntryMutationDto } from "./entry.dto";
import { Entry } from "./entry.entity";
import { EntityManager, raw } from "@mikro-orm/postgresql";

@Injectable()
export class EntriesService {
Expand Down Expand Up @@ -40,6 +41,37 @@
return (await this.entryRepository.findOneOrFail(filter, { populate: ["entryLanguages"] })).toObject();
}

/**
* Uniquely grouped entries by ratio, carer and language, that are used as templates when creating new entries for a participant.
*
* @param participantId to filter entries.
* @returns entry templates of participant.
*/
async findTemplatesForParticipant(participantId: number) {
const uniqueEntryGroups = this.em

Check warning on line 51 in apps/backend/src/research/entries/entries.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/entries/entries.service.ts#L50-L51

Added lines #L50 - L51 were not covered by tests
.createQueryBuilder(Entry, "e")
.select(["e.id"])
.distinctOn(raw("array_agg(ARRAY[el.language_id, el.ratio] ORDER BY el.language_id)"))
.join("e.entryLanguages", "el")
.join("e.questionnaire", "q")
.where({ "q.participant": participantId })
.groupBy("e.id");

const populatedUniqueEntries = await this.em

Check warning on line 60 in apps/backend/src/research/entries/entries.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/entries/entries.service.ts#L60

Added line #L60 was not covered by tests
.createQueryBuilder(Entry, "e")
.select("*")
.joinAndSelect("e.entryLanguages", "el")
.joinAndSelect("e.carer", "c")
.joinAndSelect("el.language", "l")
.where({ id: { $in: uniqueEntryGroups.getKnexQuery() } })
.getResultList();

return populatedUniqueEntries.map((entry) => {
const { entryLanguages, carer } = entry.toObject();
return { entryLanguages, carer: { ...carer } };

Check warning on line 71 in apps/backend/src/research/entries/entries.service.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/entries/entries.service.ts#L69-L71

Added lines #L69 - L71 were not covered by tests
stampaaaron marked this conversation as resolved.
Show resolved Hide resolved
});
}

async update(id: number, entryMutationDto: EntryMutationDto) {
const entry = await this.entryRepository.findOneOrFail(id, { populate: ["entryLanguages"] });

Expand Down
4 changes: 3 additions & 1 deletion apps/backend/src/research/entries/entry.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { ApiProperty, OmitType, PartialType } from "@nestjs/swagger";
import { ApiProperty, OmitType, PartialType, PickType } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsOptional, Min, Max, IsMilitaryTime } from "class-validator";
import { CarerDto } from "../../defaults/carers/carer.dto";
Expand Down Expand Up @@ -46,3 +46,5 @@ export class EntryCreationDto extends OmitType(EntryDto, ["id", "carer", "questi
entryLanguages: Array<EntryLanguageCreationDto>;
}
export class EntryMutationDto extends PartialType(EntryCreationDto) {}

export class EntryTemplateDto extends PickType(EntryDto, ["carer", "entryLanguages"]) {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Test, TestingModule } from "@nestjs/testing";
import { ParticipantsController } from "./participants.controller";
import { ParticipantsService } from "./participants.service";
import { QuestionnairesService } from "../questionnaires/questionnaires.service";
import { EntriesService } from "../entries/entries.service";

describe("ParticipantsController", () => {
let controller: ParticipantsController;
Expand All @@ -18,6 +19,10 @@ describe("ParticipantsController", () => {
provide: QuestionnairesService,
useValue: {},
},
{
provide: EntriesService,
useValue: {},
},
],
}).compile();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,17 @@
import { UserRole } from "../../system/users/user.entity";
import { QuestionnairesService } from "../questionnaires/questionnaires.service";
import { OneOrMany } from "../../types";
import { EntriesService } from "../entries/entries.service";
import { EntryTemplateDto } from "../entries/entry.dto";

@ApiTags("Participants")
@ApiExtraModels(ParticipantCreationDto)
@Controller("participants")
export class ParticipantsController {
constructor(
private readonly participantService: ParticipantsService,
private readonly questionnairesService: QuestionnairesService
private readonly questionnairesService: QuestionnairesService,
private readonly entriesService: EntriesService
) {}

@Post()
Expand Down Expand Up @@ -67,6 +70,13 @@
return { ...participant, latestQuestionnaire };
}

@Get(":id/entry-templates")
@ApiOperation({ summary: "Get a participant by ID" })
stampaaaron marked this conversation as resolved.
Show resolved Hide resolved
@ApiNotFoundResponse({ description: "Entity not found exception", type: ErrorResponseDto })
entryTemplates(@Param("id") id: string): Promise<EntryTemplateDto[]> {
return this.entriesService.findTemplatesForParticipant(+id);

Check warning on line 77 in apps/backend/src/research/participants/participants.controller.ts

View check run for this annotation

Codecov / codecov/patch

apps/backend/src/research/participants/participants.controller.ts#L77

Added line #L77 was not covered by tests
}

@Patch(":id")
@ApiOperation({ summary: "Update a participant by ID" })
update(@Param("id") id: string, @Body() participant: ParticipantMutationDto): Promise<ParticipantResponseDto> {
Expand Down
153 changes: 102 additions & 51 deletions apps/frontend/src/api.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,23 @@ export interface paths {
patch: operations["ParticipantsController_update"];
trace?: never;
};
"/participants/{id}/entry-templates": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get a participant by ID */
get: operations["ParticipantsController_entryTemplates"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/entries": {
parameters: {
query?: never;
Expand Down Expand Up @@ -658,6 +675,61 @@ export interface components {
carers: number[];
languages: number[];
};
CarerDto: {
/**
* @description The id of the carer
* @example 1
*/
id: number;
/**
* @description The name of the carer
* @example Grandmother
*/
name: string;
/**
* @description The color used to display entries in the calendar
* @example #ffffff
*/
color?: string;
participant?: components["schemas"]["ParticipantDto"];
entries: number[];
};
LanguageDto: {
/**
* @description The id of the language
* @example 1
*/
id: number;
/**
* @description The name of the language
* @example Deutsch
*/
name: string;
/**
* @description The IETF BCP 47 code of the language
* @example de-DE
*/
ietfBcp47?: string;
participant?: components["schemas"]["ParticipantDto"];
entryLanguages: number[];
};
EntryLanguageResponseDto: {
/**
* @description The id of the entry language
* @example 1
*/
id: number;
/**
* @description The ratio in percent of the entry language
* @example 50
*/
ratio: number;
language: components["schemas"]["LanguageDto"];
};
EntryTemplateDto: {
carer: components["schemas"]["CarerDto"];
entryLanguages: components["schemas"]["EntryLanguageResponseDto"][];
};
ParticipantMutationDto: {
/**
* @description The id of the participant (child id)
Expand Down Expand Up @@ -745,57 +817,6 @@ export interface components {
study: components["schemas"]["StudyDto"];
participant: components["schemas"]["ParticipantDto"];
};
CarerDto: {
/**
* @description The id of the carer
* @example 1
*/
id: number;
/**
* @description The name of the carer
* @example Grandmother
*/
name: string;
/**
* @description The color used to display entries in the calendar
* @example #ffffff
*/
color?: string;
participant?: components["schemas"]["ParticipantDto"];
entries: number[];
};
LanguageDto: {
/**
* @description The id of the language
* @example 1
*/
id: number;
/**
* @description The name of the language
* @example Deutsch
*/
name: string;
/**
* @description The IETF BCP 47 code of the language
* @example de-DE
*/
ietfBcp47?: string;
participant?: components["schemas"]["ParticipantDto"];
entryLanguages: number[];
};
EntryLanguageResponseDto: {
/**
* @description The id of the entry language
* @example 1
*/
id: number;
/**
* @description The ratio in percent of the entry language
* @example 50
*/
ratio: number;
language: components["schemas"]["LanguageDto"];
};
EntryResponseDto: {
/**
* @description The id of the entry
Expand Down Expand Up @@ -1754,6 +1775,36 @@ export interface operations {
};
};
};
ParticipantsController_entryTemplates: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["EntryTemplateDto"][];
};
};
/** @description Entity not found exception */
404: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["ErrorResponseDto"];
};
};
};
};
EntriesController_index: {
parameters: {
query?: never;
Expand Down
20 changes: 19 additions & 1 deletion apps/frontend/src/components/CarerSelect.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ColorSwatch, Group, useMantineTheme } from "@quassel/ui";
import { components } from "../api.gen";
import { EntitySelect, EntitySelectProps } from "./EntitySelect";

Expand All @@ -6,5 +7,22 @@
};

export function CarerSelect({ value, onChange, onAddNew, data, ...rest }: CarerSelectProps) {
return <EntitySelect value={value} onChange={onChange} onAddNew={onAddNew} {...rest} data={data} labelKey="name" />;
const theme = useMantineTheme();

Check warning on line 10 in apps/frontend/src/components/CarerSelect.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/CarerSelect.tsx#L10

Added line #L10 was not covered by tests

return (
<EntitySelect
value={value}
onChange={onChange}
onAddNew={onAddNew}
{...rest}
renderOption={({ color, name }) => (
<Group>
<ColorSwatch size={20} color={color ?? theme.colors[theme.primaryColor][4]} />
{name}
</Group>

Check warning on line 22 in apps/frontend/src/components/CarerSelect.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/CarerSelect.tsx#L12-L22

Added lines #L12 - L22 were not covered by tests
)}
data={data}
labelKey="name"
/>

Check warning on line 26 in apps/frontend/src/components/CarerSelect.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/CarerSelect.tsx#L24-L26

Added lines #L24 - L26 were not covered by tests
);
}
5 changes: 3 additions & 2 deletions apps/frontend/src/components/EntitySelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

type Props<T extends { id: number }> = Omit<EntitySelectProps, "data"> & {
data?: T[];
renderOption?: (item: T) => void;
onAddNew?: (value: string) => void;
labelKey: StringKeys<T>;
};
Expand All @@ -24,7 +25,7 @@
actionCreateNew: params('Create new "{value}"'),
});

export function EntitySelect<T extends { id: number }>({ value, onChange, data, labelKey, onAddNew, ...rest }: Props<T>) {
export function EntitySelect<T extends { id: number }>({ value, onChange, data, labelKey, onAddNew, renderOption, ...rest }: Props<T>) {

Check warning on line 28 in apps/frontend/src/components/EntitySelect.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/EntitySelect.tsx#L28

Added line #L28 was not covered by tests
const t = useStore(messages);

const combobox = useCombobox({
Expand All @@ -41,7 +42,7 @@

const options = filteredOptions?.map((item) => (
<Combobox.Option key={item.id} value={item.id.toString()}>
{item[labelKey] as string}
{renderOption?.(item) ?? (item[labelKey] as string)}

Check warning on line 45 in apps/frontend/src/components/EntitySelect.tsx

View check run for this annotation

Codecov / codecov/patch

apps/frontend/src/components/EntitySelect.tsx#L45

Added line #L45 was not covered by tests
</Combobox.Option>
));

Expand Down
Loading
Loading