Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PROD] TTAHUB-2886 Display FEI root cause on goal card, TTAHUB-3036 Filter out TR goals from similarity api #2235

Merged
merged 23 commits into from
Jun 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e2965e4
wip
AdamAdHocTeam Jun 24, 2024
bcaee2e
remove references to FONTAWESOME_NPM_AUTH_TOKEN
thewatermethod Jun 24, 2024
1a5c24a
correct invalid FEI responses for a particular recipient
Jun 25, 2024
b38d4fe
pacify linter
Jun 25, 2024
817dcd2
Merge branch 'main' into TTAHUB-3058/correct-fei-root-cause
Jun 25, 2024
572487b
add fei to goal card with test
AdamAdHocTeam Jun 25, 2024
777aa93
Update setup documentation
thewatermethod Jun 25, 2024
5116546
add new is fei update tests to show for fei missing responses
AdamAdHocTeam Jun 25, 2024
93c4eda
fix test
AdamAdHocTeam Jun 25, 2024
f10ba26
move to dev and fix label
AdamAdHocTeam Jun 26, 2024
82fc913
Merge pull request #2227 from HHS/update-setup-documentation
thewatermethod Jun 26, 2024
ca298b1
filter out tr goals
AdamAdHocTeam Jun 26, 2024
c453ada
add migration to remove tr goal similarities not merged
AdamAdHocTeam Jun 26, 2024
f9f60f9
Changes per Garrett
AdamAdHocTeam Jun 26, 2024
d2496b6
Suggestion from Garrett plus fix test
AdamAdHocTeam Jun 26, 2024
54efa1d
remove ordering root cause per Kelly
AdamAdHocTeam Jun 26, 2024
38ff885
cache value per Matt
AdamAdHocTeam Jun 27, 2024
1f5ea01
Update similarity_api/src/sim/compute.py
AdamAdHocTeam Jun 27, 2024
142b23e
Per Matt increase goal similarity groups version
AdamAdHocTeam Jun 27, 2024
47558e5
Merge pull request #2230 from HHS/al-ttahub-2939-add-fei-root-cause-t…
AdamAdHocTeam Jun 27, 2024
700ad39
Merge pull request #2232 from HHS/al-ttahub-3036-filter-out-tr-goals-…
AdamAdHocTeam Jun 27, 2024
f090d24
Update src/migrations/20240625000000-correct-fei-root-cause.js
hardwarehuman Jun 27, 2024
a6dcd5e
Merge pull request #2228 from HHS/TTAHUB-3058/correct-fei-root-cause
hardwarehuman Jun 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ parameters:
type: string
dev_git_branch: # change to feature branch to test deployment
description: "Name of github branch that will deploy to dev"
default: "mb/TTAHUB-3007/no-disallowed-urls"
default: "al-ttahub-2939-add-fei-root-cause-to-goal-card"
type: string
sandbox_git_branch: # change to feature branch to test deployment
default: "jp/3005/ipd-courses-widget"
Expand Down
2 changes: 0 additions & 2 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,6 @@ AWS_ELASTICSEARCH_ENDPOINT=http://localhost:9200
AWS_ELASTICSEARCH_ACCESS_KEY=admin
AWS_ELASTICSEARCH_SECRET_KEY=admin

FONTAWESOME_NPM_AUTH_TOKEN=whatever_it_is_it_isnt_this

# Email Address that notifications should come from
FROM_EMAIL_ADDRESS=ttasmarthub@test.gov

Expand Down
27 changes: 21 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ those services are already running on your machine.

1. Make sure Docker is installed. To check run `docker ps`.
2. Make sure you have Node 18.20.3 installed.
4. Copy `.env.example` to `.env`.
6. Change the `FONTAWESOME_NPM_AUTH_TOKEN`, `AUTH_CLIENT_ID` and `AUTH_CLIENT_SECRET` variables to to values found in the team Keybase account. If you don't have access to Keybase, please ask in the acf-head-start-eng slack channel for access.
7. Optionally, set `CURRENT_USER` to your current user's uid:gid. This will cause files created by docker compose to be owned by your user instead of root.
3. Run `yarn docker:reset`. This builds the frontend and backend, installs dependencies, then runs database migrations and seeders. If this returns errors that the version of nodejs is incorrect, you may have older versions of the containers built. Delete those images and it should rebuild them.
10. Run `yarn docker:start` to start the application. The [frontend][frontend] will be available on `localhost:3000` and the [backend][backend] will run on `localhost:8080`, [API documentation][API documentation] will run on `localhost:5003`, and [minio][minio] will run on `localhost:9000`.
11. Run `yarn docker:stop` to stop the servers and remove the docker containers.
3. Copy `.env.example` to `.env`.
4. Change the `AUTH_CLIENT_ID` and `AUTH_CLIENT_SECRET` variables to to values found in the team Keybase account. If you don't have access to Keybase, please ask in the acf-head-start-eng slack channel for access.
5. Optionally, set `CURRENT_USER` to your current user's uid:gid. This will cause files created by docker compose to be owned by your user instead of root.
6. Run `yarn docker:reset`. This builds the frontend and backend, installs dependencies, then runs database migrations and seeders. If this returns errors that the version of nodejs is incorrect, you may have older versions of the containers built. Delete those images and it should rebuild them. If you are using a newer Mac with the Apple Silicon chipset, puppeteer install fails with the message: ```"The chromium binary is not available for arm64"```. See the section immediately following this one, entitled "Apple Silicon & Chromium" for instructions on how to proceed.
7. Run `yarn docker:start` to start the application. The [frontend][frontend] will be available on `localhost:3000` and the [backend][backend] will run on `localhost:8080`, [API documentation][API documentation] will run on `localhost:5003`, and [minio][minio] will run on `localhost:9000`.
8. Run `yarn docker:stop` to stop the servers and remove the docker containers.

