Skip to content

Commit

Permalink
Match form to CHES schema and add validation
Browse files Browse the repository at this point in the history
Add BCC config for prod. Emails are sending.
  • Loading branch information
kyle1morel committed Mar 22, 2024
1 parent 9b29059 commit 336cbc9
Show file tree
Hide file tree
Showing 20 changed files with 268 additions and 226 deletions.
1 change: 1 addition & 0 deletions .github/environments/values.dev.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ config:
enabled: true
configMap:
FRONTEND_APIPATH: api/v1
# FRONTEND_CHES_BCC: ~
FRONTEND_COMS_APIPATH: https://coms-dev.api.gov.bc.ca/api/v1
FRONTEND_COMS_BUCKETID: 1f9e1451-c130-4804-aeb0-b78b5b109c47
FRONTEND_OIDC_AUTHORITY: https://dev.loginproxy.gov.bc.ca/auth/realms/standard
Expand Down
1 change: 1 addition & 0 deletions .github/environments/values.prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ config:
enabled: true
configMap:
FRONTEND_APIPATH: api/v1
FRONTEND_CHES_BCC: [email protected]
FRONTEND_COMS_APIPATH: https://coms.api.gov.bc.ca/api/v1
FRONTEND_COMS_BUCKETID: 0089d041-5aab-485e-842d-8875475d0ed6
FRONTEND_OIDC_AUTHORITY: https://loginproxy.gov.bc.ca/auth/realms/standard
Expand Down
1 change: 1 addition & 0 deletions .github/environments/values.test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ config:
enabled: true
configMap:
FRONTEND_APIPATH: api/v1
# FRONTEND_CHES_BCC: ~
FRONTEND_COMS_APIPATH: https://coms-test.api.gov.bc.ca/api/v1
FRONTEND_COMS_BUCKETID: a9eabd1d-5f77-4c60-bf6b-83ffa0e21c59
FRONTEND_OIDC_AUTHORITY: https://test.loginproxy.gov.bc.ca/auth/realms/standard
Expand Down
3 changes: 3 additions & 0 deletions app/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"frontend": {
"apiPath": "FRONTEND_APIPATH",
"ches": {
"bcc": "FRONTEND_CHES_BCC"
},
"coms": {
"apiPath": "FRONTEND_COMS_APIPATH",
"bucketId": "FRONTEND_COMS_BUCKETID"
Expand Down
19 changes: 9 additions & 10 deletions app/src/controllers/roadmap.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import type { NextFunction, Request, Response } from '../interfaces/IExpress';
import { emailService } from '../services';

import type { NextFunction, Request, Response } from '../interfaces/IExpress';
import type { Email } from '../types';

const controller = {
/**
* @function update
* update roadmap
* @function send
* Send an email with the roadmap data
*/
update: async (req: Request, res: Response, next: NextFunction) => {
send: async (
req: Request<never, never, { activityId: string; emailData: Email }>,
res: Response,
next: NextFunction
) => {
try {
// do other stuff related to roadmap items
// eg: update note where id = req.body.activityId;

const body = req.body as { activityId: string; emailData: Email };
// send email
const { data, status } = await emailService.email(body.emailData);
const { data, status } = await emailService.email(req.body.emailData);
res.status(status).json(data);
} catch (e: unknown) {
next(e);
Expand Down
6 changes: 3 additions & 3 deletions app/src/routes/v1/roadmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import type { NextFunction, Request, Response } from '../../interfaces/IExpress'
const router = express.Router();
router.use(requireSomeAuth);

// update roadmap
router.put('/', roadmapValidator.update, (req: Request, res: Response, next: NextFunction): void => {
roadmapController.update(req, res, next);
// Send an email with the roadmap data
router.put('/', roadmapValidator.send, (req: Request, res: Response, next: NextFunction): void => {
roadmapController.send(req, res, next);
});

export default router;
8 changes: 4 additions & 4 deletions app/src/services/email.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ async function getToken() {
* @returns {AxiosInstance} An axios instance
*/
function chesAxios(): AxiosInstance {
// create axios instance
// Create axios instance
const chesAxios = axios.create({
baseURL: config.get('server.ches.apiPath'),
timeout: 10000
});
// add bearer token
// Add bearer token
chesAxios.interceptors.request.use(async (config) => {
const token = await getToken();
const auth = token ? `Bearer ${token}` : '';
Expand All @@ -50,7 +50,7 @@ function chesAxios(): AxiosInstance {
const service = {
/**
* @function email
* sends an email with CHES service
* Sends an email with CHES service
* @param emailData
* @returns Axios response status and data
*/
Expand All @@ -67,7 +67,7 @@ const service = {

/**
* @function health
* checks CHES service health
* Checks CHES service health
* @returns Axios response status and data
*/
health: async () => {
Expand Down
4 changes: 2 additions & 2 deletions app/src/validators/roadmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import { validate } from '../middleware/validation';

const schema = {
update: {
send: {
// params: Joi.object({
// activityId: activityId
// })
Expand All @@ -13,5 +13,5 @@ const schema = {
};

export default {
update: validate(schema.update)
send: validate(schema.send)
};
4 changes: 2 additions & 2 deletions charts/pcns/templates/secret.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
{{- $f1Secret := (lookup "v1" "Secret" .Release.Namespace $f1SecretName ) }}
{{- $f2SecretName := printf "%s-%s" (include "pcns.configname" .) "form2" }}
{{- $f2Secret := (lookup "v1" "Secret" .Release.Namespace $f2SecretName ) }}
{{- $oSecretName := printf "%s-%s" (include "pcns.configname" .) "oidc" }}
{{- $oSecret := (lookup "v1" "Secret" .Release.Namespace $oSecretName ) }}
{{- $chesSecretName := printf "%s-%s" "ches-service-account" }}
{{- $chesSecret := (lookup "v1" "Secret" .Release.Namespace $chesSecretName ) }}
{{- $oSecretName := printf "%s-%s" (include "pcns.configname" .) "oidc" }}
{{- $oSecret := (lookup "v1" "Secret" .Release.Namespace $oSecretName ) }}

{{- if and (not $dbSecret) (not .Values.patroni.enabled) }}
---
Expand Down
1 change: 1 addition & 0 deletions charts/pcns/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ config:
# to string value "true".
configMap:
FRONTEND_APIPATH: api/v1
FRONTEND_CHES_BCC: ~
FRONTEND_COMS_APIPATH: ~
FRONTEND_COMS_BUCKETID: ~
FRONTEND_OIDC_AUTHORITY: ~
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RouterView } from 'vue-router';
import { AppLayout, Navbar, ProgressLoader } from '@/components/layout';
import { ConfirmDialog, Message, Toast, useToast } from '@/lib/primevue';
import { useAppStore, useAuthStore, useConfigStore } from '@/store';
import { ToastTimeout } from './utils/constants';
import type { Ref } from 'vue';
Expand All @@ -25,7 +26,7 @@ onBeforeMount(async () => {
// Top level error handler
onErrorCaptured((e: Error) => {
const toast = useToast();
toast.error('Error', e.message);
toast.error('Error', e.message, { life: ToastTimeout.STICKY });
});
</script>

Expand Down
6 changes: 4 additions & 2 deletions frontend/src/components/form/TextArea.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type Props = {
placeholder?: string;
disabled?: boolean;
bold?: boolean;
rows?: number;
};
const props = withDefaults(defineProps<Props>(), {
Expand All @@ -20,7 +21,8 @@ const props = withDefaults(defineProps<Props>(), {
label: '',
placeholder: '',
disabled: false,
bold: true
bold: true,
rows: 5
});
const { errorMessage, value } = useField<string>(toRef(props, 'name'));
Expand All @@ -42,7 +44,7 @@ const { errorMessage, value } = useField<string>(toRef(props, 'name'));
class="w-full"
:class="{ 'p-invalid': errorMessage }"
:disabled="disabled"
:rows="5"
:rows="rows"
/>
<small :id="`${name}-help`">{{ helpText }}</small>
<div>
Expand Down
165 changes: 165 additions & 0 deletions frontend/src/components/roadmap/Roadmap.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
<script setup lang="ts">
import { Form } from 'vee-validate';
import { onMounted, ref } from 'vue';
import { array, object, string } from 'yup';
import { InputText, TextArea } from '@/components/form';
import { Button, useConfirm, useToast } from '@/lib/primevue';
import { roadmapService, userService } from '@/services';
import { useConfigStore } from '@/store';
import { PERMIT_STATUS } from '@/utils/enums';
import { roadmapTemplate } from '@/utils/templates';
import type { Ref } from 'vue';
import type { Permit, PermitType, Submission } from '@/types';
import { storeToRefs } from 'pinia';
// Props
type Props = {
activityId: string;
permits: Array<Permit>;
permitTypes: Array<PermitType>;
submission: Submission;
};
const props = withDefaults(defineProps<Props>(), {});
// Store
const { getConfig } = storeToRefs(useConfigStore());
// State
const initialFormValues: Ref<any> = ref();
// Form schema
const emailValidator = array()
.transform(function (value, originalValue) {
if (this.isType(value) && value !== null) {
return value;
}
return originalValue ? originalValue.split(/[\s,]+/) : [];
})
.of(string().email(({ value }) => `${value} is not a valid email`));
const formSchema = object({
to: emailValidator,
cc: emailValidator,
bcc: emailValidator,
subject: string().required(),
body: string().required()
});
// Actions
const confirm = useConfirm();
const toast = useToast();
const confirmSubmit = (data: any) => {
confirm.require({
message: 'Please confirm that you want to send this roadmap. This cannot be undone.',
header: 'Confirm sending this Roadmap',
acceptLabel: 'Send',
rejectLabel: 'Cancel',
accept: async () => {
try {
await roadmapService.send(props.activityId, data);
toast.success('Roadmap sent');
} catch (e: any) {
toast.error('Failed to send roadmap', e.message);
}
}
});
};
function getPermitTypeNamesByStatus(permits: Array<Permit>, permitTypes: Array<PermitType>, status: string) {
return permits
.map((p) => permitTypes.find((pt) => pt.permitTypeId === p.permitTypeId && p.status === status)?.name)
.filter((pt) => !!pt)
.map((name) => name as string);
}
onMounted(async () => {
const assignee = (await userService.searchUsers({ userId: [props.submission.assignedUserId] })).data[0];
const configBCC = getConfig.value.ches?.bcc;
const bcc = assignee.email + (configBCC ? `, ${configBCC}` : '');
// Initial form values
initialFormValues.value = {
from: assignee.email,
to: props.submission.contactEmail,
cc: undefined,
bcc: bcc,
subject: "Here is your housing project's Permit Roadmap", // eslint-disable-line quotes
bodyType: 'text',
body: roadmapTemplate({
'{{ contactName }}': props.submission.contactName ?? '',
'{{ locationAddress }}': props.submission.streetAddress ?? '',
'{{ permitStateNew }}': getPermitTypeNamesByStatus(props.permits, props.permitTypes, PERMIT_STATUS.NEW),
'{{ permitStateApplied }}': getPermitTypeNamesByStatus(props.permits, props.permitTypes, PERMIT_STATUS.APPLIED),
'{{ permitStateCompleted }}': getPermitTypeNamesByStatus(
props.permits,
props.permitTypes,
PERMIT_STATUS.COMPLETED
),
'{{ navigatorName }}': `${assignee.firstName} ${assignee.lastName}`
})
};
});
</script>

<template>
<Form
v-if="initialFormValues"
:initial-values="initialFormValues"
:validation-schema="formSchema"
@submit="confirmSubmit"
>
<div class="formgrid grid">
<InputText
class="col-12 lg:col-6"
name="to"
label="To"
/>
<div class="col" />
<InputText
class="col-12 lg:col-6"
name="cc"
label="CC"
/>
<div class="col" />
<InputText
class="col-12 lg:col-6"
name="bcc"
label="BCC"
/>
<div class="col" />
<InputText
class="col-12 lg:col-6"
name="subject"
label="Subject"
/>
<div class="col" />
<TextArea
class="col-12"
name="body"
label="Note"
:rows="10"
/>
<div class="col-12"><label class="font-bold">Add attachments</label></div>
<div class="col-12 pt-2">
<Button>
<font-awesome-icon
icon="fa-solid fa-plus"
class="mr-1"
/>
Choose
</Button>
</div>
<div class="col-12 pt-5">
<Button
label="Send"
type="submit"
icon="pi pi-envelope"
/>
</div>
</div>
</Form>
</template>
Loading

0 comments on commit 336cbc9

Please sign in to comment.