Skip to content

Commit

Permalink
feat: make verification field read only
Browse files Browse the repository at this point in the history
for users not in incident manager user group
  • Loading branch information
deeonwuli committed Nov 12, 2024
1 parent 86e2a7f commit 9a36a87
Show file tree
Hide file tree
Showing 13 changed files with 136 additions and 22 deletions.
10 changes: 10 additions & 0 deletions src/CompositionRoot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ import { ConfigurationsRepository } from "./domain/repositories/ConfigurationsRe
import { ConfigurationsD2Repository } from "./data/repositories/ConfigurationsD2Repository";
import { ConfigurationsTestRepository } from "./data/repositories/test/ConfigurationsTestRepository";
import { CompleteEventTrackerUseCase } from "./domain/usecases/CompleteEventTrackerUseCase";
import { UserGroupD2Repository } from "./data/repositories/UserGroupD2Repository";
import { UserGroupRepository } from "./domain/repositories/UserGroupRepository";
import { UserGroupTestRepository } from "./data/repositories/test/UserGroupTestRepository";
import { GetUserGroupByCodeUseCase } from "./domain/usecases/GetUserGroupByCodeUseCase";

export type CompositionRoot = ReturnType<typeof getCompositionRoot>;

Expand All @@ -87,6 +91,7 @@ type Repositories = {
chartConfigRepository: ChartConfigRepository;
systemRepository: SystemRepository;
configurationsRepository: ConfigurationsRepository;
userGroupRepository: UserGroupRepository;
};

function getCompositionRoot(repositories: Repositories) {
Expand Down Expand Up @@ -139,6 +144,9 @@ function getCompositionRoot(repositories: Repositories) {
charts: {
getCases: new GetChartConfigByTypeUseCase(repositories.chartConfigRepository),
},
userGroup: {
getByCode: new GetUserGroupByCodeUseCase(repositories.userGroupRepository),
},
};
}

Expand All @@ -160,6 +168,7 @@ export function getWebappCompositionRoot(api: D2Api) {
chartConfigRepository: new ChartConfigD2Repository(dataStoreClient),
systemRepository: new SystemD2Repository(api),
configurationsRepository: new ConfigurationsD2Repository(api),
userGroupRepository: new UserGroupD2Repository(api),
};

return getCompositionRoot(repositories);
Expand All @@ -182,6 +191,7 @@ export function getTestCompositionRoot() {
chartConfigRepository: new ChartConfigTestRepository(),
systemRepository: new SystemTestRepository(),
configurationsRepository: new ConfigurationsTestRepository(),
userGroupRepository: new UserGroupTestRepository(),
};

return getCompositionRoot(repositories);
Expand Down
2 changes: 1 addition & 1 deletion src/data/repositories/TeamMemberD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { apiToFuture, FutureData } from "../api-futures";
import { assertOrError } from "./utils/AssertOrError";
import { Future } from "../../domain/entities/generic/Future";

const RTSL_ZEBRA_INCIDENTMANAGER = "RTSL_ZEBRA_INCIDENTMANAGER";
export const RTSL_ZEBRA_INCIDENTMANAGER = "RTSL_ZEBRA_INCIDENTMANAGER";
const RTSL_ZEBRA_RISKASSESSOR = "RTSL_ZEBRA_RISKASSESSOR";
const RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_MEMBERS = "RTSL_ZEBRA_INCIDENT_MANAGEMENT_TEAM_MEMBERS";
const RTSL_ZEBRA_INCIDENT_RESPONSE_OFFICERS = "RTSL_ZEBRA_INCIDENT_RESPONSE_OFFICERS";
Expand Down
26 changes: 26 additions & 0 deletions src/data/repositories/UserGroupD2Repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { D2Api } from "@eyeseetea/d2-api/2.36";
import { UserGroupRepository } from "../../domain/repositories/UserGroupRepository";
import { apiToFuture, FutureData } from "../api-futures";
import { assertOrError } from "./utils/AssertOrError";
import { UserGroup } from "../../domain/entities/UserGroup";

export class UserGroupD2Repository implements UserGroupRepository {
constructor(private api: D2Api) {}

getUserGroupByCode(code: string): FutureData<UserGroup> {
return apiToFuture(
this.api.metadata.get({
userGroups: {
fields: {
id: true,
},
filter: {
code: { eq: code },
},
},
})
)
.flatMap(response => assertOrError(response.userGroups[0], `User group ${code}`))
.map(userGroup => userGroup);
}
}
8 changes: 4 additions & 4 deletions src/data/repositories/consts/IncidentActionConstants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,9 +82,9 @@ export const statusCodeMap: Record<ResponseActionStatusType, string> = {
Complete: "RTSL_ZEB_OS_STATUS_COMPLETE",
} as const;

