Skip to content

Commit

Permalink
Merge branch 'master' into nstrctns
Browse files Browse the repository at this point in the history
  • Loading branch information
Kevin Haube authored Jan 19, 2024
2 parents 47880b9 + f2a859d commit e29562e
Show file tree
Hide file tree
Showing 21 changed files with 978 additions and 403 deletions.
25 changes: 20 additions & 5 deletions .github/workflows/github-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
name: GitHub Pages

on:
# Runs on pushes targeting the default branch
push:
branches: ["master"]

Expand All @@ -23,6 +22,9 @@ concurrency:
group: "pages"
cancel-in-progress: true

env:
STAGE_NAME: ${{ startsWith(github.ref_name, 'snyk-') && 'snyk' || github.ref_name }}

jobs:
# Build job
build:
Expand All @@ -32,23 +34,36 @@ jobs:
uses: actions/checkout@v3
- name: Setup Pages
uses: actions/configure-pages@v2
- name: Setup Node.js environment
uses: actions/[email protected]
- uses: ./.github/actions/setup

- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v2
with:
role-to-assume: ${{ secrets.AWS_OIDC_ROLE_TO_ASSUME }}
aws-region: us-east-1
role-duration-seconds: 10800
- name: Set env
run: |
echo "NEXT_PUBLIC_API_REST_URL=$(
aws cloudformation \
--region us-east-1 describe-stacks \
--stack-name $PROJECT-api-$STAGE_NAME | jq -r '.Stacks[0].Outputs[] | select(.OutputKey == "ApiGatewayRestApiUrl") | .OutputValue'
)" >> $GITHUB_ENV
- name: Install Packages
run: |
cd docs/_deploy-metrics
rm -rf node_modules
yarn install --frozen-lockfile
echo $BRANCHES_TO_GENERATE
- name: Build Dora
- name: Build Deploy Metrics
run: |
cd docs/_deploy-metrics
yarn build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_PAT: ${{ secrets.GITHUB_TOKEN }}
REPO_NAME: macpro-om-template
BRANCHES_TO_GENERATE: master
BRANCHES_TO_GENERATE: master,val,production
- name: Build with Jekyll
uses: actions/jekyll-build-pages@v1
with:
Expand Down
16 changes: 12 additions & 4 deletions docs/_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,22 @@ contact_email: [email protected]

team:
members: # This list automatically populates the Team Introduction page. Add/Edit as appropriate.
- role: Product Owner
- role: Product
description: Responsible for project scope, direction, and delivery.
name: Anna Hawk
email: [email protected]
name: Hannah Morris
email: [email protected]
- role: Product
description: Responsible for project scope, direction, and delivery.
name: Erika Durant
email: [email protected]
- role: Tech Lead
description: Leads tooling, tech, and arch discussions and decisions.
description: Tooling, tech, and arch discussions and decisions.
name: Ben Paige
email: [email protected]
- role: Tech Lead
description: Tooling, tech, and arch discussions and decisions.
name: Michael Dial
email: [email protected]
core_hours: 10:00am - 3:00pm ET

meetings:
Expand Down
92 changes: 92 additions & 0 deletions docs/_deploy-metrics/lib/formData.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
export type FormResult = {
version: string;
data: any; // replace 'any' with the actual type of the data returned from the API
} | null

export type ResultObject = {
[formId: string]: FormResult[];
}

