Skip to content

Commit

Permalink
fix: seedlot RBAC and frontend clean up (#1318)
Browse files Browse the repository at this point in the history
  • Loading branch information
craigyu authored Jul 3, 2024

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
1 parent a909731 commit bae8e26
Showing 13 changed files with 57 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -9,6 +9,6 @@
public class ClientIdForbiddenException extends ResponseStatusException {

public ClientIdForbiddenException() {
super(HttpStatus.FORBIDDEN);
super(HttpStatus.FORBIDDEN, "No access due to client ID");
}
}
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@
import ca.bc.gov.backendstartapi.security.LoggedUserService;
import ca.bc.gov.backendstartapi.security.UserInfo;
import jakarta.transaction.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
@@ -244,13 +245,22 @@ public Optional<Page<Seedlot>> getSeedlotByClientId(
* @throws SeedlotNotFoundException in case of errors.
*/
public SeedlotDto getSingleSeedlotInfo(@NonNull String seedlotNumber) {

SparLog.info("Retrieving information for Seedlot number {}", seedlotNumber);

Seedlot seedlotEntity =
seedlotRepository.findById(seedlotNumber).orElseThrow(SeedlotNotFoundException::new);

SparLog.info("Seedlot number {} found", seedlotNumber);

String clientId = seedlotEntity.getApplicantClientNumber();
Optional<UserInfo> userInfo = loggedUserService.getLoggedUserInfo();

if (userInfo.isPresent() && !userInfo.get().clientIds().contains(clientId)) {
SparLog.info("User has no access to seedlot {}, request denied.", seedlotNumber);
throw new ClientIdForbiddenException();
}

SeedlotDto seedlotDto = new SeedlotDto();

seedlotDto.setSeedlot(seedlotEntity);
@@ -523,6 +533,10 @@ public SeedlotAclassFormDto getAclassSeedlotFormInfo(@NonNull String seedlotNumb

List<SeedlotFormOwnershipDto> ownershipStep =
seedlotOwnerQuantityRepository.findAllBySeedlot_id(seedlotInfo.getId()).stream()
.filter(
owner ->
owner.getOriginalPercentageOwned() != null
&& owner.getOriginalPercentageOwned().compareTo(BigDecimal.ZERO) > 0)
.map(
owner ->
new SeedlotFormOwnershipDto(
Original file line number Diff line number Diff line change
@@ -457,8 +457,6 @@ void findAclassSeedlotFormFullDataSuccessTest() {
parentTreeDto.pollenCount(),
audit);

List<SeedlotParentTree> parentTreeData = List.of(spt);

ParentTreeGeneticQualityDto sptgqDto = createParentTreeGenQuaDto();

SeedlotParentTreeGeneticQuality sptgq =
@@ -504,11 +502,15 @@ void findAclassSeedlotFormFullDataSuccessTest() {

SeedlotOwnerQuantity seedlotOwners =
new SeedlotOwnerQuantity(seedlotEntity, onwerNumber, ownerLoc);
BigDecimal orginalPercOwned = new BigDecimal(100);
seedlotOwners.setOriginalPercentageOwned(orginalPercOwned);
seedlotOwners.setMethodOfPayment(new MethodOfPaymentEntity(methodOfPayment, "", null));

List<SeedlotOrchard> seedlotOrchards =
List.of(new SeedlotOrchard(seedlotEntity, true, orchardId));

List<SeedlotParentTree> parentTreeData = List.of(spt);

when(seedlotRepository.findById(seedlotNumber)).thenReturn(Optional.of(seedlotEntity));
when(seedlotParentTreeService.getAllSeedlotParentTree(seedlotNumber))
.thenReturn(parentTreeData);
@@ -561,7 +563,7 @@ void findAclassSeedlotFormFullDataSuccessTest() {
null, null, null, null, null, null, null, null, List.of(0)),
List.of(
new SeedlotFormOwnershipDto(
onwerNumber, ownerLoc, null, null, null, methodOfPayment, null)),
onwerNumber, ownerLoc, orginalPercOwned, null, null, methodOfPayment, null)),
new SeedlotFormInterimDto(null, null, null, null, null, null),
new SeedlotFormOrchardDto(
orchardId, null, null, null, null, null, null, null, null, null),
Original file line number Diff line number Diff line change
@@ -8,7 +8,6 @@ interface ApplicantAgencyFieldsProps {
agency: OptionsInputType;
locationCode: StringInputType;
fieldsProps: AgencyTextPropsType;
agencyOptions: Array<MultiOptionsObj>;
setAgencyAndCode: Function;
defaultAgency?: MultiOptionsObj;
defaultCode?: string;
42 changes: 34 additions & 8 deletions frontend/src/components/LotApplicantAndInfoForm/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useContext, useEffect } from 'react';
import { useQuery } from '@tanstack/react-query';

import {
@@ -12,9 +12,11 @@ import validator from 'validator';
import Subtitle from '../Subtitle';
import Divider from '../Divider';
import ApplicantAgencyFields from '../ApplicantAgencyFields';
import getApplicantAgenciesOptions from '../../api-service/applicantAgenciesAPI';
import { BooleanInputType, OptionsInputType, StringInputType } from '../../types/FormInputType';
import { EmptyBooleanInputType } from '../../shared-constants/shared-constants';
import AuthContext from '../../contexts/AuthContext';
import { getForestClientByNumberOrAcronym } from '../../api-service/forestClientsAPI';
import { THREE_HALF_HOURS, THREE_HOURS } from '../../config/TimeUnits';

import SeedlotInformation from './SeedlotInformation';
import { FormProps } from './definitions';
@@ -36,11 +38,36 @@ const LotApplicantAndInfoForm = ({
seedlotFormData,
setSeedlotFormData
}: FormProps) => {
const applicantAgencyQuery = useQuery({
queryKey: ['applicant-agencies'],
enabled: !isEdit,
queryFn: () => getApplicantAgenciesOptions()
});
const { selectedClientRoles } = useContext(AuthContext);

const defaultApplicantAgencyQuery = useQuery(
{
queryKey: ['forest-clients', selectedClientRoles?.clientId],
queryFn: () => getForestClientByNumberOrAcronym(selectedClientRoles?.clientId!),
enabled: !isEdit && !isReview && !!selectedClientRoles?.clientId,
staleTime: THREE_HOURS,
cacheTime: THREE_HALF_HOURS
}
);

useEffect(() => {
// Pre-fill the applicant acronym based on user selected role
if (defaultApplicantAgencyQuery.status === 'success' && !seedlotFormData?.client.value.code && setSeedlotFormData) {
const forestClient = defaultApplicantAgencyQuery.data;

setSeedlotFormData((prevForm) => ({
...prevForm,
client: {
...prevForm.client,
value: {
code: forestClient.clientNumber,
description: forestClient.clientName,
label: forestClient.acronym
}
}
}));
}
}, [defaultApplicantAgencyQuery.status]);

const handleEmail = (value: string) => {
const isEmailInvalid = !validator.isEmail(value);
@@ -89,7 +116,6 @@ const LotApplicantAndInfoForm = ({
: vegLotLocationCode
}
fieldsProps={agencyFieldsProp}
agencyOptions={isEdit ? [] : applicantAgencyQuery.data ?? []}
setAgencyAndCode={
(
_isDefault: BooleanInputType,

This file was deleted.

Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@ import {
Column,
Row,
TextInput,
NumberInput,
CheckboxGroup,
Checkbox,
DatePickerInput,
@@ -46,7 +45,6 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => {
setStepData,
defaultAgencyObj: defaultAgency,
defaultCode,
agencyOptions,
isFormSubmitted
} = useContext(ClassAContext);

@@ -157,7 +155,6 @@ const CollectionStep = ({ isReview }: CollectionStepProps) => {
agency={state.collectorAgency}
locationCode={state.locationCode}
fieldsProps={agencyFieldsProps}
agencyOptions={agencyOptions}
defaultAgency={defaultAgency}
defaultCode={defaultCode}
setAgencyAndCode={
Original file line number Diff line number Diff line change
@@ -42,7 +42,6 @@ const ExtractionAndStorage = (
const {
allStepData: { extractionStorageStep: state },
setStepData,
agencyOptions,
isFormSubmitted
} = useContext(ClassAContext);

@@ -115,7 +114,6 @@ const ExtractionAndStorage = (
agency={state.extraction.agency}
locationCode={state.extraction.locationCode}
fieldsProps={extractorAgencyFields}
agencyOptions={agencyOptions}
defaultAgency={defaultAgency}
defaultCode={defaultCode}
setAgencyAndCode={(
@@ -205,7 +203,6 @@ const ExtractionAndStorage = (
agency={state.seedStorage.agency}
locationCode={state.seedStorage.locationCode}
fieldsProps={storageAgencyFields}
agencyOptions={agencyOptions}
defaultAgency={defaultAgency}
defaultCode={defaultCode}
setAgencyAndCode={(
Original file line number Diff line number Diff line change
@@ -42,7 +42,6 @@ const InterimStep = ({ isReview }:InterimStepProps) => {
allStepData: { collectionStep: { collectorAgency } },
allStepData: { collectionStep: { locationCode: collectorCode } },
setStepData,
agencyOptions,
isFormSubmitted
} = useContext(ClassAContext);

@@ -160,7 +159,6 @@ const InterimStep = ({ isReview }:InterimStepProps) => {
agency={state.agencyName}
locationCode={state.locationCode}
fieldsProps={agencyFieldsProps}
agencyOptions={agencyOptions}
defaultAgency={collectorAgency.value}
defaultCode={collectorCode.value}
setAgencyAndCode={(
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, { useState } from 'react';
import { UseQueryResult } from '@tanstack/react-query';
import {
NumberInput,
TextInput,
FlexGrid,
Column,
@@ -20,10 +19,7 @@ import ComboBoxEvent from '../../../../types/ComboBoxEvent';

import { validatePerc } from '../utils';

import {
SingleOwnerForm,
NumStepperVal
} from '../definitions';
import { SingleOwnerForm } from '../definitions';
import { inputText, DEFAULT_INDEX, agencyFieldsProps } from '../constants';
import { FilterObj, filterInput } from '../../../../utils/FilterUtils';

@@ -32,7 +28,6 @@ import './styles.scss';
interface SingleOwnerInfoProps {
ownerInfo: SingleOwnerForm,
deleteAnOwner: Function,
agencyOptions: Array<MultiOptionsObj>,
defaultAgency: MultiOptionsObj,
defaultCode: string,
fundingSourcesQuery: UseQueryResult<MultiOptionsObj[], unknown>,
@@ -44,7 +39,7 @@ interface SingleOwnerInfoProps {
}

const SingleOwnerInfo = ({
ownerInfo, agencyOptions, defaultAgency, defaultCode, fundingSourcesQuery,
ownerInfo, defaultAgency, defaultCode, fundingSourcesQuery,
methodsOfPaymentQuery, deleteAnOwner, checkPortionSum, setState, readOnly, isReview
}: SingleOwnerInfoProps) => {
const [ownerPortionInvalidText, setOwnerPortionInvalidText] = useState<string>(
@@ -91,7 +86,6 @@ const SingleOwnerInfo = ({
const handleReservedAndSurplus = (field: string, value: string) => {
const clonedState = structuredClone(ownerInfo);
const isReserved = field === 'reservedPerc';
console.log(value);

// First validate the format of what is set on the value and
// get the correct error message
@@ -152,7 +146,6 @@ const SingleOwnerInfo = ({
agency={ownerInfo.ownerAgency}
locationCode={ownerInfo.ownerCode}
fieldsProps={agencyFieldsProps}
agencyOptions={agencyOptions}
defaultAgency={defaultAgency}
defaultCode={defaultCode}
setAgencyAndCode={
Original file line number Diff line number Diff line change
@@ -49,7 +49,6 @@ const OwnershipStep = ({ isReview }: OwnershipStepProps) => {
setStepData,
defaultAgencyObj: defaultAgency,
defaultCode,
agencyOptions,
isFormSubmitted
} = useContext(ClassAContext);

@@ -173,7 +172,6 @@ const OwnershipStep = ({ isReview }: OwnershipStepProps) => {
>
<SingleOwnerInfo
ownerInfo={singleOwnerInfo}
agencyOptions={agencyOptions}
defaultAgency={defaultAgency}
defaultCode={defaultCode}
fundingSourcesQuery={fundingSourcesQuery}
2 changes: 0 additions & 2 deletions frontend/src/views/Seedlot/ContextContainerClassA/context.tsx
Original file line number Diff line number Diff line change
@@ -36,7 +36,6 @@ export type ClassAContextType = {
setStep: (delta: number) => void,
defaultAgencyObj: MultiOptionsObj,
defaultCode: string,
agencyOptions: MultiOptionsObj[],
isFormSubmitted: boolean,
isFormIncomplete: boolean,
handleSaveBtn: () => void,
@@ -86,7 +85,6 @@ const ClassAContext = createContext<ClassAContextType>({
setStep: (delta: number) => { },
defaultAgencyObj: EmptyMultiOptObj,
defaultCode: '',
agencyOptions: [] as MultiOptionsObj[],
isFormSubmitted: false,
isFormIncomplete: true,
handleSaveBtn: () => { },
9 changes: 1 addition & 8 deletions frontend/src/views/Seedlot/ContextContainerClassA/index.tsx
Original file line number Diff line number Diff line change
@@ -16,7 +16,6 @@ import {
} from '../../../api-service/seedlotAPI';
import getVegCodes from '../../../api-service/vegetationCodeAPI';
import { getForestClientByNumberOrAcronym } from '../../../api-service/forestClientsAPI';
import getApplicantAgenciesOptions from '../../../api-service/applicantAgenciesAPI';
import getFundingSources from '../../../api-service/fundingSourcesAPI';
import getMethodsOfPayment from '../../../api-service/methodsOfPaymentAPI';
import getGameticMethodology from '../../../api-service/gameticMethodologyAPI';
@@ -256,11 +255,6 @@ const ContextContainerClassA = ({ children }: props) => {

const getDefaultLocationCode = (): string => (seedlotQuery.data?.seedlot.applicantLocationCode ?? '');

const applicantAgencyQuery = useQuery({
queryKey: ['applicant-agencies'],
queryFn: getApplicantAgenciesOptions
});

const updateStepStatus = (
stepName: keyof ProgressIndicatorConfig,
stepStatusObj: ProgressStepStatus
@@ -742,7 +736,6 @@ const ContextContainerClassA = ({ children }: props) => {
setStep,
defaultAgencyObj: getAgencyObj(),
defaultCode: getDefaultLocationCode(),
agencyOptions: applicantAgencyQuery.data ?? [],
isFormSubmitted,
isFormIncomplete,
handleSaveBtn,
@@ -782,7 +775,7 @@ const ContextContainerClassA = ({ children }: props) => {
[
seedlotNumber, calculatedValues, allStepData, seedlotQuery.status,
vegCodeQuery.status, formStep, forestClientQuery.status,
applicantAgencyQuery.status, isFormSubmitted, isFormIncomplete,
isFormSubmitted, isFormIncomplete,
saveStatus, saveDescription, lastSaveTimestamp, allStepCompleted,
progressStatus, submitSeedlot, saveProgress.status, getAllSeedlotInfoQuery.status,
methodsOfPaymentQuery.status, orchardQuery.status, gameticMethodologyQuery.status,

0 comments on commit bae8e26

Please sign in to comment.