export function getStatusTypeByCode(iapTypeCode: string): Maybe<ResponseActionStatusType> {
export function getStatusTypeByCode(statusCode: string): Maybe<ResponseActionStatusType> {
return (Object.keys(statusCodeMap) as ResponseActionStatusType[]).find(
key => statusCodeMap[key] === iapTypeCode
key => statusCodeMap[key] === statusCode
);
}

Expand All @@ -94,10 +94,10 @@ export const verificationCodeMap: Record<ResponseActionVerificationType, string>
};

export function getVerificationTypeByCode(
iapTypeCode: string
verificationCode: string
): Maybe<ResponseActionVerificationType> {
return (Object.keys(verificationCodeMap) as ResponseActionVerificationType[]).find(
key => verificationCodeMap[key] === iapTypeCode
key => verificationCodeMap[key] === verificationCode
);
}

Expand Down
12 changes: 12 additions & 0 deletions src/data/repositories/test/UserGroupTestRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Future } from "../../../domain/entities/generic/Future";
import { UserGroup } from "../../../domain/entities/UserGroup";
import { UserGroupRepository } from "../../../domain/repositories/UserGroupRepository";
import { FutureData } from "../../api-futures";

export class UserGroupTestRepository implements UserGroupRepository {
getUserGroupByCode(_code: string): FutureData<UserGroup> {
return Future.success({
id: "1",
});
}
}
3 changes: 3 additions & 0 deletions src/domain/entities/UserGroup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { Ref } from "./Ref";

export type UserGroup = Ref;
6 changes: 6 additions & 0 deletions src/domain/repositories/UserGroupRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { FutureData } from "../../data/api-futures";
import { UserGroup } from "../entities/UserGroup";

export interface UserGroupRepository {
getUserGroupByCode(code: string): FutureData<UserGroup>;
}
12 changes: 12 additions & 0 deletions src/domain/usecases/GetUserGroupByCodeUseCase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { FutureData } from "../../data/api-futures";
import { Code } from "../entities/Ref";
import { UserGroup } from "../entities/UserGroup";
import { UserGroupRepository } from "../repositories/UserGroupRepository";

