Skip to content

Commit

Permalink
kopier svar fra annet skjema (#477)
Browse files Browse the repository at this point in the history
* copy answers from other contexts

* copy answers from other contexts

* copy answers from other contexts

* copy answers from other contexts
  • Loading branch information
starheim98 authored Nov 7, 2024
1 parent ad56b23 commit 1d8bb00
Show file tree
Hide file tree
Showing 11 changed files with 192 additions and 6 deletions.
64 changes: 62 additions & 2 deletions backend/src/main/kotlin/no/bekk/database/AnswerRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ object AnswerRepository {
)
)
}
logger.info("Successfully fetched context's $contextId answers from database.")
logger.debug("Successfully fetched context's $contextId answers from database.")
}
} catch (e: SQLException) {
logger.error("Error fetching answers from database for contextId: $contextId. ${e.message}", e)
Expand Down Expand Up @@ -83,7 +83,7 @@ object AnswerRepository {
)
)
}
logger.info("Successfully fetched context's $contextId answers with record id $recordId from database.")
logger.debug("Successfully fetched context's $contextId answers with record id $recordId from database.")
}
} catch (e: SQLException) {
logger.error(
Expand All @@ -95,6 +95,66 @@ object AnswerRepository {
return answers
}

fun copyAnswersFromOtherContext(newContextId: String, contextToCopy: String) {
logger.info("Copying most recent answers from context $contextToCopy to new context $newContextId")
val mostRecentAnswers = getLatestAnswersByContextIdFromDatabase(contextToCopy)

mostRecentAnswers.forEach { answer ->
try {
insertAnswerOnContext(
DatabaseAnswerRequest(
actor = answer.actor,
recordId = answer.recordId,
questionId = answer.questionId,
answer = answer.answer,
answerType = answer.answerType,
answerUnit = answer.answerUnit,
contextId = newContextId
)
)
logger.info("Answer for questionId ${answer.questionId} copied to context $newContextId")
} catch (e: SQLException) {
logger.error("Error copying answer for questionId ${answer.questionId} to context $newContextId: ${e.message}", e)
throw RuntimeException("Error copying answers to new context", e)
}
}
}

private fun getLatestAnswersByContextIdFromDatabase(contextId: String): MutableList<DatabaseAnswer> {
logger.debug("Fetching latest answers from database for contextId: $contextId")
val answers = mutableListOf<DatabaseAnswer>()
try {
Database.getConnection().use { conn ->
val statement = conn.prepareStatement("""
SELECT DISTINCT ON (question_id) id, actor, record_id, question_id, answer, updated, answer_type, answer_unit
FROM answers
WHERE context_id = ?
ORDER BY question_id, updated DESC
""")
statement.setObject(1, UUID.fromString(contextId))
val resultSet = statement.executeQuery()
while (resultSet.next()) {
answers.add(
DatabaseAnswer(
actor = resultSet.getString("actor"),
recordId = resultSet.getString("record_id"),
questionId = resultSet.getString("question_id"),
answer = resultSet.getString("answer"),
updated = resultSet.getObject("updated", java.time.LocalDateTime::class.java)?.toString() ?: "",
answerType = resultSet.getString("answer_type"),
answerUnit = resultSet.getString("answer_unit"),
contextId = contextId
)
)
}
logger.info("Successfully fetched latest context's $contextId answers from database.")
}
} catch (e: SQLException) {
logger.error("Error fetching latest answers from database for contextId: $contextId. ${e.message}", e)
throw RuntimeException("Error fetching latest answers from database", e)
}
return answers
}

fun insertAnswerOnContext(answer: DatabaseAnswerRequest): DatabaseAnswer {
require(answer.contextId != null) {
Expand Down
4 changes: 2 additions & 2 deletions backend/src/main/kotlin/no/bekk/database/CommentRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ object CommentRepository {
)
)
}
logger.info("Successfully fetched context's $contextId comments from database.")
logger.debug("Successfully fetched context's $contextId comments from database.")
}
} catch (e: SQLException) {
logger.error("Error fetching comments for context $contextId: ${e.message}")
Expand Down Expand Up @@ -72,7 +72,7 @@ object CommentRepository {
)
)
}
logger.info("Successfully fetched context's $contextId comments with recordId $recordId from database.")
logger.debug("Successfully fetched context's $contextId comments with recordId $recordId from database.")
}
} catch (e: SQLException) {
logger.error("Error fetching comments for context $contextId with recordId $recordId: ${e.message}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,5 @@ data class DatabaseContextRequest(
val teamId: String,
val tableId: String,
val name: String,
val copyContext: String? = null,
)
11 changes: 11 additions & 0 deletions backend/src/main/kotlin/no/bekk/routes/ContextRouting.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import no.bekk.authentication.hasContextAccess
import no.bekk.authentication.hasTeamAccess
import no.bekk.database.AnswerRepository
import no.bekk.database.ContextRepository
import no.bekk.database.DatabaseContextRequest
import no.bekk.database.UniqueConstraintViolationException
Expand All @@ -27,7 +28,17 @@ fun Route.contextRouting() {
call.respond(HttpStatusCode.Forbidden)
return@post
}


val insertedContext = ContextRepository.insertContext(contextRequest)

if (contextRequest.copyContext != null) {
if (!hasContextAccess(call, contextRequest.copyContext)) {
call.respond(HttpStatusCode.Forbidden)
return@post
}
AnswerRepository.copyAnswersFromOtherContext(insertedContext.id, contextRequest.copyContext)
}
call.respond(HttpStatusCode.Created, Json.encodeToString(insertedContext))
return@post
} catch (e: UniqueConstraintViolationException) {
Expand Down
9 changes: 9 additions & 0 deletions frontend/beCompliant/src/api/apiConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,5 +87,14 @@ export const apiConfig = {
queryKey: (teamId: string) => [PATH_CONTEXTS, teamId],
url: (teamId: string) => `${API_URL_CONTEXTS}?teamId=${teamId}`,
},
forTeamAndTable: {
queryKey: (teamId: string, tableId: string) => [
PATH_CONTEXTS,
teamId,
tableId,
],
url: (teamId: string, tableId: string) =>
`${API_URL_CONTEXTS}?teamId=${teamId}&tableId=${tableId}`,
},
},
} as const;
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Box, FormControl, FormLabel, Select, Spinner } from '@kvib/react';
import { useFetchTeamTableContexts } from '../../hooks/useFetchTeamTableContexts';

