Skip to content

Commit

Permalink
Merge branch 'main' into jmo-grant-assignment-email-errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffsmohan authored Mar 11, 2024
2 parents 9e70696 + 25bc33a commit ec2b933
Show file tree
Hide file tree
Showing 37 changed files with 420 additions and 227 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ jobs:
BAKEFILE_PATH: ${{ steps.meta.outputs.bake-file }}
- name: Build and push Docker image
id: build-push
uses: docker/build-push-action@4a13e500e55cf31b7a5d59a38ab2040ab0f42f56 # v5.1.0
uses: docker/build-push-action@af5a7ed5ba88268d5278f7203fb52cd833f66d6e # v5.2.0
with:
context: .
github-token: ${{ secrets.GITHUB_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/qa.yml
Original file line number Diff line number Diff line change
Expand Up @@ -279,4 +279,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Run TFLint
run: tflint --format compact --recursive --minimum-failure-severity=error
run: tflint --format compact --recursive --minimum-failure-severity=warning
8 changes: 7 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
{
"recommendations": ["Vue.volar", "dbaeumer.vscode-eslint"]
"recommendations": [
"Vue.volar",
"hashicorp.terraform",
"ms-azuretools.vscode-docker",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
2 changes: 1 addition & 1 deletion docs/decisions/0007-adopt-vue-recommended-linting.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 0007. Adopt vue/recommended linting

Date: 2024-02-26
Status: Proposed
Status: Accepted

## Context and Problem Statement

Expand Down
113 changes: 109 additions & 4 deletions packages/client/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module.exports = {
es6: true,
},
extends: [
'plugin:vue/essential',
'plugin:vue/recommended',
'@vue/airbnb',
],
parserOptions: {
Expand All @@ -24,11 +24,116 @@ module.exports = {
'no-restricted-syntax': 'off',

'import/prefer-default-export': 'off',

'vue/multi-word-component-names': 'off',
'vue/no-mutating-props': 'off',
},
overrides: [
// This override is part of the process of upgrading plugin:vue/essential -> plugin:vue/recommended.
// In order to avoid a huge stop-the-world PR, we're introducing the plugin upgrade and a file list here
// that ignores the added rules. That way, we can bring individual files into compliance in smaller, less
// disruptive PRs. We'll plan to burn down the list of files in this list until we're done and can remove
// this override entirely.
{
files: [
// File list to ignore generated by `yarn run lint --no-fix --format unix | sed -E 's/:.+//g' | uniq`
'./src/App.vue',
'./src/arpa_reporter/App.vue',
'./src/arpa_reporter/components/AlertBox.vue',
'./src/arpa_reporter/components/DownloadButton.vue',
'./src/arpa_reporter/components/DownloadFileButton.vue',
'./src/arpa_reporter/components/DownloadFileButtonSmall.vue',
'./src/arpa_reporter/components/DownloadTemplateBtn.vue',
'./src/arpa_reporter/components/Navigation.vue',
'./src/arpa_reporter/components/StandardForm.vue',
'./src/arpa_reporter/views/Agencies.vue',
'./src/arpa_reporter/views/Agency.vue',
'./src/arpa_reporter/views/Home.vue',
'./src/arpa_reporter/views/Login.vue',
'./src/arpa_reporter/views/NewTemplate.vue',
'./src/arpa_reporter/views/NewUpload.vue',
'./src/arpa_reporter/views/ReportingPeriod.vue',
'./src/arpa_reporter/views/ReportingPeriods.vue',
'./src/arpa_reporter/views/Subrecipient.vue',
'./src/arpa_reporter/views/Subrecipients.vue',
'./src/arpa_reporter/views/Upload.vue',
'./src/arpa_reporter/views/Uploads.vue',
'./src/arpa_reporter/views/User.vue',
'./src/arpa_reporter/views/Users.vue',
'./src/arpa_reporter/views/Validation.vue',
'./src/components/GrantsTable.vue',
'./src/components/Layout.vue',
'./src/components/Modals/AddKeyword.vue',
'./src/components/Modals/AddOrganization.vue',
'./src/components/Modals/AddTeam.vue',
'./src/components/Modals/AddUser.vue',
'./src/components/Modals/EditOrganization.vue',
'./src/components/Modals/EditTeam.vue',
'./src/components/Modals/EditUser.vue',
'./src/components/Modals/GrantDetailsLegacy.vue',
'./src/components/Modals/ImportTeams.vue',
'./src/components/Modals/ImportUsers.vue',
'./src/components/Modals/ProfileSettings.vue',
'./src/components/Modals/SavedSearchPanel.vue',
'./src/components/Modals/SearchPanel.vue',
'./src/components/SearchFilter.vue',
'./src/components/Uploader.vue',
'./src/components/UserAvatar.vue',
'./src/main.js',
'./src/views/ArpaAnnualPerformanceReporter.vue',
'./src/views/Dashboard.vue',
'./src/views/EligibilityCodes.vue',
'./src/views/GrantDetails.vue',
'./src/views/Grants.vue',
'./src/views/Home.vue',
'./src/views/Keywords.vue',
'./src/views/Login.vue',
'./src/views/MyGrants.vue',
'./src/views/MyProfile.vue',
'./src/views/NotFound.vue',
'./src/views/Organizations.vue',
'./src/views/RecentActivity.vue',
'./src/views/Teams.vue',
'./src/views/UpcomingClosingDates.vue',
'./src/views/Users.vue',
],
rules: {
// List of essential rules we previously had turned off
'vue/multi-word-component-names': 'off',
'vue/no-mutating-props': 'off',
// List of strongly recommended rules introduced (https://eslint.vuejs.org/rules/#priority-b-strongly-recommended-improving-readability)
'vue/attribute-hyphenation': 'off',
'vue/component-definition-name-casing': 'off',
'vue/first-attribute-linebreak': 'off',
'vue/html-closing-bracket-newline': 'off',
'vue/html-closing-bracket-spacing': 'off',
'vue/html-end-tags': 'off',
'vue/html-indent': 'off',
'vue/html-quotes': 'off',
'vue/html-self-closing': 'off',
'vue/max-attributes-per-line': 'off',
'vue/multiline-html-element-content-newline': 'off',
'vue/mustache-interpolation-spacing': 'off',
'vue/no-multi-spaces': 'off',
'vue/no-spaces-around-equal-signs-in-attribute': 'off',
'vue/no-template-shadow': 'off',
'vue/one-component-per-file': 'off',
'vue/prop-name-casing': 'off',
'vue/require-default-prop': 'off',
'vue/require-explicit-emits': 'off',
'vue/require-prop-types': 'off',
'vue/singleline-html-element-content-newline': 'off',
'vue/v-bind-style': 'off',
'vue/v-on-event-hyphenation': 'off',
'vue/v-on-style': 'off',
'vue/v-slot-style': 'off',
// List of recommended rules introduced (https://eslint.vuejs.org/rules/#priority-b-strongly-recommended-improving-readability)
'vue/attributes-order': 'off',
'vue/component-tags-order': 'off',
'vue/no-lone-template': 'off',
'vue/no-multiple-slot-args': 'off',
'vue/no-v-html': 'off',
'vue/order-in-components': 'off',
'vue/this-in-template': 'off',
},
},
{
files: [
'**/__tests__/*.{j,t}s?(x)',
Expand Down
15 changes: 15 additions & 0 deletions packages/server/__tests__/email/email.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,21 @@ describe('Email sender', () => {
expect(body).to.include(name);
expect(body).to.include(moment(openDate).format('MMMM Do YYYY'));
});
it('links to Grants.gov when Grant Details page is not live', async () => {
const agencies = await db.getAgency(fixtures.agencies.accountancy.id);
const agency = agencies[0];
agency.matched_grants = [fixtures.grants.healthAide];
const body = await email.buildDigestBody({ name: 'Saved search test', openDate: '2021-08-05', matchedGrants: agency.matched_grants });
expect(body).to.include(`https://www.grants.gov/search-results-detail/${fixtures.grants.healthAide.grant_id}`);
});
it('links to Grant Finder when Grant Details page is live', async () => {
process.env.NEW_GRANT_DETAILS_PAGE_ENABLED = 'true';
const agencies = await db.getAgency(fixtures.agencies.accountancy.id);
const agency = agencies[0];
agency.matched_grants = [fixtures.grants.healthAide];
const body = await email.buildDigestBody({ name: 'Saved search test', openDate: '2021-08-05', matchedGrants: agency.matched_grants });
expect(body).to.include(`${process.env.WEBSITE_DOMAIN}/grants/${fixtures.grants.healthAide.grant_id}`);
});
});
context('getAndSendGrantForSavedSearch', () => {
it('Sends an email for a saved search', async () => {
Expand Down
34 changes: 26 additions & 8 deletions packages/server/src/lib/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,17 @@ function sendWelcomeEmail(email, httpOrigin) {
});
}

function getGrantDetail(grant, emailNotificationType) {
const grantDetailTemplate = fileSystem.readFileSync(path.join(__dirname, '../static/email_templates/_grant_detail.html'));
function buildGrantDetailUrlSafe(grantId, emailNotificationType) {
const grantDetailUrl = new URL(process.env.WEBSITE_DOMAIN);
grantDetailUrl.pathname = `grants/${mustache.escape(grantId)}`;
grantDetailUrl.searchParams.set('utm_source', 'usdr-grants');
grantDetailUrl.searchParams.set('utm_medium', 'email');
grantDetailUrl.searchParams.set('utm_campaign', mustache.escape(emailNotificationType));
grantDetailUrl.searchParams.set('utm_content', 'grant-details');
return grantDetailUrl.toString();
}

const description = grant.description?.substring(0, 380).replace(/(<([^>]+)>)/ig, '');
function buildGrantsUrlSafe(emailNotificationType) {
const grantsUrl = new URL(process.env.WEBSITE_DOMAIN);
if (emailNotificationType === notificationType.grantDigest) {
grantsUrl.pathname = 'grants';
Expand All @@ -165,7 +172,13 @@ function getGrantDetail(grant, emailNotificationType) {
}
grantsUrl.searchParams.set('utm_source', 'subscription');
grantsUrl.searchParams.set('utm_medium', 'email');
grantsUrl.searchParams.set('utm_campaign', emailNotificationType);
grantsUrl.searchParams.set('utm_campaign', mustache.escape(emailNotificationType));
return grantsUrl.toString();
}

function getGrantDetail(grant, emailNotificationType) {
const grantDetailTemplate = fileSystem.readFileSync(path.join(__dirname, '../static/email_templates/_grant_detail.html'));
const description = grant.description?.substring(0, 380).replace(/(<([^>]+)>)/ig, '');
const grantDetail = mustache.render(
grantDetailTemplate.toString(), {
title: grant.title,
Expand All @@ -178,8 +191,13 @@ function getGrantDetail(grant, emailNotificationType) {
award_ceiling: grant.award_ceiling || 'Not available',
// estimated_funding: grant.estimated_funding, TODO: add once field is available in the database.
cost_sharing: grant.cost_sharing,
link_url: `https://www.grants.gov/search-results-detail/${grant.grant_id}`,
grants_url: grantsUrl.toString(),
link_url_safe: process.env.NEW_GRANT_DETAILS_PAGE_ENABLED === 'true'
? buildGrantDetailUrlSafe(grant.grant_id, emailNotificationType)
: `https://www.grants.gov/search-results-detail/${mustache.escape(grant.grant_id)}`,
link_description: process.env.NEW_GRANT_DETAILS_PAGE_ENABLED === 'true'
? 'Grant Finder'
: 'Grants.gov',
grants_url_safe: buildGrantsUrlSafe(emailNotificationType),
view_grant_label: emailNotificationType === notificationType.grantDigest ? undefined : 'View My Grants',
},
);
Expand Down Expand Up @@ -219,10 +237,10 @@ async function sendGrantAssignedNotficationForAgency(assignee_agency, grantDetai
// TODO: add plain text version of the email
const emailPlain = emailHTML.replace(/<[^>]+>/g, '');
const emailSubject = `Grant Assigned to ${assignee_agency.name}`;
const assginees = await db.getSubscribersForNotification(assignee_agency.id, notificationType.grantAssignment);
const assignees = await db.getSubscribersForNotification(assignee_agency.id, notificationType.grantAssignment);

const inputs = [];
assginees.forEach((assignee) => inputs.push(
assignees.forEach((assignee) => inputs.push(
{
toAddress: assignee.email,
emailHTML,
Expand Down
10 changes: 5 additions & 5 deletions packages/server/src/static/email_templates/_grant_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<tr style="border-collapse:collapse">
<td align="left"
style="Margin:0;padding-bottom:16px;">
<a style="text-decoration:none;" href="{{link_url}}">
<a style="text-decoration:none;" href="{{& link_url_safe }}">
<h2
style="Margin:0;line-height:30px;mso-line-height-rule:exactly;font-family:arial,'helvetica neue',helvetica,sans-serif;font-size:22px;font-style:normal;font-weight:normal;color:#0068D6">
{{title}}</h2>
Expand All @@ -31,7 +31,7 @@
style="Margin:0;padding-bottom:16px;">
<p
style="Margin:0;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;font-family:arial, 'helvetica neue', helvetica, sans-serif;line-height:24px;color:#000000;font-size:16px">
{{{description}}}<span>... View on <a style="color:#0068D6;text-decoration:none;" href="{{link_url}}">Grants.gov</a></span>
{{{description}}}<span>... View on <a style="color:#0068D6;text-decoration:none;" href="{{& link_url_safe }}">{{ link_description }}</a></span>
</p>
</td>
</tr>
Expand Down Expand Up @@ -60,16 +60,16 @@
<tr style="border-collapse:collapse">
<td align="center"
style="Margin:0;padding-left:10px;padding-right:10px;padding-top:15px;padding-bottom:25px">
<!--[if mso]><a href="{{grants_url}}" target="_blank" hidden>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" esdevVmlButton href="{{grants_url}}"
<!--[if mso]><a href="{{& grants_url_safe }}" target="_blank" hidden>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" esdevVmlButton href="{{& grants_url_safe }}"
style="height:49px; v-text-anchor:middle; width:131px" arcsize="50%" strokecolor="#75b6c9" strokeweight="1px" fillcolor="#007bff">
<w:anchorlock></w:anchorlock>
<center style='color:#ffffff; font-family:arial, "helvetica neue", helvetica, sans-serif; font-size:16px; font-weight:400; line-height:16px; mso-text-raise:1px'>{{view_grant_label}}</center>
</v:roundrect></a>
<![endif]-->
<!--[if !mso]><!-- --><span class="msohide es-button-border"
style="border-style:solid;border-color:#75B6C9;background:#007bff;border-width:1px;display:inline-block;border-radius:28px;width:auto;mso-hide:all"><a
href="{{grants_url}}" class="es-button" target="_blank"
href="{{& grants_url_safe }}" class="es-button" target="_blank"
utm-content="view my grants" style="mso-style-priority:100 !important;text-decoration:none;-webkit-text-size-adjust:none;-ms-text-size-adjust:none;mso-line-height-rule:exactly;color:#FFFFFF;font-size:16px;border-style:solid;border-color:#007bff;border-width:15px 25px 15px 25px;display:inline-block;background:#007bff;border-radius:28px;font-family:arial, 'helvetica neue', helvetica, sans-serif;font-weight:normal;font-style:normal;line-height:19px;width:auto;text-align:center">{{view_grant_label}}</a></span>
<!--<![endif]-->
</td>
Expand Down
12 changes: 6 additions & 6 deletions terraform/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ resource "aws_ecs_cluster" "default" {
}

resource "aws_ecs_cluster_capacity_providers" "default" {
count = length(aws_ecs_cluster.default.*)
count = length(aws_ecs_cluster.default[*])

cluster_name = aws_ecs_cluster.default[count.index].name
capacity_providers = ["FARGATE"]
Expand All @@ -166,8 +166,8 @@ module "api" {
]

# Cluster
ecs_cluster_id = join("", aws_ecs_cluster.default.*.id)
ecs_cluster_name = join("", aws_ecs_cluster.default.*.name)
ecs_cluster_id = join("", aws_ecs_cluster.default[*].id)
ecs_cluster_name = join("", aws_ecs_cluster.default[*].name)

# Task configuration
docker_tag = var.api_container_image_tag
Expand Down Expand Up @@ -219,7 +219,7 @@ module "consume_grants" {
security_group_ids = [module.api_to_postgres_security_group.id]

# Task configuration
ecs_cluster_name = join("", aws_ecs_cluster.default.*.name)
ecs_cluster_name = join("", aws_ecs_cluster.default[*].name)
docker_tag = var.api_container_image_tag
unified_service_tags = local.unified_service_tags
datadog_environment_variables = var.consume_grants_datadog_environment_variables
Expand Down Expand Up @@ -260,7 +260,7 @@ module "arpa_audit_report" {
security_group_ids = [module.arpa_audit_report_security_group.id]

# Task configuration
ecs_cluster_name = join("", aws_ecs_cluster.default.*.name)
ecs_cluster_name = join("", aws_ecs_cluster.default[*].name)
docker_tag = var.api_container_image_tag
unified_service_tags = local.unified_service_tags
stop_timeout_seconds = 120
Expand Down Expand Up @@ -348,7 +348,7 @@ module "arpa_treasury_report" {
security_group_ids = [module.arpa_treasury_report_security_group.id]

# Task configuration
ecs_cluster_name = join("", aws_ecs_cluster.default.*.name)
ecs_cluster_name = join("", aws_ecs_cluster.default[*].name)
docker_tag = var.api_container_image_tag
unified_service_tags = local.unified_service_tags
stop_timeout_seconds = 120
Expand Down
8 changes: 4 additions & 4 deletions terraform/modules/gost_api/autoscaling.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resource "aws_appautoscaling_target" "desired_count" {

service_namespace = "ecs"
scalable_dimension = "ecs:service:DesiredCount"
resource_id = "service/${var.ecs_cluster_name}/${join("", aws_ecs_service.default.*.name)}"
resource_id = "service/${var.ecs_cluster_name}/${join("", aws_ecs_service.default[*].name)}"

min_capacity = var.autoscaling_desired_count_minimum
max_capacity = var.autoscaling_desired_count_maximum
Expand All @@ -14,9 +14,9 @@ resource "aws_appautoscaling_policy" "average_cpu_target_tracking" {

name = "${var.namespace}-api-CPU-TargetTrackingScaling"
policy_type = "TargetTrackingScaling"
service_namespace = join("", aws_appautoscaling_target.desired_count.*.service_namespace)
resource_id = join("", aws_appautoscaling_target.desired_count.*.resource_id)
scalable_dimension = join("", aws_appautoscaling_target.desired_count.*.scalable_dimension)
service_namespace = join("", aws_appautoscaling_target.desired_count[*].service_namespace)
resource_id = join("", aws_appautoscaling_target.desired_count[*].resource_id)
scalable_dimension = join("", aws_appautoscaling_target.desired_count[*].scalable_dimension)

target_tracking_scaling_policy_configuration {
predefined_metric_specification {
Expand Down
2 changes: 1 addition & 1 deletion terraform/modules/gost_api/ecs_exec.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ module "ecs_exec_policy" {
"logs:PutLogEvents",
]
resources = [
for arn in aws_cloudwatch_log_group.default.*.arn : "${arn}:log-stream:*"
for arn in aws_cloudwatch_log_group.default[*].arn : "${arn}:log-stream:*"
]
}
}
Expand Down
Loading

0 comments on commit ec2b933

Please sign in to comment.