export async function getAllFormData(formData: any): Promise<ResultObject> {
const resultObject: ResultObject = {};

// Loop through each key-value pair in formData
for (const formId in formData) {
if (formData.hasOwnProperty(formId)) {
const formVersions = formData[formId];

// Loop through each formVersion for the current formId
resultObject[formId] = await Promise.all(
formVersions.map(async (formVersion: any) => {
try {
// Make API request using fetch
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_REST_URL}/forms?formId=${formId.toLowerCase()}&formVersion=${formVersion}`
);

// Ensure the request was successful
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}

// Extract and format data from the API response
const data = await response.json();
const formattedResult: FormResult = {
version: formVersion,
data,
};

return formattedResult;
} catch (error) {
// Handle error if API request fails
console.error(`Error fetching data for formId: ${formId}, version: ${formVersion}`);
console.error(error);
return null;
}
})
);
}
}

return resultObject;
}

export function generateDocs(obj: any, results: any = [], parentName: string = '', prompt: string = '') {
if (typeof obj === 'object' && obj !== null) {
if ('rhf' in obj) {
const resultItem: any = { rhf: obj.rhf };

if ('label' in obj) {
resultItem.label = obj.label;
}

if ('name' in obj) {
resultItem.name = obj.name;
}

if ((obj.rhf === 'Select' || obj.rhf === 'Radio') && obj.props) {
resultItem.options = []
obj.props?.options.forEach((field: any) => {
resultItem.options?.push(field.value)
})
}

resultItem.parentName = parentName;
resultItem.prompt = prompt;

results.push(resultItem);
}

for (const key in obj) {
if (obj.hasOwnProperty(key)) {
if ('name' in obj) {
parentName = obj.name;
}
if ('description' in obj) {
prompt = obj.description;
}
generateDocs(obj[key], results, parentName, prompt);
}
}
}
}
2 changes: 1 addition & 1 deletion docs/_deploy-metrics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
},
"devDependencies": {
"@types/node": "18.11.0",
"@types/react": "18.0.21",
"@types/react": "^18.2.21",
"@types/react-dom": "18.0.6",
"eslint": "8.25.0",
"eslint-config-next": "12.3.1",
Expand Down
134 changes: 134 additions & 0 deletions docs/_deploy-metrics/pages/webforms/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import type { InferGetStaticPropsType } from "next";
import {
Box,
HStack,
Heading,
Select,
Stack,
Text,
} from "@chakra-ui/react";
import {
getAllFormData,
ResultObject,
generateDocs,
} from "../../lib/formData";
import React from "react";

export const getStaticProps = async () => {
let allFormsWithData, allFormsAndVersions;
try {
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_REST_URL}/allForms`
);
allFormsAndVersions = await response.json();
allFormsWithData = await getAllFormData(allFormsAndVersions);
} catch (e) {
console.error(e);
}

return {
props: {
allFormsAndVersions,
allFormsWithData,
},
};
};

const WebformsDocs = ({
allFormsAndVersions,
allFormsWithData,
}: InferGetStaticPropsType<typeof getStaticProps>) => {
const [form, setForm] = React.useState("");
const [version, setVersion] = React.useState("");

if (!allFormsWithData) return;

return (
<Box
as="section"
bg="bg.surface"
pt={{ base: "4", md: "8" }}
pb={{ base: "12", md: "24" }}
mx="10"
>
<Box maxW={"5xl"} m="auto">
<Stack spacing="4">
<Heading size={{ base: "md", md: "lg" }} fontWeight="medium">
Webforms Documentation
</Heading>
<Text>
The purpose of this page is provide developers and anyone who might
need to use the data collected in these forms infomation about how
the data collected from the user connects to the form schema itself.
</Text>
<Text color="fg.muted">
Choose a form and version to see all possible fields
</Text>
<HStack spacing={4}>
<Select
placeholder="Select a form"
value={form}
onChange={(e) => setForm(e.target.value)}
>
{Object.keys(allFormsAndVersions).map((form) => (
<option key={form} value={form}>
{form}
</option>
))}
</Select>
<Select
placeholder="version"
disabled={!form}
value={version}
onChange={(e) => setVersion(e.target.value)}
>
{form &&
allFormsWithData[form].map((val) => (
<option key={val?.version} value={val?.version}>
{val?.version}
</option>
))}
</Select>
</HStack>

{allFormsWithData[form] && version && (
<VersionDocs
allFormsWithData={allFormsWithData}
form={form}
version={version}
/>
)}
</Stack>
</Box>
</Box>
);
};

const VersionDocs: React.FC<{
allFormsWithData: ResultObject;
form: string;
version: string;
}> = ({ allFormsWithData, form, version }) => {
const selectedFormData = allFormsWithData[form].find(
(f) => f?.version === version
);

const resultsArray: any = []
generateDocs(selectedFormData?.data, resultsArray);

return <>
<Text fontSize='2xl'>{selectedFormData?.data?.header}</Text>
{resultsArray.map((d: any, ind: number) => (
<div key={d.name + ind}>
<Text fontSize='sm' fontWeight="extrabold">Name: {d.name}</Text>
<Text fontSize='sm' fontWeight="bold">Type: {d.rhf}</Text>
{d.prompt && <Text fontSize='sm' fontWeight="bold">Prompt: {d.prompt}</Text>}
{d.label && <Text fontSize='sm' fontWeight="bold">Label: {d.label}</Text>}
{d.parentName && <Text fontSize='sm'>Parent: {d.parentName}</Text>}
{d.options && <Text fontSize='sm'> options: {d.options.join(', ')}</Text>}
<hr style={{ marginTop: 16}}/>
</div>
))}
</>
};
export default WebformsDocs;
8 changes: 4 additions & 4 deletions docs/_deploy-metrics/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1556,10 +1556,10 @@
"@types/scheduler" "*"
csstype "^3.0.2"

"@types/react@18.0.21":
version "18.0.21"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.0.21.tgz#b8209e9626bb00a34c76f55482697edd2b43cc67"
integrity sha512-7QUCOxvFgnD5Jk8ZKlUAhVcRj7GuJRjnjjiY/IUBWKgOlnvDvTMLD4RTF7NPyVmbRhNrbomZiOepg7M/2Kj1mA==
"@types/react@^18.2.21":
version "18.2.45"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.45.tgz#253f4fac288e7e751ab3dc542000fb687422c15c"
integrity sha512-TtAxCNrlrBp8GoeEp1npd5g+d/OejJHFxS3OWmrPBMFaVQMSN0OFySozJio5BHxTuTeug00AVXVAjfDSfk+lUg==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
Expand Down
3 changes: 2 additions & 1 deletion docs/docs/services/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ There are four endpoints on the api. Each is guarded by AWS IAM, meaning that w
- /search (POST): This endpoint accepts search queries from clients in the form of OpenSearch Query DSL queries. Once the query is received, the lambda adds extra query filters to ensure fine grain auth. This works by looking up the user making the call in Cognito, determining what type of user (cms or state) is making the call, determining what states that user has access to (if appropriate), and modifying the query in a way that will only return results for those states. By design, the only thing the search endpoint adds is related to authentication; the rest of the query building is left to the frontend for faster and more flexible development.
- /item (POST): The item endpoint is used to fetch details for exactly one record. While you can form a query to do this and use the search endpoint, the item endpoint is for convenience. Simply make a post call containing the ID of the desired record to the item endpoint, and the record will be returned. Note that fine grain auth is still enforced in an identical way to search, whereby you will only obtain results for that ID if you should have access to that ID.
- /getAttachmentUrl (POST): This endpoint is used to generate a presigned url for direct client downloading of S3 data, enforcing fine grain auth along the way. This is how we securely allow download of submission attachment data. From the details page, a user may click a file to download. Once clicked, their client makes a post to /getAttachmentUrl with the attachment metadata. The lambda function determines if the caller should or should not have access based on identical logic as the other endpoints (the UI would not display something they cannot download, but this guards against bad actors). If access is allowed, the lambda function generates a presigned url good for 60 seconds and returns it to the client browser, at which point files are downloaded automatically.
- /forms (GET): This endpoint function serves as the backend for handling forms and their associated data. This function provides various features, including retrieving form data, validating access, and serving the requested form content. The request to this endpoint must include a formId in the request body. Optionally, you can include a formVersion parameter. If you access this endpoint with formId without specifying formVersion, it will return the latest version. Form schemas are stored in a Lambda layer. Each form is organized in its directory, and each version is stored within that directory. The Lambda layer is located in the "opt" directory when deployed to aws. To access a specific version of a form with a formId, use the following URL structure: /opt/${formId}/v${formVersion}.json. The JSON form schemas are versioned and stored in Git under the "api/layers" directory.
- /allForms (GET): This endpoint serves GET requests and will return a list off all available webforms and their associated version. the result will look like: { ABP1: [ '1', '2' ], ABP2: [ '1' ] }
- /forms (GET): This endpoint function serves as the backend for handling forms and their associated data. This function provides various features, including retrieving form data, validating access, and serving the requested form content. The request to this endpoint must include a formId in the request body. Optionally, you can include a formVersion parameter. If you access this endpoint with formId without specifying formVersion, it will return the latest version. Form schemas are stored in a Lambda layer. Each form is organized in its directory, and each version is stored within that directory. The Lambda layer is located in the "opt" directory when deployed to aws. To access a specific version of a form with a formId, use the following URL structure: /opt/${formId}/v${formVersion}.js. The form schemas are versioned and stored in Git under the "api/layers" directory.

All endpoints and backing functions interact with the OpenSearch data layer. As such, and because OpenSearch is deployed within a VPC, all lambda functions of the api service are VPC based. The functions share a security group that allows outbound traffic.

Expand Down
34 changes: 34 additions & 0 deletions docs/docs/webforms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
layout: default
title: Webforms
nav_order: 8
---

# Webforms
{: .no_toc }

## Table of contents
{: .no_toc .text-delta }

- TOC
{:toc}

<br/>
### [ClICK HERE]({{ site.url }}{{ site.repo.name }}/metrics/webforms) to view documentation for individual webforms for the {{ site.repo.name }} project.
{: .no_toc }
<br/>


## Purpose of this view
The goal of the webforms view is to be able to view all possible form fields as they relate to the data collected. We try to associate the "name" of the field, which is the key of the data as it is collected and stored, to the prompt and label of the question the user is presented with.

## What are onemac webforms
These webforms are a replacement for the pdf form uploads previously used in mmdl, etc. These are dynamicly generated forms from a single document that represent the shape of a form and version. There three pieces to this puzzle.
1. The form schema itself. this is the shape of a form that is delivered by the /forms endpoint to the frontend ui.
1. The form genereator. This is a collection of ui react components and `react-hook-form` methods that generate the ui from the form schema and render and validate the form presented to the user.
1. The data. The data collected is added along with its metadata to its parent record. This data can be used to view/approve/edit the form later by admins or other users with appropriate permissions.

## Things to note
Some of the fields listed in these webforms are FieldGroups and FieldArrays. This means that they are arrays of values that contain groups of like values (think an array of identicaly typed objects). The items within those arrays will have `Parent` values to indicate which parent they belong to.


Loading

0 comments on commit e29562e

Please sign in to comment.