export function CopyContextDropdown({
tableId,
teamId,
copyContext,
setCopyContext,
}: {
tableId: string;
teamId: string;
copyContext: string | null;
setCopyContext: (context: string) => void;
}) {
const { data: contexts, isLoading: contextsIsLoading } =
useFetchTeamTableContexts(teamId, tableId);

if (contextsIsLoading) {
return <Spinner size="sm" />;
}

return (
<Box marginBottom="1rem">
<FormLabel htmlFor="select">
Kopier svar fra eksisterende skjema
</FormLabel>
<FormControl>
<Select
id="select"
placeholder="Velg skjema"
onChange={(e) => setCopyContext(e.target.value)}
bgColor="white"
borderColor="gray.200"
value={copyContext ?? undefined}
>
{contexts?.map((context) => (
<option key={context.id} value={context.id}>
{context.name}
</option>
))}
</Select>
</FormControl>
</Box>
);
}
15 changes: 15 additions & 0 deletions frontend/beCompliant/src/hooks/useFetchTeamTableContexts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useQuery } from '@tanstack/react-query';
import { apiConfig } from '../api/apiConfig';
import { axiosFetch } from '../api/Fetch';
import { Context } from './useFetchTeamContexts';

export function useFetchTeamTableContexts(teamId: string, tableId: string) {
return useQuery({
queryKey: apiConfig.contexts.forTeamAndTable.queryKey(teamId, tableId),
queryFn: () =>
axiosFetch<Context[]>({
url: `${apiConfig.contexts.forTeamAndTable.url(teamId, tableId)}`,
}).then((response) => response.data),
enabled: !!teamId && !!tableId,
});
}
1 change: 1 addition & 0 deletions frontend/beCompliant/src/hooks/useSubmitContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type SubmitContextRequest = {
teamId: string;
tableId: string;
name: string;
copyContext: string | null;
};