export class GetUserGroupByCodeUseCase {
constructor(private userGroupRepository: UserGroupRepository) {}

public execute(code: Code): FutureData<UserGroup> {
return this.userGroupRepository.getUserGroupByCode(code);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ export function mapIncidentActionPlanToInitialFormState(
}

export function mapIncidentResponseActionToInitialFormState(
incidentResponseActionFormData: ResponseActionFormData
incidentResponseActionFormData: ResponseActionFormData,
isIncidentManager: boolean
): FormState {
const {
entity: incidentResponseActions,
Expand All @@ -240,6 +241,7 @@ export function mapIncidentResponseActionToInitialFormState(
statusOptions: statusOptions,
verificationOptions: verificationOptions,
},
isIncidentManager: isIncidentManager,
index: 0,
});

Expand All @@ -253,6 +255,7 @@ export function mapIncidentResponseActionToInitialFormState(
statusOptions: statusOptions,
verificationOptions: verificationOptions,
},
isIncidentManager: isIncidentManager,
index: index,
});
})
Expand All @@ -279,9 +282,10 @@ function getResponseActionSection(options: {
statusOptions: UIOption[];
verificationOptions: UIOption[];
};
isIncidentManager: boolean;
index: number;
}) {
const { incidentResponseAction, options: formOptions, index } = options;
const { incidentResponseAction, options: formOptions, index, isIncidentManager } = options;
const { searchAssignROOptions, statusOptions, verificationOptions } = formOptions;

const responseActionSection: FormSectionState = {
Expand Down Expand Up @@ -366,7 +370,7 @@ function getResponseActionSection(options: {
{
id: `${responseActionConstants.verification}_${index}`,
label: "Verification",
isVisible: true,
isVisible: isIncidentManager ? true : false,
errors: [],
value: incidentResponseAction?.verification || "",
type: "select",
Expand All @@ -382,7 +386,10 @@ function getResponseActionSection(options: {
return responseActionSection;
}

export function addNewResponseActionSection(sections: FormSectionState[]): FormSectionState {
export function addNewResponseActionSection(
sections: FormSectionState[],
isIncidentManager: boolean
): FormSectionState {
const responseActionSections = sections.filter(
section => !section.id.startsWith("addNewResponseActionSection")
);
Expand All @@ -400,6 +407,7 @@ export function addNewResponseActionSection(sections: FormSectionState[]): FormS
verificationOptions:
verificationField?.type === "select" ? verificationField.options : [],
},
isIncidentManager: isIncidentManager,
index: responseActionSections.length,
});

Expand Down
18 changes: 12 additions & 6 deletions src/webapp/pages/form-page/mapEntityToFormState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@ import {
mapRiskGradingToInitialFormState,
} from "./risk-assessment/mapRiskAssessmentToInitialFormState";

export function mapEntityToFormState(
configurableForm: ConfigurableForm,
editMode?: boolean,
existingEventTrackerTypes?: (DiseaseNames | HazardNames)[]
): FormState {
export function mapEntityToFormState(options: {
configurableForm: ConfigurableForm;
editMode?: boolean;
existingEventTrackerTypes?: (DiseaseNames | HazardNames)[];
isIncidentManager?: boolean;
}): FormState {
const { configurableForm, editMode, existingEventTrackerTypes, isIncidentManager } = options;

switch (configurableForm.type) {
case "disease-outbreak-event":
return mapDiseaseOutbreakEventToInitialFormState(
Expand All @@ -41,7 +44,10 @@ export function mapEntityToFormState(
case "incident-action-plan":
return mapIncidentActionPlanToInitialFormState(configurableForm);
case "incident-response-action":
return mapIncidentResponseActionToInitialFormState(configurableForm);
return mapIncidentResponseActionToInitialFormState(
configurableForm,
isIncidentManager ?? false
);
case "incident-management-team-member-assignment":
return mapIncidentManagementTeamMemberToInitialFormState(configurableForm);
}
Expand Down
7 changes: 6 additions & 1 deletion src/webapp/pages/form-page/mapFormStateToEntityData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ import {
import { ActionPlanAttrs } from "../../../domain/entities/incident-action-plan/ActionPlan";
import {
actionPlanConstants as actionPlanConstants,
getVerificationTypeByCode,
responseActionConstants,
verificationCodeMap,
} from "../../../data/repositories/consts/IncidentActionConstants";
import {
ResponseAction,
Expand Down Expand Up @@ -579,7 +581,10 @@ function mapFormStateToIncidentResponseAction(
)?.value as string;
const verification = formData.options.verification.find(
option => option.id === verificationValue
);
) ?? {
id: verificationCodeMap.Unverified,
name: getVerificationTypeByCode(verificationCodeMap.Unverified) ?? "",
};
if (!verification) throw new Error("Verification not found");

const responseAction = new ResponseAction({
Expand Down
16 changes: 13 additions & 3 deletions src/webapp/pages/form-page/useForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { useExistingEventTrackerTypes } from "../../contexts/existing-event-trac
import { useCheckWritePermission } from "../../hooks/useHasCurrentUserCaptureAccess";
import { useSnackbar } from "@eyeseetea/d2-ui-components";
import { usePerformanceOverview } from "../dashboard/usePerformanceOverview";
import { useIncidentActionPlan } from "../incident-action-plan/useIncidentActionPlan";

export type GlobalMessage = {
text: string;
Expand Down Expand Up @@ -69,6 +70,7 @@ export function useForm(formType: FormType, id?: Id): State {
const currentEventTracker = getCurrentEventTracker();
const { existingEventTrackerTypes } = useExistingEventTrackerTypes();
const { dataPerformanceOverview } = usePerformanceOverview();
const { isIncidentManager } = useIncidentActionPlan(currentEventTracker?.id ?? "");
useCheckWritePermission(formType);
const snackbar = useSnackbar();

Expand All @@ -89,7 +91,12 @@ export function useForm(formType: FormType, id?: Id): State {
setFormLabels(formData.labels);
setFormState({
kind: "loaded",
data: mapEntityToFormState(formData, !!id, existingEventTrackers),
data: mapEntityToFormState({
configurableForm: formData,
editMode: !!id,
existingEventTrackerTypes: existingEventTrackerTypes,
isIncidentManager: isIncidentManager,
}),
});
},
error => {
Expand All @@ -112,6 +119,8 @@ export function useForm(formType: FormType, id?: Id): State {
existingEventTrackers,
snackbar,
goTo,
isIncidentManager,
existingEventTrackerTypes,
]);

const handleAddNew = useCallback(() => {
Expand Down Expand Up @@ -170,7 +179,8 @@ export function useForm(formType: FormType, id?: Id): State {
);
const addAnotherSection = getAnotherResponseActionSection();
const newResponseActionSection = addNewResponseActionSection(
prevState.data.sections
prevState.data.sections,
isIncidentManager
);

const updatedData = {
Expand Down Expand Up @@ -207,7 +217,7 @@ export function useForm(formType: FormType, id?: Id): State {
default:
break;
}
}, [configurableForm, formState]);
}, [configurableForm, formState.kind, isIncidentManager]);

const handleFormChange = useCallback(
(updatedField: FormFieldState) => {
Expand Down
22 changes: 19 additions & 3 deletions src/webapp/pages/incident-action-plan/useIncidentActionPlan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { Option } from "../../components/utils/option";
import { useCurrentEventTracker } from "../../contexts/current-event-tracker-context";
import { DiseaseOutbreakEvent } from "../../../domain/entities/disease-outbreak-event/DiseaseOutbreakEvent";
import _c from "../../../domain/entities/generic/Collection";
import { RTSL_ZEBRA_INCIDENTMANAGER } from "../../../data/repositories/TeamMemberD2Repository";

export type IncidentActionFormSummaryData = {
subTitle: string;
Expand All @@ -33,7 +34,7 @@ export type UIIncidentActionOptions = {
};

export function useIncidentActionPlan(id: Id) {
const { compositionRoot, configurations: appConfiguration } = useAppContext();
const { compositionRoot, configurations: appConfiguration, currentUser } = useAppContext();
const { changeCurrentEventTracker, getCurrentEventTracker } = useCurrentEventTracker();
const currentEventTracker = getCurrentEventTracker();

Expand All @@ -44,6 +45,7 @@ export function useIncidentActionPlan(id: Id) {
const [incidentActionPlan, setIncidentActionPlan] = useState<IncidentActionPlan>();
const [incidentActionExists, setIncidentActionExists] = useState<boolean>(false);
const [incidentActionOptions, setIncidentActionOptions] = useState<UIIncidentActionOptions>();
const [isIncidentManager, setIsIncidentManager] = useState<boolean>(false);

const saveTableOption = useCallback(
(value: Maybe<string>, rowIndex: number, column: TableColumn["value"]) => {
Expand Down Expand Up @@ -82,14 +84,14 @@ export function useIncidentActionPlan(id: Id) {
{
value: "verification",
label: "Verification",
type: "selector",
type: isIncidentManager ? "selector" : "text",
options: incidentActionOptions?.verification ?? [],
onChange: saveTableOption,
},
{ value: "timeLine", label: "Timeline", type: "text" },
{ value: "dueDate", label: "Due date", type: "text" },
];
}, [incidentActionOptions, saveTableOption]);
}, [incidentActionOptions, saveTableOption, isIncidentManager]);

useEffect(() => {
compositionRoot.incidentActionPlan.get.execute(id, appConfiguration).run(
Expand Down Expand Up @@ -129,6 +131,19 @@ export function useIncidentActionPlan(id: Id) {
}
}, [changeCurrentEventTracker, currentEventTracker, incidentActionExists, incidentActionPlan]);

useEffect(() => {
compositionRoot.userGroup.getByCode.execute(RTSL_ZEBRA_INCIDENTMANAGER).run(
userGroup => {
const isIncidentManager = currentUser.belongToUserGroup(userGroup.id);
setIsIncidentManager(isIncidentManager);
},
err => {
console.error(err);
setIsIncidentManager(false);
}
);
}, [compositionRoot.userGroup.getByCode, currentUser]);

const orderByDueDate = useCallback(
(direction: "asc" | "desc") => {
setResponseActionRows(prevRows => {
Expand Down Expand Up @@ -164,6 +179,7 @@ export function useIncidentActionPlan(id: Id) {

return {
incidentActionExists: incidentActionExists,
isIncidentManager: isIncidentManager,
saveTableOption: saveTableOption,
responseActionColumns: responseActionColumns,
actionPlanSummary: actionPlanSummary,
Expand Down

0 comments on commit 9a36a87

Please sign in to comment.