The frontend [proxies requests](https://create-react-app.dev/docs/proxying-api-requests-in-development/) to paths it doesn't recognize to the backend.

Expand All @@ -52,6 +52,21 @@ We use an AWS OpenSearch docker image (Elasticsearch fork) and require that the
* `AWS_ELASTICSEARCH_ACCESS_KEY=admin`
* `AWS_ELASTICSEARCH_SECRET_KEY=admin`

#### Apple Silicon & Chromium
On a Mac with Apple Silicon, puppeteer install fails with the message:
```"The chromium binary is not available for arm64"```

See [docker-compose.override.yml](docker-compose.override.yml) and uncomment the relevant lines to skip downloading chromium and use the host's binary instead.

You will need to have chromium installed (you probably do not). The recommended installation method is to use brew: `brew install chromium --no-quarantine`

To ~/.zshrc (or your particular shell config), you'll need to add:

```sh
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
export PUPPETEER_EXECUTABLE_PATH=`which chromium`
```

#### Local build

You can also run build commands directly on your host (without docker). Make sure you install dependencies when changing execution method. You could see some odd errors if you install dependencies for docker and then run yarn commands directly on the host, especially if you are developing on windows. If you want to use the host yarn commands be sure to run `yarn deps:local` before any other yarn commands. Likewise if you want to use docker make sure you run `yarn docker:deps`.
Expand Down
1 change: 0 additions & 1 deletion docker-compose.debug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ services:
- POSTGRES_HOST=postgres_docker
- REDIS_HOST=redis
- SMTP_HOST=mailcatcher
- FONTAWESOME_NPM_AUTH_TOKEN
# On an M1 mac, puppeteer install fails with the message:
# "The chromium binary is not available for arm64"
#
Expand Down
1 change: 0 additions & 1 deletion docker-compose.dss.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ services:
- POSTGRES_DB=ttasmarthub
- DATABASE_URL=postgres://postgres:secretpass@postgres_docker:5432/ttasmarthub
- SESSION_SECRET=notasecret
- FONTAWESOME_NPM_AUTH_TOKEN
- BYPASS_SOCKETS="true"
volumes:
- ".:/app:rw"
Expand Down
2 changes: 0 additions & 2 deletions docker-compose.override.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ services:
- POSTGRES_HOST=postgres_docker
- REDIS_HOST=redis
- SMTP_HOST=mailcatcher
- FONTAWESOME_NPM_AUTH_TOKEN
# On an M1 mac, puppeteer install fails with the message:
# "The chromium binary is not available for arm64"
#
Expand Down Expand Up @@ -43,7 +42,6 @@ services:
- "./packages:/packages:ro"
environment:
- BACKEND_PROXY=http://backend:8080
- FONTAWESOME_NPM_AUTH_TOKEN
- REACT_APP_WEBSOCKET_URL
worker:
build:
Expand Down
1 change: 0 additions & 1 deletion docker-compose.test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ services:
- "./scripts:/app/scripts"
environment:
- BACKEND_PROXY=http://test-backend:8080
- FONTAWESOME_NPM_AUTH_TOKEN
- REACT_APP_WEBSOCKET_URL
networks:
- ttadp-test
Expand Down
19 changes: 19 additions & 0 deletions frontend/src/components/GoalCards/GoalCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,11 @@ export default function GoalCard({
const internalLeftMargin = hideCheckbox ? '' : 'desktop:margin-left-5';
const border = erroneouslySelected || deleteError ? 'smart-hub-border-base-error' : 'smart-hub-border-base-lighter';

const getResponses = () => {
const responses = goal.responses.length ? goal.responses[0].response : [];
return responses.map((r) => r).join(', ');
};

return (
<article
className={`ttahub-goal-card usa-card padding-3 radius-lg border ${border} width-full maxw-full margin-bottom-2`}
Expand Down Expand Up @@ -277,6 +282,20 @@ export default function GoalCard({
goalNumbers={goalNumbers}
/>
</p>
{
goal.isFei
? (
<div className="grid-row">
<p className="usa-prose text-bold margin-bottom-0 margin-top-1 margin-right-1">
Root cause:
</p>
<p className="usa-prose margin-bottom-0 margin-top-1">
{ getResponses() }
</p>
</div>
)
: null
}
</div>
<div className="ttahub-goal-card__goal-column ttahub-goal-card__goal-column__goal-source padding-right-3">
<p className="usa-prose text-bold margin-y-0">Goal source</p>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/components/GoalCards/__tests__/GoalCard.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ describe('GoalCard', () => {
source: 'The inferno',
createdVia: 'rtr',
onAR: true,
responses: [],
sessionObjectives: [{
title: 'Session objective 1',
trainingReportId: 'TR-1',
Expand Down Expand Up @@ -132,6 +133,13 @@ describe('GoalCard', () => {
expect(screen.getByText(/The inferno/i)).toBeInTheDocument();
});

it('shows the fei root causes', () => {
renderGoalCard(DEFAULT_PROPS,
{ ...goal, isFei: true, responses: [{ response: ['root cause 1', 'root cause 2', 'root cause 3'] }] });
expect(screen.getByText('Root cause:')).toBeInTheDocument();
expect(screen.getByText(/root cause 1, root cause 2, root cause 3/i)).toBeInTheDocument();
});

it('hides the checkbox when hideCheckbox is true', () => {
renderGoalCard({ ...DEFAULT_PROPS, hideCheckbox: true });
expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
Expand Down
10 changes: 6 additions & 4 deletions similarity_api/src/sim/compute.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,10 @@ def compute_goal_similarities(recipient_id: int, alpha: float, cluster: bool):
AND NULLIF(TRIM(g."name"), '') IS NOT NULL
-- -------------------------------------------
-- Only needed to prevent goals created from non-approved reports from being merged
AND ((ar."approvedAt" IS NOT NULL
AND g."createdVia"::text = 'activityReport')
OR (g."createdVia"::text != 'activityReport')
-- Prevent goals created via TR.
AND (
(ar."approvedAt" IS NOT NULL AND g."createdVia"::text = 'activityReport')
OR (g."createdVia"::text NOT IN ( 'activityReport', 'tr'))
)
-- -------------------------------------------
;
Expand Down Expand Up @@ -193,7 +194,8 @@ def find_similar_goals(recipient_id, goal_name, alpha, include_curated_templates
JOIN "Recipients" r
ON gr."recipientId" = r."id"
WHERE r."id" = :recipient_id
AND NULLIF(TRIM(g."name"), '') IS NOT NULL;
AND NULLIF(TRIM(g."name"), '') IS NOT NULL
AND g."createdVia"::text != 'tr';
""",
{'recipient_id': recipient_id}
)
Expand Down
2 changes: 1 addition & 1 deletion src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ const MAINTENANCE_CATEGORY = {

const GOAL_CREATED_VIA = ['imported', 'activityReport', 'rtr', 'merge', 'admin', 'tr'];

const CURRENT_GOAL_SIMILARITY_VERSION = 3;
const CURRENT_GOAL_SIMILARITY_VERSION = 4;

module.exports = {
CURRENT_GOAL_SIMILARITY_VERSION,
Expand Down
124 changes: 124 additions & 0 deletions src/migrations/20240625000000-correct-fei-root-cause.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
const {
prepMigration,
} = require('../lib/migration');

module.exports = {
up: async (queryInterface) => queryInterface.sequelize.transaction(
async (transaction) => {
await prepMigration(queryInterface, transaction, __filename);
await queryInterface.sequelize.query(/* sql */`

-- Find all the ActivityReportGoalFieldResponses that have the
-- invalid 'Transportation' value
DROP TABLE IF EXISTS argfr_to_update;
CREATE TEMP TABLE argfr_to_update
AS
SELECT
argfr.id argfrid,
arg."goalId" gid
FROM "ActivityReportGoalFieldResponses" argfr
JOIN "ActivityReportGoals" arg
ON "activityReportGoalId" = arg.id
WHERE 'Transportation' = ANY(response);

-- Find all the GoalFieldResponses that have the
-- invalid 'Transportation' value
DROP TABLE IF EXISTS gfr_to_update;
CREATE TEMP TABLE gfr_to_update
AS
SELECT
id gfrid,
"goalId" gid
FROM "GoalFieldResponses"
WHERE 'Transportation' = ANY(response);

-- Make sure this is only the one recipient being updated.
-- If 'Transportation' somehow spread somewhere else then
-- we don't know how to correct it accurately.
DROP TABLE IF EXISTS recipient_list;
CREATE TEMP TABLE recipient_list
AS
SELECT DISTINCT gr."recipientId"
FROM argfr_to_update atu
JOIN "Goals" g
ON g.id = atu.gid
JOIN "Grants" gr
ON g."grantId" = gr.id
UNION
SELECT DISTINCT gr."recipientId"
FROM gfr_to_update atu
JOIN "Goals" g
ON g.id = atu.gid
JOIN "Grants" gr
ON g."grantId" = gr.id
;

-- As a protective step, this will create a divide by zero error and
-- rollback the transaction if there is more than
-- one recipient found with 'Transportation' responses
SELECT 1/
(
LEAST(2, (SELECT COUNT(*) FROM recipient_list))
- 2
)
;

-- Perform the actual updates to ActivityReportGoalFieldResponses
CREATE TEMP TABLE argfr_updates
AS
WITH updater AS (
UPDATE "ActivityReportGoalFieldResponses" argfr
SET response = ARRAY_REPLACE(response,'Transportation','Family Circumstances')
FROM argfr_to_update u
WHERE argfrid = argfr.id
RETURNING
argfr.id argfrid,
'ActivityReportGoalFieldResponses' tablename
) SELECT * FROM updater
;

-- Perform the actual updates to GoalFieldResponses
CREATE TEMP TABLE gfr_updates
AS
WITH updater AS (
UPDATE "GoalFieldResponses" gfr
SET response = ARRAY_REPLACE(response,'Transportation','Family Circumstances')
FROM gfr_to_update u
WHERE gfrid = gfr.id
RETURNING
gfr.id gfrid,
'GoalFieldResponses' tablename
) SELECT * FROM updater
;


-- A quick count of the results that is expected to be:
-- update_cnt | tablename
-- -----------+----------------------------------
-- 3 | GoalFieldResponses
-- 10 | ActivityReportGoalFieldResponses
SELECT
COUNT(*) update_cnt,
tablename
FROM gfr_updates
GROUP BY 2
UNION
SELECT
COUNT(*),
tablename
FROM argfr_updates
GROUP BY 2
;


`, { transaction });
},
),

down: async (queryInterface) => queryInterface.sequelize.transaction(
async (transaction) => {
await prepMigration(queryInterface, transaction, __filename);
// Reversing this should be a separate migration
},
),
};
23 changes: 23 additions & 0 deletions src/services/recipient.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
ActivityReportCollaborator,
ActivityReportApprover,
ActivityReportObjective,
GoalTemplateFieldPrompt,
} from '../models';
import orderRecipientsBy from '../lib/orderRecipientsBy';
import {
Expand All @@ -41,6 +42,7 @@ import {
findOrFailExistingGoal,
responsesForComparison,
} from '../goalServices/helpers';
import getCachedResponse from '../lib/cache';

export async function allArUserIdsByRecipientAndRegion(recipientId, regionId) {
const reports = await ActivityReport.findAll({
Expand Down Expand Up @@ -511,6 +513,24 @@ export async function getGoalsByActivityRecipient(
...filters
},
) {
// Get the GoalTemplateFieldPrompts where title is 'FEI root cause'.
const feiCacheKey = 'feiRootCauseFieldPrompt';
const feiResponse = await getCachedResponse(
feiCacheKey,
async () => {
const feiRootCauseFieldPrompt = await GoalTemplateFieldPrompt.findOne({
attributes: ['goalTemplateId'],
where: {
title: 'FEI root cause',
},
});
return JSON.stringify({
feiRootCauseFieldPrompt,
});
},
JSON.parse,
);

// Scopes.
const { goal: scopes } = await filtersToScopes(filters, { goal: { recipientId } });

Expand Down Expand Up @@ -589,6 +609,7 @@ export async function getGoalsByActivityRecipient(
ELSE 7 END`),
'status_sort'],
[sequelize.literal(`CASE WHEN "Goal"."id" IN (${sanitizedIds}) THEN 1 ELSE 2 END`), 'merged_id'],
[sequelize.literal(`COALESCE("Goal"."goalTemplateId", 0) = ${feiResponse.feiRootCauseFieldPrompt.goalTemplateId}`), 'isFei'],
],
where: goalWhere,
include: [
Expand Down Expand Up @@ -868,6 +889,8 @@ export async function getGoalsByActivityRecipient(
collaborators: [],
onAR: current.onAR,
sessionObjectives: [],
responses: current.responses,
isFei: current.dataValues.isFei,
};

goalToAdd.collaborators.push(...createCollaborators(current));
Expand Down
Loading