Skip to content

Commit

Permalink
VKT(Backend&Frontend): Load all examiner details for OPH clerk
Browse files Browse the repository at this point in the history
  • Loading branch information
pkoivisto committed Nov 4, 2024
1 parent 397554d commit 5ee0bd4
Show file tree
Hide file tree
Showing 14 changed files with 183 additions and 27 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package fi.oph.vkt.api.clerk;

import fi.oph.vkt.api.dto.examiner.ExaminerDetailsDTO;
import fi.oph.vkt.service.ClerkExaminerService;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.annotation.Resource;
import java.util.List;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/api/v1/clerk/examiner", produces = MediaType.APPLICATION_JSON_VALUE)
public class ClerkExaminerController {

private static final String TAG_CLERK_EXAMINER = "Examiner API for OPH clerk users";

@Resource
private ClerkExaminerService clerkExaminerService;

@GetMapping
@Operation(tags = TAG_CLERK_EXAMINER, summary = "List examiner details")
public List<ExaminerDetailsDTO> listExaminers() {
return clerkExaminerService.listExaminers();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
@Repository
public interface ExaminerRepository extends BaseRepository<Examiner> {
List<Examiner> getAllByDeletedAtIsNullAndIsPublicIsTrue();
List<Examiner> getAllByDeletedAtIsNull();
Examiner getByOid(String oid);
Optional<Examiner> findByOid(String oid);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package fi.oph.vkt.service;

import fi.oph.vkt.api.dto.examiner.ExaminerDetailsDTO;
import fi.oph.vkt.audit.AuditService;
import fi.oph.vkt.repository.ExaminerRepository;
import fi.oph.vkt.util.ExaminerUtil;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class ClerkExaminerService {

private final ExaminerRepository examinerRepository;
private final AuditService auditService;

@Transactional(readOnly = true)
public List<ExaminerDetailsDTO> listExaminers() {
// TODO Audit log entry
return examinerRepository
.getAllByDeletedAtIsNull()
.stream()
.map(ExaminerUtil::toExaminerDetailsDTO)
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
package fi.oph.vkt.service;

import fi.oph.vkt.api.dto.MunicipalityDTO;
import fi.oph.vkt.api.dto.examiner.ExaminerDetailsDTO;
import fi.oph.vkt.api.dto.examiner.ExaminerDetailsInitDTO;
import fi.oph.vkt.api.dto.examiner.ExaminerDetailsUpsertDTO;
import fi.oph.vkt.audit.AuditService;
import fi.oph.vkt.model.Examiner;
import fi.oph.vkt.model.Municipality;
import fi.oph.vkt.repository.ExaminerRepository;
import fi.oph.vkt.service.onr.OnrService;
import fi.oph.vkt.service.onr.PersonalData;
import fi.oph.vkt.util.ExaminerUtil;
import fi.oph.vkt.util.exception.APIException;
import fi.oph.vkt.util.exception.APIExceptionType;
import java.util.List;
Expand All @@ -34,27 +33,6 @@ private PersonalData getOnrPersonalData(final String oid) {
return oidToData.get(oid);
}

private static MunicipalityDTO toMunicipalityDTO(final Municipality municipality) {
return MunicipalityDTO.builder().code(municipality.getCode()).build();
}

private static ExaminerDetailsDTO toExaminerDetailsDTO(final Examiner examiner) {
return ExaminerDetailsDTO
.builder()
.id(examiner.getId())
.version(examiner.getVersion())
.oid(examiner.getOid())
.lastName(examiner.getLastName())
.firstName(examiner.getFirstName())
.email(examiner.getEmail())
.phoneNumber(examiner.getPhoneNumber())
.municipalities(examiner.getMunicipalities().stream().map(ExaminerDetailsService::toMunicipalityDTO).toList())
.isPublic(examiner.isPublic())
.examLanguageFinnish(examiner.isExamLanguageFinnish())
.examLanguageSwedish(examiner.isExamLanguageSwedish())
.build();
}

@Transactional(readOnly = true)
public ExaminerDetailsInitDTO getInitialExaminerPersonalData(final String oid) {
// TODO Audit log entry
Expand Down Expand Up @@ -105,7 +83,7 @@ public ExaminerDetailsDTO upsertExaminer(final String oid, ExaminerDetailsUpsert
examiner.setPublic(examinerDetailsUpsertDTO.isPublic());
examinerRepository.saveAndFlush(examiner);

return toExaminerDetailsDTO(examiner);
return ExaminerUtil.toExaminerDetailsDTO(examiner);
}

@Transactional(readOnly = true)
Expand All @@ -115,7 +93,7 @@ public ExaminerDetailsDTO getExaminer(final String oid) {
if (examiner == null) {
throw new APIException(APIExceptionType.EXAMINER_NOT_FOUND);
}
return toExaminerDetailsDTO(examiner);
return ExaminerUtil.toExaminerDetailsDTO(examiner);
}

@Transactional
Expand Down
30 changes: 30 additions & 0 deletions backend/vkt/src/main/java/fi/oph/vkt/util/ExaminerUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package fi.oph.vkt.util;

import fi.oph.vkt.api.dto.MunicipalityDTO;
import fi.oph.vkt.api.dto.examiner.ExaminerDetailsDTO;
import fi.oph.vkt.model.Examiner;
import fi.oph.vkt.model.Municipality;

public class ExaminerUtil {

public static MunicipalityDTO toMunicipalityDTO(final Municipality municipality) {
return MunicipalityDTO.builder().code(municipality.getCode()).build();
}

public static ExaminerDetailsDTO toExaminerDetailsDTO(final Examiner examiner) {
return ExaminerDetailsDTO
.builder()
.id(examiner.getId())
.version(examiner.getVersion())
.oid(examiner.getOid())
.lastName(examiner.getLastName())
.firstName(examiner.getFirstName())
.email(examiner.getEmail())
.phoneNumber(examiner.getPhoneNumber())
.municipalities(examiner.getMunicipalities().stream().map(ExaminerUtil::toMunicipalityDTO).toList())
.isPublic(examiner.isPublic())
.examLanguageFinnish(examiner.isExamLanguageFinnish())
.examLanguageSwedish(examiner.isExamLanguageSwedish())
.build();
}
}
1 change: 1 addition & 0 deletions frontend/packages/vkt/src/enums/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum APIEndpoints {
FeatureFlags = '/vkt/api/v1/featureFlags',
UploadPostPolicy = '/vkt/api/v1/uploadPostPolicy/:examEventId',
ClerkRefreshKoskiEducationDetails = '/vkt/api/v1/clerk/enrollment/:enrollmentId/refreshKoskiEducationDetails',
ClerkExaminer = '/vkt/api/v1/clerk/examiner',
// TODO Consider using prefix /examiner instead of /tv
ExaminerDetails = '/vkt/api/v1/tv/:oid',
ExaminerDetailsInit = '/vkt/api/v1/tv/:oid/init',
Expand Down
8 changes: 8 additions & 0 deletions frontend/packages/vkt/src/interfaces/clerkListExaminer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { APIResponseStatus } from 'shared/enums';

import { ExaminerDetails } from 'interfaces/examinerDetails';

export interface ClerkListExaminerState {
status: APIResponseStatus;
examiners: Array<ExaminerDetails>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import { useClerkTranslation } from 'configs/i18n';
import { useAppDispatch, useAppSelector } from 'configs/redux';
import { resetClerkExamEventOverview } from 'redux/reducers/clerkExamEventOverview';
import { loadExamEvents } from 'redux/reducers/clerkListExamEvent';
import { loadExaminers } from 'redux/reducers/clerkListExaminer';
import { clerkListExamEventsSelector } from 'redux/selectors/clerkListExamEvent';
import { clerkListExaminerSelector } from 'redux/selectors/clerkListExaminer';

export const ClerkGoodAndSatisfactoryLevelPage: FC = () => {
// I18
Expand All @@ -19,14 +21,22 @@ export const ClerkGoodAndSatisfactoryLevelPage: FC = () => {

const dispatch = useAppDispatch();
const { status } = useAppSelector(clerkListExamEventsSelector);
const { status: examinerListStatus } = useAppSelector(
clerkListExaminerSelector,
);

const examinersLoading = false;
const examinersLoading = examinerListStatus === APIResponseStatus.InProgress;

useEffect(() => {
if (status === APIResponseStatus.NotStarted) {
dispatch(loadExamEvents());
}
}, [dispatch, status]);
useEffect(() => {
if (examinerListStatus === APIResponseStatus.NotStarted) {
dispatch(loadExaminers());
}
}, [dispatch, examinerListStatus]);

useEffect(() => {
dispatch(resetClerkExamEventOverview());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const PublicInformation = () => {

const examDates: Array<PublicExaminerExamDate> = [
{ examDate: dayjs('2024-10-10'), isFull: false },
{ examDate: dayjs('2024-10-24'), isFull: true },
{ examDate: dayjs('2024-10-12'), isFull: true },
{ examDate: dayjs('2024-10-15'), isFull: false },
{ examDate: dayjs('2024-10-21'), isFull: false },
];
Expand Down
31 changes: 31 additions & 0 deletions frontend/packages/vkt/src/redux/reducers/clerkListExaminer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import { APIResponseStatus } from 'shared/enums';

import { ClerkListExaminerState } from 'interfaces/clerkListExaminer';
import { ExaminerDetails } from 'interfaces/examinerDetails';

const initialState: ClerkListExaminerState = {
status: APIResponseStatus.NotStarted,
examiners: [],
};

const clerkListExaminerSlice = createSlice({
name: 'clerkListExaminer',
initialState,
reducers: {
acceptExaminers(state, action: PayloadAction<Array<ExaminerDetails>>) {
state.status = APIResponseStatus.Success;
state.examiners = action.payload;
},
loadExaminers(state) {
state.status = APIResponseStatus.InProgress;
},
rejectExaminers(state) {
state.status = APIResponseStatus.Error;
},
},
});

export const { acceptExaminers, loadExaminers, rejectExaminers } =
clerkListExaminerSlice.actions;
export const clerkListExaminerReducer = clerkListExaminerSlice.reducer;
31 changes: 31 additions & 0 deletions frontend/packages/vkt/src/redux/sagas/clerkListExaminer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AxiosError, AxiosResponse } from 'axios';
import { call, put, takeLatest } from 'redux-saga/effects';

import axiosInstance from 'configs/axios';
import { APIEndpoints } from 'enums/api';
import { ExaminerDetails } from 'interfaces/examinerDetails';
import { setAPIError } from 'redux/reducers/APIError';
import {
acceptExaminers,
loadExaminers,
rejectExaminers,
} from 'redux/reducers/clerkListExaminer';
import { NotifierUtils } from 'utils/notifier';

function* loadExaminersSaga() {
try {
const response: AxiosResponse<Array<ExaminerDetails>> = yield call(
axiosInstance.get,
APIEndpoints.ClerkExaminer,
);
yield put(acceptExaminers(response.data));
} catch (error) {
const errorMessage = NotifierUtils.getAPIErrorMessage(error as AxiosError);
yield put(setAPIError(errorMessage));
yield put(rejectExaminers());
}
}

export function* watchListExaminers() {
yield takeLatest(loadExaminers.type, loadExaminersSaga);
}
2 changes: 2 additions & 0 deletions frontend/packages/vkt/src/redux/sagas/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { all } from 'redux-saga/effects';
import { watchClerkEnrollmentDetails } from 'redux/sagas/clerkEnrollmentDetails';
import { watchClerkExamEventOverview } from 'redux/sagas/clerkExamEventOverview';
import { watchListExamEvents } from 'redux/sagas/clerkListExamEvent';
import { watchListExaminers } from 'redux/sagas/clerkListExaminer';
import { watchClerkNewExamDate } from 'redux/sagas/clerkNewExamDate';
import { watchClerkUser } from 'redux/sagas/clerkUser';
import { watchExaminerDetails } from 'redux/sagas/examinerDetails';
Expand Down Expand Up @@ -33,5 +34,6 @@ export default function* rootSaga() {
watchExaminerDetails(),
watchExaminerDetailsInit(),
watchExaminerDetailsUpsert(),
watchListExaminers(),
]);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { RootState } from 'configs/redux';
import { ClerkListExaminerState } from 'interfaces/clerkListExaminer';

export const clerkListExaminerSelector = (
state: RootState,
): ClerkListExaminerState => state.clerkListExaminer;
2 changes: 2 additions & 0 deletions frontend/packages/vkt/src/redux/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { APIErrorReducer } from 'redux/reducers/APIError';
import { clerkEnrollmentDetailsReducer } from 'redux/reducers/clerkEnrollmentDetails';
import { clerkExamEventOverviewReducer } from 'redux/reducers/clerkExamEventOverview';
import { clerkListExamEventReducer } from 'redux/reducers/clerkListExamEvent';
import { clerkListExaminerReducer } from 'redux/reducers/clerkListExaminer';
import { clerkNewExamDateReducer } from 'redux/reducers/clerkNewExamDate';
import { clerkUserReducer } from 'redux/reducers/clerkUser';
import { examinerDetailsReducer } from 'redux/reducers/examinerDetails';
Expand Down Expand Up @@ -46,6 +47,7 @@ const reducer = combineReducers({
examinerDetails: examinerDetailsReducer,
examinerDetailsInit: examinerDetailsInitReducer,
examinerDetailsUpsert: examinerDetailsUpsertReducer,
clerkListExaminer: clerkListExaminerReducer,
});

const persistedReducer = persistReducer(persistConfig, reducer);
Expand Down

0 comments on commit 5ee0bd4

Please sign in to comment.