export interface SubmitContextResponse {
Expand Down
4 changes: 3 additions & 1 deletion frontend/beCompliant/src/pages/CreateContextPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ export const CreateContextPage = () => {
[search, setSearch]
);

const copyContext = search.get('copyContext');

const { mutate: submitContext, isPending: isLoading } = useSubmitContext();

const {
Expand Down Expand Up @@ -61,7 +63,7 @@ export const CreateContextPage = () => {
event.preventDefault();
if (teamId && name && tableId) {
submitContext(
{ teamId, tableId, name },
{ teamId, tableId, name, copyContext },
{
onSuccess: (data) => {
if (redirect) {
Expand Down
24 changes: 23 additions & 1 deletion frontend/beCompliant/src/pages/LockedCreateContextPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@ import {
Text,
} from '@kvib/react';
import { Form } from 'react-router-dom';
import { FormEvent } from 'react';
import { FormEvent, useCallback } from 'react';
import { Table } from '../api/types';
import { CopyContextDropdown } from '../components/createContextPage/CopyContextDropdown';
import { useSearchParams } from 'react-router-dom';

type Props = {
userinfo: UserInfo;
Expand All @@ -37,6 +39,17 @@ export const LockedCreateContextPage = ({
const teamDisplayName = userinfo.groups.find(
(group) => group.id === teamId
)?.displayName;
const [search, setSearch] = useSearchParams();
const tableId = search.get('tableId');
const copyContext = search.get('copyContext');

const setCopyContext = useCallback(
(newCopyContext: string) => {
search.set('copyContext', newCopyContext);
setSearch(search);
},
[search, setSearch]
);

return (
<Stack
Expand Down Expand Up @@ -80,6 +93,7 @@ export const LockedCreateContextPage = ({
w="fit-content"
bgColor="white"
borderColor="gray.200"
value={tableId ?? undefined}
>
{tablesData?.map((table) => (
<option key={table.id} value={table.id}>
Expand All @@ -89,6 +103,14 @@ export const LockedCreateContextPage = ({
</Select>
</FormControl>
</Box>
{tableId && tableId.trim() && teamId && teamId.trim() && (
<CopyContextDropdown
tableId={tableId}
teamId={teamId}
copyContext={copyContext}
setCopyContext={setCopyContext}
/>
)}
<Button
type="submit"
variant="primary"
Expand Down
20 changes: 20 additions & 0 deletions frontend/beCompliant/src/pages/UnlockedCreateContextPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { UserInfo } from '../hooks/useFetchUserinfo';
import { CopyContextDropdown } from '../components/createContextPage/CopyContextDropdown';
import {
Box,
Button,
Expand Down Expand Up @@ -37,6 +38,7 @@ export const UnlockedCreateContextPage = ({
}: Props) => {
const [search, setSearch] = useSearchParams();
const tableId = search.get('tableId');
const copyContext = search.get('copyContext');

const setTeamId = useCallback(
(newTeamId: string) => {
Expand All @@ -54,6 +56,14 @@ export const UnlockedCreateContextPage = ({
[search, setSearch]
);

const setCopyContext = useCallback(
(newCopyContext: string) => {
search.set('copyContext', newCopyContext);
setSearch(search);
},
[search, setSearch]
);

// Effect to set default values
useEffect(() => {
if (teamId == null) {
Expand Down Expand Up @@ -103,6 +113,7 @@ export const UnlockedCreateContextPage = ({
required
bgColor="white"
borderColor="gray.200"
value={teamId ?? undefined}
>
{userinfo.groups.map((group) => (
<option key={group.id} value={group.id}>
Expand All @@ -122,6 +133,7 @@ export const UnlockedCreateContextPage = ({
bgColor="white"
borderColor="gray.200"
onChange={(e) => setTableId(e.target.value)}
value={tableId ?? undefined}
>
{tablesData?.map((table) => (
<option key={table.id} value={table.id}>
Expand All @@ -131,6 +143,14 @@ export const UnlockedCreateContextPage = ({
</Select>
</FormControl>
</Box>
{tableId && tableId.trim() && teamId && teamId.trim() && (
<CopyContextDropdown
tableId={tableId}
teamId={teamId}
copyContext={copyContext}
setCopyContext={setCopyContext}
/>
)}
<Box marginBottom="50px">
<FormControl>
<FormLabel htmlFor="contextName">Navn på skjemautfylling</FormLabel>
Expand Down

0 comments on commit 1d8bb00

Please sign in to comment.