From 419277b7cf8194d55be3debd4659830cf69d700c Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 3 Mar 2023 12:26:20 -0600 Subject: [PATCH 001/208] Remove the business type from the Stripe::Account.create --- app/legacy_lib/stripe_account_utils.rb | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/app/legacy_lib/stripe_account_utils.rb b/app/legacy_lib/stripe_account_utils.rb index 61b93168b..804ab28ff 100644 --- a/app/legacy_lib/stripe_account_utils.rb +++ b/app/legacy_lib/stripe_account_utils.rb @@ -24,16 +24,6 @@ def self.create(np) params = { type: 'custom', email: np['email'].present? ? np['email'] : np.roles.nonprofit_admins.order('created_at ASC').first.user.email, - business_type: 'company', - company: { - name: np['name'], - address: { - city: np['city'], - state: np['state_code'], - postal_code: np['zip_code'], - country: 'US' - } - }, settings: { payouts: { schedule: { @@ -53,16 +43,10 @@ def self.create(np) if np['website'] && np['website'] =~ URI::regexp params[:business_profile][:url] = np['website'] end - begin - acct = Stripe::Account.create(params, {stripe_version: '2019-09-09' }) - #byebug - rescue Stripe::InvalidRequestError => e - #byebug - [:state, :postal_code].each {|i| params[:company][:address].delete(i)} - params[:business_profile].delete(:url) - acct = Stripe::Account.create(params, {stripe_version: '2019-09-09' }) - end - Qx.update(:nonprofits).set(stripe_account_id: acct.id).where(id: np['id']).execute + acct = Stripe::Account.create(params, {stripe_version: '2019-09-09' }) + np = Nonprofit.find(np['id']) + np.stripe_account_id = acct.id + np.save! return acct.id end end From 4e7feac13085e7fe936d48441938c200ab34fc1b Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sat, 14 Oct 2023 12:16:57 -0500 Subject: [PATCH 002/208] Add a spec for get-params --- .../js/nonprofits/donate/get-params.spec.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 client/js/nonprofits/donate/get-params.spec.js diff --git a/client/js/nonprofits/donate/get-params.spec.js b/client/js/nonprofits/donate/get-params.spec.js new file mode 100644 index 000000000..674c554c3 --- /dev/null +++ b/client/js/nonprofits/donate/get-params.spec.js @@ -0,0 +1,46 @@ +// License: LGPL-3.0-or-later +const getParams = require('./get-params'); +const {getDefaultAmounts} = require('./custom_amounts'); + +describe('.getParams', () => { + describe('custom_amounts:', () => { + it('gives custom_amounts defaults if not passed in', () => { + expect(getParams({})).toHaveProperty('custom_amounts', getDefaultAmounts()); + }); + + it('accepts integers', () => { + expect(getParams({custom_amounts: '3'})).toHaveProperty('custom_amounts', [3]); + }); + + it('accepts floats', () => { + expect(getParams({custom_amounts: '3.5'})).toHaveProperty('custom_amounts', [3.5]); + }); + + it('splits properly', () => { + expect(getParams({custom_amounts: '3.5, 600\n;400;3'})).toHaveProperty('custom_amounts', [3.5, 600, 400, 3]); + }); + + }); + + describe.skip('custom_fields:', () => { + + }); + + describe.skip('multiple_designations:', () => { + + }); + + describe('tags:', () => { + it('keeps tags empty if not passed in', () => { + expect(getParams({})).not.toHaveProperty('tags') + }); + + it('when one tag passed it is in an array by itself', () => { + expect(getParams({tags: 'A tag name'})).toHaveProperty('tags', ['A tag name']); + }); + + it('when a tag has a leading or trailing whitespace, it is stripped',() => { + expect(getParams({tags: ' \tA tag name\n'})).toHaveProperty('tags', ['A tag name']); + }); + }); +}); \ No newline at end of file From b98bf68ba5a4c06b7bf50539c2a9855b6e717f9f Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 7 Nov 2023 16:05:53 -0600 Subject: [PATCH 003/208] Correct a typo in name of postgres for validator --- app/models/export_format.rb | 2 +- ..._format_validator.rb => postgresql_date_format_validator.rb} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename app/validators/{posgresql_date_format_validator.rb => postgresql_date_format_validator.rb} (95%) diff --git a/app/models/export_format.rb b/app/models/export_format.rb index 29202f782..b7e38d5a6 100644 --- a/app/models/export_format.rb +++ b/app/models/export_format.rb @@ -10,7 +10,7 @@ class ExportFormat < ApplicationRecord validates :name, presence: true validates :nonprofit_id, presence: true - validates_with PosgresqlDateFormatValidator, { attribute_name: :date_format } + validates_with PostgresqlDateFormatValidator, { attribute_name: :date_format } validate :valid_custom_columns_and_values? diff --git a/app/validators/posgresql_date_format_validator.rb b/app/validators/postgresql_date_format_validator.rb similarity index 95% rename from app/validators/posgresql_date_format_validator.rb rename to app/validators/postgresql_date_format_validator.rb index ea09fcc9e..33b3ce790 100644 --- a/app/validators/posgresql_date_format_validator.rb +++ b/app/validators/postgresql_date_format_validator.rb @@ -1,5 +1,5 @@ # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -class PosgresqlDateFormatValidator < ActiveModel::Validator +class PostgresqlDateFormatValidator < ActiveModel::Validator # based on https://www.postgresql.org/docs/current/functions-formatting.html # must receive a { options: { attribute_name: } } to do the validation def validate(record) From 4be936641ad97498b327c1368ee579aa4e2a1b6d Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 7 Nov 2023 16:08:34 -0600 Subject: [PATCH 004/208] Remove unused required_keys --- app/legacy_lib/required_keys.rb | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 app/legacy_lib/required_keys.rb diff --git a/app/legacy_lib/required_keys.rb b/app/legacy_lib/required_keys.rb deleted file mode 100644 index c4d75e537..000000000 --- a/app/legacy_lib/required_keys.rb +++ /dev/null @@ -1,12 +0,0 @@ -# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later - -# Given a hash and an array of keys -# Raise an argument error if any keys are missing from the hash - -class RequiredKeys - - def initialize(hash, keys) - missing = keys.select{|k| hash[k].nil?} - raise ArgumentError.new("Missing keys: #{missing}") if missing.any? - end -end From 7685f58b58552f976f11d19dc06d1832057395a7 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 16 Oct 2023 10:36:25 -0500 Subject: [PATCH 005/208] Reuse readableInterval --- .../recurring_donations/readable_interval.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/client/js/nonprofits/recurring_donations/readable_interval.js b/client/js/nonprofits/recurring_donations/readable_interval.js index 068f78e6e..7fb949c6d 100644 --- a/client/js/nonprofits/recurring_donations/readable_interval.js +++ b/client/js/nonprofits/recurring_donations/readable_interval.js @@ -2,12 +2,6 @@ // Given a time interval (eg 1,2,3..) and a time unit (eg. 'day', 'week', 'month', or 'year') // Convert it to a nice readable single interval word like 'daily', 'biweekly', 'yearly', etc.. // If one of the above words don't exist, will return eg 'every 7 months' +const { readableInterval: readable_interval } = require("../../../../javascripts/src/lib/format") module.exports = readable_interval -function readable_interval(interval, time_unit) { - if(interval === 1) return time_unit + 'ly' - if(interval === 4 && time_unit === 'year') return 'quarterly' - if(interval === 2 && time_unit === 'year') return 'biannually' - if(interval === 2 && time_unit === 'week') return 'biweekly' - if(interval === 2 && time_unit === 'month') return 'bimonthly' - else return 'every ' + appl.pluralize(Number(interval), time_unit + 's') -} + From 795541830aac4545cf55786d004377ce73c08b03 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 8 Nov 2023 10:09:12 -0600 Subject: [PATCH 006/208] Add custom fields spec in get-params spec --- client/js/nonprofits/donate/get-params.spec.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/client/js/nonprofits/donate/get-params.spec.js b/client/js/nonprofits/donate/get-params.spec.js index 674c554c3..b24a954a8 100644 --- a/client/js/nonprofits/donate/get-params.spec.js +++ b/client/js/nonprofits/donate/get-params.spec.js @@ -22,8 +22,22 @@ describe('.getParams', () => { }); - describe.skip('custom_fields:', () => { + describe('custom_fields:', () => { + it('creates undefined when undefined', () => { + expect(getParams({})).not.toHaveProperty('custom_fields') + }); + + it('creates custom fields from just a name', () => { + expect(getParams({custom_fields: "name"})).toHaveProperty('custom_fields', [{name: 'name', label: 'name'}]); + }); + it('creates custom fields from name and label', () => { + expect(getParams({custom_fields: "name: Label with Spaces"})).toHaveProperty('custom_fields', [{name: 'name', label: 'Label with Spaces'}]); + }); + + it('creates custom fields from JSON', () => { + expect(getParams({custom_fields: "[{name: 'name', label: 'Label with Spaces'}]"})).toHaveProperty('custom_fields', [{name: 'name', label: 'Label with Spaces'}]); + }); }); describe.skip('multiple_designations:', () => { From aa35649996e2455fd511a7299488be6ea26f3b01 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 8 Nov 2023 10:23:50 -0600 Subject: [PATCH 007/208] Add parseCustomField specs --- .../parseFields/customField/index.spec.ts | 20 +++++++++++++------ .../donate/parseFields/customField/new.ts | 7 ++----- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/client/js/nonprofits/donate/parseFields/customField/index.spec.ts b/client/js/nonprofits/donate/parseFields/customField/index.spec.ts index 3f1f09be5..f11b98d63 100644 --- a/client/js/nonprofits/donate/parseFields/customField/index.spec.ts +++ b/client/js/nonprofits/donate/parseFields/customField/index.spec.ts @@ -1,12 +1,20 @@ // License: LGPL-3.0-or-later import parseCustomField from '.'; +import {parseCustomField as legacyParse} from './legacy'; +import {parseCustomField as newParse} from './new'; -describe('.parseCustomField', () => { - it('when only name provided, label is name', () => { - expect(parseCustomField(" Supporter Tier ")).toStrictEqual({name: "Supporter Tier", label: "Supporter Tier"}); - }); +describe.each([ + ['using index', parseCustomField], + ['using legacy', legacyParse], + ['using new', newParse] +])('parseCustomField', (name, method) => { + describe(name, () => { + it('when only name provided, label is name', () => { + expect(method(" Supporter Tier ")).toStrictEqual({ name: "Supporter Tier", label: "Supporter Tier" }); + }); - it('when label provided, label is set too', () => { - expect(parseCustomField(" Custom Supp Level : Supporter Tier ")).toStrictEqual({name: "Custom Supp Level", label: "Supporter Tier"}); + it('when label provided, label is set too', () => { + expect(method(" Custom Supp Level : Supporter Tier ")).toStrictEqual({ name: "Custom Supp Level", label: "Supporter Tier" }); + }); }); }); \ No newline at end of file diff --git a/client/js/nonprofits/donate/parseFields/customField/new.ts b/client/js/nonprofits/donate/parseFields/customField/new.ts index a8e31ebc0..b558f585f 100644 --- a/client/js/nonprofits/donate/parseFields/customField/new.ts +++ b/client/js/nonprofits/donate/parseFields/customField/new.ts @@ -1,12 +1,9 @@ // License: LGPL-3.0-or-later import { CustomFieldDescription } from "."; -export function newParseCustomField(input:string) : CustomFieldDescription { +export function parseCustomField(input:string) : CustomFieldDescription { const [name, ...rest] = input.split(":").map((s) => s.trim()) - let label = null; - if (rest.length > 0) { - label = rest[0] - } + const label = rest.length > 0 ? rest[0] : null; return {name, label: label || name } as CustomFieldDescription; }; From be92e96e1656a766f88322aa17266297d751be86 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 8 Nov 2023 10:30:02 -0600 Subject: [PATCH 008/208] Add parseCustomFields specs --- .../parseFields/customFields/index.spec.ts | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts b/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts index ea9f161fd..e02d3d9e5 100644 --- a/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts +++ b/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts @@ -1,20 +1,31 @@ // License: LGPL-3.0-or-later import parseCustomFields from '.'; +import {parseCustomFields as legacyParse} from './legacy'; +import {parseCustomFields as newParse} from './new'; describe('.parseCustomFields', () => { - it('when only name provided, label is name', () => { - expect(parseCustomFields(" Supporter Tier ")).toStrictEqual([{name: "Supporter Tier", label: "Supporter Tier"}]); + it('when passed an array of json, it parses it', () => { + expect(parseCustomFields("[{name: 'name', label: 'labeled'}] ")).toStrictEqual([{name: "name", label: "labeled"}]); }); +}); - it('when label provided, label is set too', () => { - expect(parseCustomFields(" Custom Supp Level : Supporter Tier ")).toStrictEqual([{name: "Custom Supp Level", label: "Supporter Tier"}]); - }); +describe.each([ + ['with index', parseCustomFields], + ['with legacy', legacyParse], + ['with new', newParse] +] +)('parse with simple custom fields', (name, method) => { + describe(name, () => { + it('when only name provided, label is name', () => { + expect(method(" Supporter Tier ")).toStrictEqual([{name: "Supporter Tier", label: "Supporter Tier"}]); + }); - it('when passed an array looking thing, it treats a standard label', () => { - expect(parseCustomFields(" [Custom Supp Level] : Supporter Tier ")).toStrictEqual([{name: "[Custom Supp Level]", label: "Supporter Tier"}]); - }); + it('when label provided, label is set too', () => { + expect(method(" Custom Supp Level : Supporter Tier ")).toStrictEqual([{name: "Custom Supp Level", label: "Supporter Tier"}]); + }); - it('when passed an array of json, it parses it', () => { - expect(parseCustomFields("[{name: 'name', label: 'labeled'}] ")).toStrictEqual([{name: "name", label: "labeled"}]); + it('when passed an array looking thing, it treats a standard label', () => { + expect(method(" [Custom Supp Level] : Supporter Tier ")).toStrictEqual([{name: "[Custom Supp Level]", label: "Supporter Tier"}]); + }); }); }); \ No newline at end of file From fca118e400c6590a2cdd3dc50d29461e1931abde Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 8 Nov 2023 11:08:47 -0600 Subject: [PATCH 009/208] Update to es2016 lib for TS --- tsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index bb0ae7134..a8b097c2d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,7 @@ "target": "es5", "jsx": "react", "experimentalDecorators": true, - "lib": ["dom","es5", "scripthost", "ES2015"], + "lib": ["dom","es5", "scripthost", "es2016"], "paths": { "*": [ "./types/*"] }, "typeRoots" : [ "node_modules/@types", From f8e37f216d25f247b70126b6c56cbb490d9a12ff Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 8 Nov 2023 10:46:45 -0600 Subject: [PATCH 010/208] Add specs to donate/components/info-step/customFields --- .../__snapshots__/index.spec.ts.snap | 114 ++++++++++++++++++ .../info-step/customFields/index.spec.ts | 35 ++++-- .../info-step/customFields/index.ts | 11 +- .../info-step/customFields/legacy.ts | 11 ++ .../components/info-step/customFields/new.ts | 10 ++ 5 files changed, 159 insertions(+), 22 deletions(-) create mode 100644 client/js/nonprofits/donate/components/info-step/customFields/legacy.ts create mode 100644 client/js/nonprofits/donate/components/info-step/customFields/new.ts diff --git a/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap b/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap index 8ee3bdb5c..11478b909 100644 --- a/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap +++ b/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap @@ -37,3 +37,117 @@ Object { "text": undefined, } `; + +exports[`customFields with index sets the the field correctly 1`] = ` +Object { + "children": Array [ + Object { + "children": undefined, + "data": Object { + "props": Object { + "name": "customFields[Name of Field]", + "placeholder": "Field Label", + }, + }, + "elm": undefined, + "key": undefined, + "sel": "input", + "text": undefined, + }, + Object { + "children": undefined, + "data": Object { + "props": Object { + "name": "customFields[Another Field Name]", + "placeholder": "Label2", + }, + }, + "elm": undefined, + "key": undefined, + "sel": "input", + "text": undefined, + }, + ], + "data": Object {}, + "elm": undefined, + "key": undefined, + "sel": "div", + "text": undefined, +} +`; + +exports[`customFields with legacy sets the the field correctly 1`] = ` +Object { + "children": Array [ + Object { + "children": undefined, + "data": Object { + "props": Object { + "name": "customFields[Name of Field]", + "placeholder": "Field Label", + }, + }, + "elm": undefined, + "key": undefined, + "sel": "input", + "text": undefined, + }, + Object { + "children": undefined, + "data": Object { + "props": Object { + "name": "customFields[Another Field Name]", + "placeholder": "Label2", + }, + }, + "elm": undefined, + "key": undefined, + "sel": "input", + "text": undefined, + }, + ], + "data": Object {}, + "elm": undefined, + "key": undefined, + "sel": "div", + "text": undefined, +} +`; + +exports[`customFields with new sets the the field correctly 1`] = ` +Object { + "children": Array [ + Object { + "children": undefined, + "data": Object { + "props": Object { + "name": "customFields[Name of Field]", + "placeholder": "Field Label", + }, + }, + "elm": undefined, + "key": undefined, + "sel": "input", + "text": undefined, + }, + Object { + "children": undefined, + "data": Object { + "props": Object { + "name": "customFields[Another Field Name]", + "placeholder": "Label2", + }, + }, + "elm": undefined, + "key": undefined, + "sel": "input", + "text": undefined, + }, + ], + "data": Object {}, + "elm": undefined, + "key": undefined, + "sel": "div", + "text": undefined, +} +`; diff --git a/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts b/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts index f0c30979a..c383f50e2 100644 --- a/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts +++ b/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts @@ -1,20 +1,29 @@ // License: LGPL-3.0-or-later import customFields from "." +import {customFields as legacyCustomFields} from './legacy'; +import {customFields as newCustomFields} from './new'; -describe('customFields', () => { - it('sets the the field correctly', () => { - expect(customFields([ - {name: "Name of Field", label: "Field Label"}, - {name: "Another Field Name", label: "Label2"} - ])).toMatchSnapshot(); - }); - it('returns blank string correctly with nothing passed', () => { - expect(customFields()).toBe(""); - }); - it('returns blank string correctly with null passed', () => { - expect(customFields(null)).toBe(""); - }); +describe.each([ + ['with index', customFields], + ['with legacy', legacyCustomFields], + ['with new', newCustomFields] +])('customFields', (name, method) => { + describe(name, () => { + it('sets the the field correctly', () => { + expect(method([ + {name: "Name of Field", label: "Field Label"}, + {name: "Another Field Name", label: "Label2"} + ])).toMatchSnapshot(); + }); + it('returns blank string correctly with nothing passed', () => { + expect(method()).toBe(""); + }); + + it('returns blank string correctly with null passed', () => { + expect(method(null)).toBe(""); + }); + }); }); \ No newline at end of file diff --git a/client/js/nonprofits/donate/components/info-step/customFields/index.ts b/client/js/nonprofits/donate/components/info-step/customFields/index.ts index 70547093f..0fadd222b 100644 --- a/client/js/nonprofits/donate/components/info-step/customFields/index.ts +++ b/client/js/nonprofits/donate/components/info-step/customFields/index.ts @@ -1,11 +1,4 @@ // License: LGPL-3.0-or-later -import {map as Rmap} from 'ramda'; -import { CustomFieldDescription } from '../../../types'; -import customField from './customField'; -const h = require('snabbdom/h'); +import {customFields} from './legacy'; -export default function customFields(fields?:CustomFieldDescription[]|null): ReturnType | '' { - if (!fields) return ''; - - return h('div', Rmap(customField, fields)); -} +export default customFields; diff --git a/client/js/nonprofits/donate/components/info-step/customFields/legacy.ts b/client/js/nonprofits/donate/components/info-step/customFields/legacy.ts new file mode 100644 index 000000000..e1a356ac6 --- /dev/null +++ b/client/js/nonprofits/donate/components/info-step/customFields/legacy.ts @@ -0,0 +1,11 @@ +// License: LGPL-3.0-or-later +import {map as Rmap} from 'ramda'; +import { CustomFieldDescription } from '../../../types'; +import customField from './customField'; +const h = require('snabbdom/h'); + +export function customFields(fields?:CustomFieldDescription[]|null): ReturnType | '' { + if (!fields) return ''; + + return h('div', Rmap(customField, fields)); +} diff --git a/client/js/nonprofits/donate/components/info-step/customFields/new.ts b/client/js/nonprofits/donate/components/info-step/customFields/new.ts new file mode 100644 index 000000000..c7ec3a64b --- /dev/null +++ b/client/js/nonprofits/donate/components/info-step/customFields/new.ts @@ -0,0 +1,10 @@ +// License: LGPL-3.0-or-later +import { CustomFieldDescription } from '../../../types'; +import customField from './customField'; +const h = require('snabbdom/h'); + +export function customFields(fields?:CustomFieldDescription[]|null): ReturnType | '' { + if (!fields) return ''; + + return h('div', fields.map(customField)); +} From 534418852ad669ac5982d0a95c3d0c283ea87005 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 8 Nov 2023 11:12:01 -0600 Subject: [PATCH 011/208] Add a type property to the CustomFieldDescription --- .../__snapshots__/index.spec.ts.snap | 38 ------------------- .../customFields/customField.spec.ts | 2 +- .../info-step/customFields/index.spec.ts | 4 +- .../js/nonprofits/donate/get-params.spec.js | 6 +-- .../parseFields/customField/index.spec.ts | 4 +- .../donate/parseFields/customField/index.ts | 1 + .../donate/parseFields/customField/legacy.ts | 2 +- .../donate/parseFields/customField/new.ts | 2 +- .../customFields/JsonStringParser.spec.ts | 8 ++-- .../customFields/JsonStringParser.ts | 6 ++- .../parseFields/customFields/index.spec.ts | 8 ++-- 11 files changed, 25 insertions(+), 56 deletions(-) diff --git a/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap b/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap index 11478b909..888ea36c6 100644 --- a/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap +++ b/client/js/nonprofits/donate/components/info-step/customFields/__snapshots__/index.spec.ts.snap @@ -1,43 +1,5 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`customFields sets the the field correctly 1`] = ` -Object { - "children": Array [ - Object { - "children": undefined, - "data": Object { - "props": Object { - "name": "customFields[Name of Field]", - "placeholder": "Field Label", - }, - }, - "elm": undefined, - "key": undefined, - "sel": "input", - "text": undefined, - }, - Object { - "children": undefined, - "data": Object { - "props": Object { - "name": "customFields[Another Field Name]", - "placeholder": "Label2", - }, - }, - "elm": undefined, - "key": undefined, - "sel": "input", - "text": undefined, - }, - ], - "data": Object {}, - "elm": undefined, - "key": undefined, - "sel": "div", - "text": undefined, -} -`; - exports[`customFields with index sets the the field correctly 1`] = ` Object { "children": Array [ diff --git a/client/js/nonprofits/donate/components/info-step/customFields/customField.spec.ts b/client/js/nonprofits/donate/components/info-step/customFields/customField.spec.ts index 7fa52c987..b2ddd57b5 100644 --- a/client/js/nonprofits/donate/components/info-step/customFields/customField.spec.ts +++ b/client/js/nonprofits/donate/components/info-step/customFields/customField.spec.ts @@ -3,6 +3,6 @@ import customField from "./customField" describe('customField', () => { it('sets the the field correctly', () => { - expect(customField({name: "Name of Field", label: "Field Label"})).toMatchSnapshot(); + expect(customField({name: "Name of Field", label: "Field Label", type: 'supporter'})).toMatchSnapshot(); }) }); \ No newline at end of file diff --git a/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts b/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts index c383f50e2..5c915f4d1 100644 --- a/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts +++ b/client/js/nonprofits/donate/components/info-step/customFields/index.spec.ts @@ -13,8 +13,8 @@ describe.each([ describe(name, () => { it('sets the the field correctly', () => { expect(method([ - {name: "Name of Field", label: "Field Label"}, - {name: "Another Field Name", label: "Label2"} + {name: "Name of Field", label: "Field Label", type: 'supporter'}, + {name: "Another Field Name", label: "Label2", type: 'supporter'} ])).toMatchSnapshot(); }); diff --git a/client/js/nonprofits/donate/get-params.spec.js b/client/js/nonprofits/donate/get-params.spec.js index b24a954a8..b33595bef 100644 --- a/client/js/nonprofits/donate/get-params.spec.js +++ b/client/js/nonprofits/donate/get-params.spec.js @@ -28,15 +28,15 @@ describe('.getParams', () => { }); it('creates custom fields from just a name', () => { - expect(getParams({custom_fields: "name"})).toHaveProperty('custom_fields', [{name: 'name', label: 'name'}]); + expect(getParams({custom_fields: "name"})).toHaveProperty('custom_fields', [{name: 'name', label: 'name', type: 'supporter'}]); }); it('creates custom fields from name and label', () => { - expect(getParams({custom_fields: "name: Label with Spaces"})).toHaveProperty('custom_fields', [{name: 'name', label: 'Label with Spaces'}]); + expect(getParams({custom_fields: "name: Label with Spaces"})).toHaveProperty('custom_fields', [{name: 'name', label: 'Label with Spaces', type: 'supporter'}]); }); it('creates custom fields from JSON', () => { - expect(getParams({custom_fields: "[{name: 'name', label: 'Label with Spaces'}]"})).toHaveProperty('custom_fields', [{name: 'name', label: 'Label with Spaces'}]); + expect(getParams({custom_fields: "[{name: 'name', label: 'Label with Spaces', type: 'supporter'}]"})).toHaveProperty('custom_fields', [{name: 'name', label: 'Label with Spaces', type: 'supporter'}]); }); }); diff --git a/client/js/nonprofits/donate/parseFields/customField/index.spec.ts b/client/js/nonprofits/donate/parseFields/customField/index.spec.ts index f11b98d63..b44cf954a 100644 --- a/client/js/nonprofits/donate/parseFields/customField/index.spec.ts +++ b/client/js/nonprofits/donate/parseFields/customField/index.spec.ts @@ -10,11 +10,11 @@ describe.each([ ])('parseCustomField', (name, method) => { describe(name, () => { it('when only name provided, label is name', () => { - expect(method(" Supporter Tier ")).toStrictEqual({ name: "Supporter Tier", label: "Supporter Tier" }); + expect(method(" Supporter Tier ")).toStrictEqual({ name: "Supporter Tier", label: "Supporter Tier", type: 'supporter'}); }); it('when label provided, label is set too', () => { - expect(method(" Custom Supp Level : Supporter Tier ")).toStrictEqual({ name: "Custom Supp Level", label: "Supporter Tier" }); + expect(method(" Custom Supp Level : Supporter Tier ")).toStrictEqual({ name: "Custom Supp Level", label: "Supporter Tier", type: 'supporter' }); }); }); }); \ No newline at end of file diff --git a/client/js/nonprofits/donate/parseFields/customField/index.ts b/client/js/nonprofits/donate/parseFields/customField/index.ts index 33e196933..c56f93266 100644 --- a/client/js/nonprofits/donate/parseFields/customField/index.ts +++ b/client/js/nonprofits/donate/parseFields/customField/index.ts @@ -4,6 +4,7 @@ import { parseCustomField } from "./legacy"; export interface CustomFieldDescription { name: NonNullable; label: NonNullable; + type: 'supporter'; } diff --git a/client/js/nonprofits/donate/parseFields/customField/legacy.ts b/client/js/nonprofits/donate/parseFields/customField/legacy.ts index 4d252e6ae..35192623d 100644 --- a/client/js/nonprofits/donate/parseFields/customField/legacy.ts +++ b/client/js/nonprofits/donate/parseFields/customField/legacy.ts @@ -4,5 +4,5 @@ import { CustomFieldDescription } from '.'; export function parseCustomField(f:string) :CustomFieldDescription { const [name, label] = Rmap(Rtrim, Rsplit(':', f)) - return {name, label: label || name} + return {name, label: label || name, type: 'supporter'}; } \ No newline at end of file diff --git a/client/js/nonprofits/donate/parseFields/customField/new.ts b/client/js/nonprofits/donate/parseFields/customField/new.ts index b558f585f..adea0b20e 100644 --- a/client/js/nonprofits/donate/parseFields/customField/new.ts +++ b/client/js/nonprofits/donate/parseFields/customField/new.ts @@ -5,5 +5,5 @@ export function parseCustomField(input:string) : CustomFieldDescription { const [name, ...rest] = input.split(":").map((s) => s.trim()) const label = rest.length > 0 ? rest[0] : null; - return {name, label: label || name } as CustomFieldDescription; + return {name, label: label || name, type: 'supporter' } as CustomFieldDescription; }; diff --git a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.spec.ts b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.spec.ts index d7c690b46..e3864ce06 100644 --- a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.spec.ts +++ b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.spec.ts @@ -6,7 +6,9 @@ describe('JsonStringParser', () => { describe.each([ ["with bracket", "["], ["with brace", "[{]"], - ["with non-custom-field-description", "[{name:'name'}]"] + ["with non-custom-field-description", "[{name:'name'}]"], + ["with no type given", "[{name:'name', label: 'LABEL'}]"], + ["with type given but wrong value", "[{name:'name', label: 'LABEL', type: 'something else'}]"] ])("when invalid %s", (_n, input)=> { const parser = new JsonStringParser(input) it('has correct result', () => { @@ -23,8 +25,8 @@ describe('JsonStringParser', () => { }); describe.each([ - ['with non-custom-field-description', "[{name:'name', label: 'another'}]", [{name: 'name', label: 'another'}]], - ['with different json quote', '[{name:"name", label: "another"}]', [{name: 'name', label: 'another'}]], + ['with non-custom-field-description', "[{name:'name', label: 'another', type: 'supporter'}]", [{name: 'name', label: 'another', type: 'supporter'}]], + ['with different json quote', '[{name:"name", label: "another", type: "supporter"}]', [{name: 'name', label: 'another', type: 'supporter'}]], ['when an empty array', '[]', []], ])("when valid %s", (_name, input, result) => { const parser = new JsonStringParser(input); diff --git a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts index a729eac54..eac6da492 100644 --- a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts +++ b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts @@ -1,10 +1,14 @@ // License: LGPL-3.0-or-later import has from 'lodash/has'; +import get from 'lodash/get'; import { parse } from 'json5'; import { CustomFieldDescription } from '../customField'; function isCustomFieldDesc(item:unknown) : item is CustomFieldDescription { - return typeof item == 'object' && has(item, 'name') && has(item, 'label'); + return typeof item == 'object' && + has(item, 'name') && + has(item, 'label') && + ['supporter'].includes(get(item, 'type')); } export default class JsonStringParser { public errors:SyntaxError[] = []; diff --git a/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts b/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts index e02d3d9e5..b9938c714 100644 --- a/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts +++ b/client/js/nonprofits/donate/parseFields/customFields/index.spec.ts @@ -5,7 +5,7 @@ import {parseCustomFields as newParse} from './new'; describe('.parseCustomFields', () => { it('when passed an array of json, it parses it', () => { - expect(parseCustomFields("[{name: 'name', label: 'labeled'}] ")).toStrictEqual([{name: "name", label: "labeled"}]); + expect(parseCustomFields("[{name: 'name', label: 'labeled', type:'supporter'}] ")).toStrictEqual([{name: "name", label: "labeled", type: 'supporter'}]); }); }); @@ -17,15 +17,15 @@ describe.each([ )('parse with simple custom fields', (name, method) => { describe(name, () => { it('when only name provided, label is name', () => { - expect(method(" Supporter Tier ")).toStrictEqual([{name: "Supporter Tier", label: "Supporter Tier"}]); + expect(method(" Supporter Tier ")).toStrictEqual([{name: "Supporter Tier", label: "Supporter Tier", type: 'supporter'}]); }); it('when label provided, label is set too', () => { - expect(method(" Custom Supp Level : Supporter Tier ")).toStrictEqual([{name: "Custom Supp Level", label: "Supporter Tier"}]); + expect(method(" Custom Supp Level : Supporter Tier ")).toStrictEqual([{name: "Custom Supp Level", label: "Supporter Tier", type: 'supporter'}]); }); it('when passed an array looking thing, it treats a standard label', () => { - expect(method(" [Custom Supp Level] : Supporter Tier ")).toStrictEqual([{name: "[Custom Supp Level]", label: "Supporter Tier"}]); + expect(method(" [Custom Supp Level] : Supporter Tier ")).toStrictEqual([{name: "[Custom Supp Level]", label: "Supporter Tier", type: 'supporter'}]); }); }); }); \ No newline at end of file From 828b242739cb9deab8e53644cb5df70ad858a072 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 8 Nov 2023 11:31:07 -0600 Subject: [PATCH 012/208] Add type to get-params --- client/js/nonprofits/donate/amt.ts | 2 +- client/js/nonprofits/donate/get-params.d.ts | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 client/js/nonprofits/donate/get-params.d.ts diff --git a/client/js/nonprofits/donate/amt.ts b/client/js/nonprofits/donate/amt.ts index a0886927d..eaf9821d4 100644 --- a/client/js/nonprofits/donate/amt.ts +++ b/client/js/nonprofits/donate/amt.ts @@ -5,7 +5,7 @@ export interface AmountButtonDesc { highlight: string | false; } -type AmountButtonInput = { +export type AmountButtonInput = { amount: number; highlight: string | boolean; } | number; diff --git a/client/js/nonprofits/donate/get-params.d.ts b/client/js/nonprofits/donate/get-params.d.ts new file mode 100644 index 000000000..e3700d394 --- /dev/null +++ b/client/js/nonprofits/donate/get-params.d.ts @@ -0,0 +1,14 @@ +// License: LGPL-3.0-or-later +import { AmountButtonInput } from "./amt"; + +type GetParamsInputBase = {[prop:string]: any}; + +export type GetParamsOutput = TInput & { + custom_amounts:AmountButtonInput[], + tags?:string[], +} + +declare const getParams: (input:GetParamsInput) => GetParamsOutput; + + +export default getParams; \ No newline at end of file From 322d3fe2e67342019ca96468ca1e091fec627cd6 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 17:55:29 -0600 Subject: [PATCH 013/208] Use typeToFormInputName in customField to correctly set the type of custom field it is --- .../components/info-step/customFields/customField.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/js/nonprofits/donate/components/info-step/customFields/customField.ts b/client/js/nonprofits/donate/components/info-step/customFields/customField.ts index 46ef72c5c..459e41d3d 100644 --- a/client/js/nonprofits/donate/components/info-step/customFields/customField.ts +++ b/client/js/nonprofits/donate/components/info-step/customFields/customField.ts @@ -2,10 +2,15 @@ import { CustomFieldDescription } from "../../../types"; const h = require('snabbdom/h'); + +const typeToFormInputName = { + 'supporter': 'customFields' +} + export default function customField(field: CustomFieldDescription) : ReturnType { return h('input', { props: { - name: `customFields[${field.name}]`, + name: `${typeToFormInputName[field.type]}[${field.name}]`, placeholder: field.label } }); From 5adb96863a3bb3354d7ce9f1d7c812436e48ac58 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 18:10:22 -0600 Subject: [PATCH 014/208] Correct return type on sustaining_amount --- client/js/nonprofits/donate/sustaining_amount.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/nonprofits/donate/sustaining_amount.ts b/client/js/nonprofits/donate/sustaining_amount.ts index 310560a92..51fd5205a 100644 --- a/client/js/nonprofits/donate/sustaining_amount.ts +++ b/client/js/nonprofits/donate/sustaining_amount.ts @@ -2,7 +2,7 @@ const h = require('snabbdom/h') declare const app: {widget?:{custom_recurring_donation_phrase?:string}} |undefined; -export default function getSustainingAmount() : any[]| string | null { +export default function getSustainingAmount() : ReturnType[]| string | null { if (app && app.widget && app.widget.custom_recurring_donation_phrase) { From b69072aa61f9b9c5453cf6fca489b519817cedda Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 18:10:53 -0600 Subject: [PATCH 015/208] Add initial typescript defs for payment-step --- client/js/nonprofits/donate/payment-step.d.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 client/js/nonprofits/donate/payment-step.d.ts diff --git a/client/js/nonprofits/donate/payment-step.d.ts b/client/js/nonprofits/donate/payment-step.d.ts new file mode 100644 index 000000000..8a356fa55 --- /dev/null +++ b/client/js/nonprofits/donate/payment-step.d.ts @@ -0,0 +1,27 @@ +import h from 'snabbdom/h'; + + + +type Supporter = any; +type Donation = any; +type DedicationData = any; +type SelectedPaymentType = any; +type ParamsType = any; + +interface InitInput { + supporter$: (supporter?:Supporter) => Supporter; + donation$: (donation?:Donation) => Donation; + dedicationData$: (dedication?:DedicationData) => DedicationData; + activePaymentTab$: (selectedPayment?:SelectedPaymentType) => SelectedPaymentType; + params$: (params?:ParamsType) => ParamsType +} + +type InitOutput = any; + + +export declare function init( input: InitInput): InitOutput; + + +type ViewState = any; + +export declare function view(state:ViewState) : ReturnType; \ No newline at end of file From 4b00135f1069d7779af2d64f97303db1057d0379 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 18:44:29 -0600 Subject: [PATCH 016/208] Install js-routes --- Gemfile | 4 +++- Gemfile.lock | 3 +++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index 6f73f9fc2..ac36a27ac 100644 --- a/Gemfile +++ b/Gemfile @@ -184,4 +184,6 @@ gem 'fx', git: 'https://github.com/teoljungberg/fx.git', ref: '946cdccbd12333de gem 'has_scope' -gem 'globalid', git: "https://github.com/CommitChange/globalid.git", tag: "0.4.2.1" \ No newline at end of file +gem 'globalid', git: "https://github.com/CommitChange/globalid.git", tag: "0.4.2.1" + +gem 'js-routes' diff --git a/Gemfile.lock b/Gemfile.lock index 932cab738..5662c55c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,6 +280,8 @@ GEM jbuilder (2.9.1) activesupport (>= 4.2.0) jmespath (1.6.1) + js-routes (2.2.7) + railties (>= 4) json (2.6.1) kaminari (1.2.1) activesupport (>= 4.1.0) @@ -556,6 +558,7 @@ DEPENDENCIES httparty i18n-js jbuilder + js-routes json (>= 2.3.0) kaminari lograge From 61c2c39de558a092b94a3bfb264a62af2b080608 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 18:45:20 -0600 Subject: [PATCH 017/208] Ignore generated js routes --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 355eb68c0..276f33bf5 100755 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,7 @@ javascripts/api /.yardoc # Data files you wouldn't want to push -*.csv \ No newline at end of file +*.csv + +app/javascript/routes.js +app/javascript/routes.d.ts \ No newline at end of file From ab9c51d8f87a0e42f6e9b60c0efda5aeec5008bd Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 18:45:47 -0600 Subject: [PATCH 018/208] Add initializer for js routes --- config/initializers/js_routes.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 config/initializers/js_routes.rb diff --git a/config/initializers/js_routes.rb b/config/initializers/js_routes.rb new file mode 100644 index 000000000..b1f92216f --- /dev/null +++ b/config/initializers/js_routes.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/main/LICENSE +JsRoutes.setup do |c| + # Setup your JS module system: + # ESM, CJS, AMD, UMD or nil + c.module_type = 'CJS' + c.camel_case = true +end From 06ae8ffdd52d61700b6ad1cce2af03585246f1b8 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 18:45:57 -0600 Subject: [PATCH 019/208] Add js routes tasks --- lib/tasks/js.rake | 17 ++++++++++++ lib/tasks/js_routes.rake | 13 ++++++++++ spec/tasks/js_spec.rb | 56 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 lib/tasks/js.rake create mode 100644 lib/tasks/js_routes.rake create mode 100644 spec/tasks/js_spec.rb diff --git a/lib/tasks/js.rake b/lib/tasks/js.rake new file mode 100644 index 000000000..acf8c9c2c --- /dev/null +++ b/lib/tasks/js.rake @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/main/LICENSE +desc 'Javascript related tasks' +namespace :js do + desc 'generate all the pre-build Javascript' + task generate: ['js:routes:typescript', 'i18n:js:export'] + namespace :routes do + desc 'delete generated route files' + task clean: :environment do + js_dir = Rails.root.join('app/javascript') + FileUtils.rm_f js_dir.join('routes.js') + FileUtils.rm_f js_dir.join('routes.d.ts') + end + end +end diff --git a/lib/tasks/js_routes.rake b/lib/tasks/js_routes.rake new file mode 100644 index 000000000..b216ff57d --- /dev/null +++ b/lib/tasks/js_routes.rake @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/main/LICENSE + +# rubocop:disable Rake/Desc -- we're enhancing a task we didn't create so no description + +# adds support for generating js routes as part of the assets:precompile task +namespace :assets do + task precompile: 'js:routes:typescript' +end + +# rubocop:enable Rake/Desc diff --git a/spec/tasks/js_spec.rb b/spec/tasks/js_spec.rb new file mode 100644 index 000000000..46f69e1e1 --- /dev/null +++ b/spec/tasks/js_spec.rb @@ -0,0 +1,56 @@ +# frozen_string_literal: true + +# License: AGPL-3.0-or-later WITH WTO-AP-3.0-or-later +# Full license explanation at https://github.com/houdiniproject/houdini/blob/main/LICENSE +require 'rails_helper' + +Rails.application.load_tasks + +describe 'js.rake' do # rubocop:disable RSpec/DescribeClass + def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") + end + + let(:js_dir) { Rails.root.join('app/javascript') } + + def wrap_task(rake_taskname) + task = Rake::Task[rake_taskname] + Rake.application.tasks.each(&:reenable) + task.invoke + yield + Rake.application.tasks.each(&:reenable) + end + + def verify_cleaned(rake_taskname) # rubocop:disable Metrics/AbcSize + wrap_task(rake_taskname) do + expect(!File.exist?(js_dir.join('routes.js'))).to( + be(true), + "#{js_dir.join('routes.js')} wasn't cleaned by bin/rails #{rake_taskname}" + ) + + expect(!File.exist?(js_dir.join('routes.d.ts'))).to( + be(true), + "#{js_dir.join('routes.d.ts')} wasn't cleaned by bin/rails #{rake_taskname}" + ) + end + end + + def verify_task_generate(rake_taskname) + wrap_task(rake_taskname) do + expect(File.exist?(js_dir.join('routes.js'))).to( + be(true), + "#{js_dir.join('routes.js')} wasn't generated by bin/rails #{rake_taskname}" + ) + + expect(File.exist?(js_dir.join('routes.d.ts'))).to( + be(true), + "#{js_dir.join('routes.d.ts')} wasn't generated by bin/rails #{rake_taskname}" + ) + end + end + + it 'export and clean' do # rubocop:disable RSpec/NoExpectationExample + verify_task_generate('js:routes:typescript') + verify_cleaned('js:routes:clean') + end +end From 1c45d4a19257b270877964ecb14639c9489a4d9d Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 10 Nov 2023 21:50:14 -0600 Subject: [PATCH 020/208] Correct a typing bug --- javascripts/src/lib/format.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/javascripts/src/lib/format.ts b/javascripts/src/lib/format.ts index 5f4f4ee38..e5088402a 100644 --- a/javascripts/src/lib/format.ts +++ b/javascripts/src/lib/format.ts @@ -4,14 +4,8 @@ import * as deprecated_format from './deprecated_format' export function centsToDollars(cents:string|number|undefined, options:{noCents?:boolean}={}):string { if(cents === undefined) return '0' - let centsAsNumber:number = undefined - if (typeof cents === 'string') - { - centsAsNumber = Number(cents) - } - else { - centsAsNumber = cents - } + const centsAsNumber:number = typeof cents === 'string' ? Number(cents) : cents; + return numberWithCommas((centsAsNumber / 100.0).toFixed(options.noCents ? 0 : 2).toString()).replace(/\.00$/,'') } From 330164c198331f34c9a50957a98effc6593b5c54 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 10 Nov 2023 21:53:08 -0600 Subject: [PATCH 021/208] Correct typing bug in React component --- .../src/components/session_login_page/ErrorDivDetails.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascripts/src/components/session_login_page/ErrorDivDetails.tsx b/javascripts/src/components/session_login_page/ErrorDivDetails.tsx index f0d912390..575c53c22 100644 --- a/javascripts/src/components/session_login_page/ErrorDivDetails.tsx +++ b/javascripts/src/components/session_login_page/ErrorDivDetails.tsx @@ -8,7 +8,7 @@ export interface ErrorDivDetailsProps { serverError?: string | null } -function AlertInnerHtmlContents({serverError}:{serverError:string}): JSX.Element { +function AlertInnerHtmlContents({serverError}:{serverError?:string|undefined|null}): JSX.Element { if (!serverError) { return <> ; } From 4d0641847c3afbd6a060aeb34bea4c916d110354 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 10 Nov 2023 21:56:46 -0600 Subject: [PATCH 022/208] Fix typing bug in date --- javascripts/src/lib/date.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/javascripts/src/lib/date.ts b/javascripts/src/lib/date.ts index 98695365b..ff8617751 100644 --- a/javascripts/src/lib/date.ts +++ b/javascripts/src/lib/date.ts @@ -7,13 +7,13 @@ function momentTz(date:string, timezone:string='UTC'):moment.Moment { } // Return a date in the format MM/DD/YY for a given date string or moment obj -export function readable_date(date?:string, timezone:string='UTC'):string { +export function readable_date(date?:string, timezone:string='UTC') { if(!date) return return momentTz(date,timezone).format("MM/DD/YYYY") } // Given a created_at string (eg. Charge.last.created_at.to_s), convert it to a readable date-time string -export function readable_date_time(date?:string, timezone:string='UTC'):string { +export function readable_date_time(date?:string, timezone:string='UTC') { if(!date) return return momentTz(date,timezone).format("MM/DD/YYYY H:mma z") } @@ -49,15 +49,15 @@ export class NonprofitTimezonedDates { } - readable_date(date?:string):string{ + readable_date(date?:string) { return readable_date(date, this.nonprofitTimezone || 'UTC') } - readable_date_time(date?:string):string { + readable_date_time(date?:string) { return readable_date_time(date, this.nonprofitTimezone || 'UTC') } - readable_date_time_to_iso(date?:string):string { + readable_date_time_to_iso(date?:string) { return readable_date_time_to_iso(date, this.nonprofitTimezone || 'UTC') } } \ No newline at end of file From 4078b9129fc9e8014e67d8ca7df968dbee07dde1 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 14 Nov 2023 15:32:57 -0600 Subject: [PATCH 023/208] Convert from GetData.chain --- app/legacy_lib/insert_charge.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/legacy_lib/insert_charge.rb b/app/legacy_lib/insert_charge.rb index 29537fbcb..cd05b9f0f 100644 --- a/app/legacy_lib/insert_charge.rb +++ b/app/legacy_lib/insert_charge.rb @@ -135,9 +135,9 @@ def self.with_stripe(data) charge.amount = data[:amount] charge.fee = fee - charge.stripe_charge_id = GetData.chain(stripe_charge, :id) + charge.stripe_charge_id = stripe_charge&.id charge.failure_message = failure_message - charge.status = GetData.chain(stripe_charge, :paid) ? 'pending' : 'failed' + charge.status = stripe_charge&.paid ? 'pending' : 'failed' charge.card = card charge.donation = Donation.where('id = ?', data[:donation_id]).first charge.supporter = supporter From c8cc909d45ca309d362f97123475318afbb30862 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 14 Nov 2023 15:53:53 -0600 Subject: [PATCH 024/208] Correct documentation in TaxMailerPreview --- spec/mailers/previews/tax_mailer_preview.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index edd95f36e..4e9d0e1be 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -1,8 +1,8 @@ # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -# Preview all emails at http://localhost:3000/rails/mailers/tax_mailer +# Preview all emails at http://localhost:5000/rails/mailers/tax_mailer class TaxMailerPreview < ActionMailer::Preview include FactoryBot::Syntax::Methods - # Preview this email at http://localhost:3000/rails/mailers/tax_mailer/annual_receipt + # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt def annual_receipt tax_id = "12-3456789" supporter = build(:supporter_generator, nonprofit: build(:fv_poverty, ein: tax_id)) From e01a7911adbcadae4f5a04697ee498824452d381 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 16 Nov 2023 11:53:57 -0600 Subject: [PATCH 025/208] Move traits for offline donations upwards --- spec/factories/payments.rb | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index eeb208f1b..ed3a06bf5 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -1,28 +1,25 @@ # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later FactoryBot.define do + trait :with_offline_payment do + offsite_payment { association :offsite_payment_base, + nonprofit: nonprofit, + supporter: supporter, + gross_amount: gross_amount, + payment: @instance + } + end - factory :payment, aliases: [:payment_base, :legacy_payment_base] do + trait :with_offline_donation do + with_offline_payment + donation { build(:donation_base, supporter: supporter, payments: [@instance])} + end + factory :payment, aliases: [:payment_base, :legacy_payment_base] do supporter {association :supporter_base} nonprofit {supporter.nonprofit} gross_amount { 333 } fee_total {0} net_amount { gross_amount + fee_total} - - trait :with_offline_payment do - offsite_payment { association :offsite_payment_base, - nonprofit: nonprofit, - supporter: supporter, - gross_amount: gross_amount, - payment: @instance - } - end - - - trait :with_offline_donation do - with_offline_payment - donation { build(:donation_base, supporter: supporter, payments: [@instance])} - end end From 7b8227a5ffe1d46c83ba8f32ceae1c2d69c33b3f Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 9 Nov 2023 18:20:59 -0600 Subject: [PATCH 026/208] Add typing to simplify various upgrades --- client/js/components/card-form.d.ts | 17 +++++++++ client/js/components/supporter-fields.d.ts | 13 +++++++ client/js/nonprofits/donate/info-step.d.ts | 36 +++++++++++++++++++ client/js/nonprofits/donate/payment-step.d.ts | 30 ++++++++++++---- 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 client/js/components/card-form.d.ts create mode 100644 client/js/components/supporter-fields.d.ts create mode 100644 client/js/nonprofits/donate/info-step.d.ts diff --git a/client/js/components/card-form.d.ts b/client/js/components/card-form.d.ts new file mode 100644 index 000000000..be4f378d0 --- /dev/null +++ b/client/js/components/card-form.d.ts @@ -0,0 +1,17 @@ +// License: LGPL-3.0-or-later +// npm +import h from 'snabbdom/h' + + +type InitInput = any; +type InitState = any; + + +export function init(state:InitInput): InitState; + +type MountInput = {elementMounted:true, element:any}| {elementMount?:false}; + +export function mount(state:MountInput):void; + +export function view(state:InitState): ReturnType< typeof h>; + diff --git a/client/js/components/supporter-fields.d.ts b/client/js/components/supporter-fields.d.ts new file mode 100644 index 000000000..d5ff575b1 --- /dev/null +++ b/client/js/components/supporter-fields.d.ts @@ -0,0 +1,13 @@ +// License: LGPL-3.0-or-later +import h from 'snabbdom/h'; + +type InitState = any; +type InputState =any; +type Params = () => any; +type OutputState = any; + +export function init(state:InitState, params$:Params):OutputState; + +type ViewState = any + +export function view(state:ViewState) : ReturnType; diff --git a/client/js/nonprofits/donate/info-step.d.ts b/client/js/nonprofits/donate/info-step.d.ts new file mode 100644 index 000000000..dc9ac774d --- /dev/null +++ b/client/js/nonprofits/donate/info-step.d.ts @@ -0,0 +1,36 @@ +import h from 'snabbdom/h'; +import {init as supporterFieldsInit} from '../../components/supporter-fields'; + +type Supporter = any; +type Donation = any; +type DedicationData = any; +type SelectedPaymentType = any; +type ParamsType = any; + +interface ParentState { + selectedPayment$:(payment?:string) => string | undefined; + donationAmount$: () => number; + params$: () => ParamsType; + currentStep$: (currentStep?:number) => number; +} + + +type SavedSupp = any; +type SavedDedicatee = any; + +interface InitState { + donation$: () => Donation + dedicationData$: () => DedicationData + dedicationForm$: () => boolean; + supporterFields: ReturnType + savedSupp$: () => SavedSupp; + savedDedicatee$: () => SavedDedicatee; + supporter$: () => Supporter; + currentStep$: (currentStep?:number) => number; + params$: () => ParamsType; +} + +export declare function init( donation$:Donation, parentState: ParentState): InitState; + + +export declare function view( state:InitState ) : ReturnType; \ No newline at end of file diff --git a/client/js/nonprofits/donate/payment-step.d.ts b/client/js/nonprofits/donate/payment-step.d.ts index 8a356fa55..47806f599 100644 --- a/client/js/nonprofits/donate/payment-step.d.ts +++ b/client/js/nonprofits/donate/payment-step.d.ts @@ -1,7 +1,7 @@ import h from 'snabbdom/h'; - - - +import DonationSubmitter from './DonationSubmitter' +import {init as cardFormInit} from '../../components/card-form'; + type Supporter = any; type Donation = any; type DedicationData = any; @@ -16,12 +16,28 @@ interface InitInput { params$: (params?:ParamsType) => ParamsType } -type InitOutput = any; +type Progress = {hidden:boolean } | {status: string}; + +type SepaForm = any; +type DonationParams = any; + +interface InitState extends InitInput { + donationTotal$: (total?:number) => number| undefined; + potentialFees$: (fees?:number) => number | undefined; + loading$: (loading?:boolean) => boolean |undefined; + error$: () => DonationSubmitter['error'] + progress$: () => Progress; + onInsert: () => void; + onRemove: () => void; + cardForm: ReturnType< typeof cardFormInit>; + sepaForm: SepaForm; + donationParams$: () => DonationParams; + paid$: () => boolean; + } -export declare function init( input: InitInput): InitOutput; +export declare function init( input: InitInput): InitState; -type ViewState = any; -export declare function view(state:ViewState) : ReturnType; \ No newline at end of file +export declare function view(state:InitState) : ReturnType; \ No newline at end of file From 11e2b1442ffe7695518b6a4ffba3d5ac6d1c2ef7 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 17 Nov 2023 13:04:16 -0600 Subject: [PATCH 027/208] Correct bug in card-form-defs --- client/js/components/card-form.d.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/js/components/card-form.d.ts b/client/js/components/card-form.d.ts index be4f378d0..97df09916 100644 --- a/client/js/components/card-form.d.ts +++ b/client/js/components/card-form.d.ts @@ -13,5 +13,10 @@ type MountInput = {elementMounted:true, element:any}| {elementMount?:false}; export function mount(state:MountInput):void; -export function view(state:InitState): ReturnType< typeof h>; +interface ViewState extends InitState { + error$:() => string| false |undefined; + hideButton: boolean +} + +export function view(state:ViewState): ReturnType< typeof h>; From 8a2c64e60994df7cd419735bd214e1b76f7b6b08 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 17 Nov 2023 13:11:38 -0600 Subject: [PATCH 028/208] Add followup step definition --- client/js/nonprofits/donate/followup-step.d.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 client/js/nonprofits/donate/followup-step.d.ts diff --git a/client/js/nonprofits/donate/followup-step.d.ts b/client/js/nonprofits/donate/followup-step.d.ts new file mode 100644 index 000000000..636f60f4d --- /dev/null +++ b/client/js/nonprofits/donate/followup-step.d.ts @@ -0,0 +1,18 @@ +// License: LGPL-3.0-or-later +import h from 'snabbdom/h' +import {init as infoStepInit} from './info-step'; + +type Params = { + offsite?: boolean | undefined; + redirect?: boolean | undefined; + modal?: boolean | undefined; +}; + +interface ViewState { + infoStep: ReturnType; + thankyou_msg?:string | undefined; + params$: () => Params + clickFinish$: () => void +} + +export function view(state: ViewState): ReturnType; From 1c271a9320570bfa0d4660f3b077f3e656fb29e2 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 17 Nov 2023 13:19:26 -0600 Subject: [PATCH 029/208] Add additional types --- client/js/nonprofits/donate/dedication-form.d.ts | 13 +++++++++++++ client/js/nonprofits/donate/info-step.d.ts | 2 +- client/js/nonprofits/donate/payment-step.d.ts | 3 ++- client/js/nonprofits/donate/types.ts | 4 +++- 4 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 client/js/nonprofits/donate/dedication-form.d.ts diff --git a/client/js/nonprofits/donate/dedication-form.d.ts b/client/js/nonprofits/donate/dedication-form.d.ts new file mode 100644 index 000000000..6ac7d2ebd --- /dev/null +++ b/client/js/nonprofits/donate/dedication-form.d.ts @@ -0,0 +1,13 @@ +// License: LGPL-3.0-or-later +import h from 'snabbdom/h'; + +import type {DedicationData} from './types'; + +interface ViewState { + dedicationData$: () => DedicationData | undefined; + submitDedication$: (form: HTMLFormElement) => void; +} + +// A contact info form for a donor to add a dedication in honor/memory of somebody +export function view(state:ViewState): ReturnType; + diff --git a/client/js/nonprofits/donate/info-step.d.ts b/client/js/nonprofits/donate/info-step.d.ts index dc9ac774d..e9a225841 100644 --- a/client/js/nonprofits/donate/info-step.d.ts +++ b/client/js/nonprofits/donate/info-step.d.ts @@ -1,9 +1,9 @@ import h from 'snabbdom/h'; import {init as supporterFieldsInit} from '../../components/supporter-fields'; +import type {DedicationData} from './types'; type Supporter = any; type Donation = any; -type DedicationData = any; type SelectedPaymentType = any; type ParamsType = any; diff --git a/client/js/nonprofits/donate/payment-step.d.ts b/client/js/nonprofits/donate/payment-step.d.ts index 47806f599..6c1d3a5a6 100644 --- a/client/js/nonprofits/donate/payment-step.d.ts +++ b/client/js/nonprofits/donate/payment-step.d.ts @@ -1,10 +1,11 @@ import h from 'snabbdom/h'; import DonationSubmitter from './DonationSubmitter' import {init as cardFormInit} from '../../components/card-form'; +import type {DedicationData} from './types'; type Supporter = any; type Donation = any; -type DedicationData = any; + type SelectedPaymentType = any; type ParamsType = any; diff --git a/client/js/nonprofits/donate/types.ts b/client/js/nonprofits/donate/types.ts index da59c22be..e2337f8bd 100644 --- a/client/js/nonprofits/donate/types.ts +++ b/client/js/nonprofits/donate/types.ts @@ -1,2 +1,4 @@ // License: LGPL-3.0-or-later -export {CustomFieldDescription} from './parseFields/customField'; \ No newline at end of file +export {CustomFieldDescription} from './parseFields/customField'; + +export type DedicationData = any; \ No newline at end of file From 8ed09d3321eda8f9884adae19760ddee6ab7d72a Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 17 Nov 2023 21:17:09 -0600 Subject: [PATCH 030/208] Add more typing --- client/js/nonprofits/donate/amount-step.d.ts | 22 ++++++++++++ .../js/nonprofits/donate/followup-step.d.ts | 3 +- client/js/nonprofits/donate/info-step.d.ts | 8 ++--- client/js/nonprofits/donate/payment-step.d.ts | 5 +-- client/js/nonprofits/donate/types.ts | 6 +++- client/js/nonprofits/donate/wizard.d.ts | 35 +++++++++++++++++++ 6 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 client/js/nonprofits/donate/amount-step.d.ts create mode 100644 client/js/nonprofits/donate/wizard.d.ts diff --git a/client/js/nonprofits/donate/amount-step.d.ts b/client/js/nonprofits/donate/amount-step.d.ts new file mode 100644 index 000000000..6cc07b1ba --- /dev/null +++ b/client/js/nonprofits/donate/amount-step.d.ts @@ -0,0 +1,22 @@ +// License: LGPL-3.0-or-later +import h from 'snabbdom/h'; +import { StandardizedParams } from './types'; + + +type InputParamsType = any; +type Donation = any; +type InputDonationDefaults = any; + + +interface InitState { + params$:()=> StandardizedParams; + evolveDonation$:(evolution?:Partial) => Partial | undefined; + buttonAmountSelected$(isSelected?:boolean): boolean|undefined; + currentStep$(step?:number): number |undefined; + } + + +export declare function init( donationDefaults: InputDonationDefaults, params$: () => InputParamsType): InitState; + + +export declare function view(state:InitState) : ReturnType; \ No newline at end of file diff --git a/client/js/nonprofits/donate/followup-step.d.ts b/client/js/nonprofits/donate/followup-step.d.ts index 636f60f4d..47a6bae0f 100644 --- a/client/js/nonprofits/donate/followup-step.d.ts +++ b/client/js/nonprofits/donate/followup-step.d.ts @@ -1,6 +1,7 @@ // License: LGPL-3.0-or-later import h from 'snabbdom/h' import {init as infoStepInit} from './info-step'; +import { StandardizedParams } from './types'; type Params = { offsite?: boolean | undefined; @@ -11,7 +12,7 @@ type Params = { interface ViewState { infoStep: ReturnType; thankyou_msg?:string | undefined; - params$: () => Params + params$: () => StandardizedParams clickFinish$: () => void } diff --git a/client/js/nonprofits/donate/info-step.d.ts b/client/js/nonprofits/donate/info-step.d.ts index e9a225841..9fb9777b9 100644 --- a/client/js/nonprofits/donate/info-step.d.ts +++ b/client/js/nonprofits/donate/info-step.d.ts @@ -1,16 +1,16 @@ import h from 'snabbdom/h'; import {init as supporterFieldsInit} from '../../components/supporter-fields'; -import type {DedicationData} from './types'; +import type {DedicationData, StandardizedParams} from './types'; type Supporter = any; type Donation = any; type SelectedPaymentType = any; -type ParamsType = any; + interface ParentState { selectedPayment$:(payment?:string) => string | undefined; donationAmount$: () => number; - params$: () => ParamsType; + params$: () => StandardizedParams; currentStep$: (currentStep?:number) => number; } @@ -27,7 +27,7 @@ interface InitState { savedDedicatee$: () => SavedDedicatee; supporter$: () => Supporter; currentStep$: (currentStep?:number) => number; - params$: () => ParamsType; + params$: () => StandardizedParams; } export declare function init( donation$:Donation, parentState: ParentState): InitState; diff --git a/client/js/nonprofits/donate/payment-step.d.ts b/client/js/nonprofits/donate/payment-step.d.ts index 6c1d3a5a6..97c18154e 100644 --- a/client/js/nonprofits/donate/payment-step.d.ts +++ b/client/js/nonprofits/donate/payment-step.d.ts @@ -1,7 +1,8 @@ +// License: LGPL-3.0-or-later import h from 'snabbdom/h'; import DonationSubmitter from './DonationSubmitter' import {init as cardFormInit} from '../../components/card-form'; -import type {DedicationData} from './types'; +import type {DedicationData, StandardizedParams} from './types'; type Supporter = any; type Donation = any; @@ -14,7 +15,7 @@ interface InitInput { donation$: (donation?:Donation) => Donation; dedicationData$: (dedication?:DedicationData) => DedicationData; activePaymentTab$: (selectedPayment?:SelectedPaymentType) => SelectedPaymentType; - params$: (params?:ParamsType) => ParamsType + params$: () => StandardizedParams } type Progress = {hidden:boolean } | {status: string}; diff --git a/client/js/nonprofits/donate/types.ts b/client/js/nonprofits/donate/types.ts index e2337f8bd..542bc6ede 100644 --- a/client/js/nonprofits/donate/types.ts +++ b/client/js/nonprofits/donate/types.ts @@ -1,4 +1,8 @@ // License: LGPL-3.0-or-later export {CustomFieldDescription} from './parseFields/customField'; +import getParams from './get-params'; -export type DedicationData = any; \ No newline at end of file + +export type DedicationData = any; + +export type StandardizedParams = ReturnType \ No newline at end of file diff --git a/client/js/nonprofits/donate/wizard.d.ts b/client/js/nonprofits/donate/wizard.d.ts new file mode 100644 index 000000000..e96383adb --- /dev/null +++ b/client/js/nonprofits/donate/wizard.d.ts @@ -0,0 +1,35 @@ +// License: LGPL-3.0-or-later +import h from 'snabbdom/h'; +import {init as amountStepInit} from './amount-step' +import {init as infoStepInit} from './info-step'; +import {init as paymentStepInit} from './payment-step'; +import {init as wizardInit} from 'ff-core/wizard'; +import { StandardizedParams } from './types'; + + +type InputParamsType = any; +type Donation = any; + + +interface InitState { + params$:()=> StandardizedParams; + error$:(error?:string) => string|undefined + loader$:(loading?:boolean) => boolean|undefined + clickLogout$:() => void; + clickFinish$:() => void; + hide_cover_fees_option: boolean | undefined ; + hide_anonymous: boolean| undefined; + selected_payment$: (payment:string) => string; + amountStep: ReturnType + donationAmount$: () => number|undefined; + infoStep: ReturnType; + donation$: () => Donation + paymentStep: ReturnType + wizard: ReturnType; + } + + +export declare function init( params$: () => InputParamsType): InitState; + + +export declare function view(state:InitState) : ReturnType; \ No newline at end of file From 4839578d3e9fb702c72a13d437c1dfab2c4a9f9a Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 17 Nov 2023 21:43:33 -0600 Subject: [PATCH 031/208] Finalize StandardizedParameters --- client/js/nonprofits/donate/get-params.d.ts | 3 ++- client/js/nonprofits/donate/types.ts | 29 +++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/client/js/nonprofits/donate/get-params.d.ts b/client/js/nonprofits/donate/get-params.d.ts index e3700d394..125d3c264 100644 --- a/client/js/nonprofits/donate/get-params.d.ts +++ b/client/js/nonprofits/donate/get-params.d.ts @@ -1,5 +1,6 @@ // License: LGPL-3.0-or-later import { AmountButtonInput } from "./amt"; +import { StandardizedParams } from "./types"; type GetParamsInputBase = {[prop:string]: any}; @@ -8,7 +9,7 @@ export type GetParamsOutput = TInput & { tags?:string[], } -declare const getParams: (input:GetParamsInput) => GetParamsOutput; +declare const getParams: (input:GetParamsInput) => StandardizedParams; export default getParams; \ No newline at end of file diff --git a/client/js/nonprofits/donate/types.ts b/client/js/nonprofits/donate/types.ts index 542bc6ede..a135654eb 100644 --- a/client/js/nonprofits/donate/types.ts +++ b/client/js/nonprofits/donate/types.ts @@ -1,8 +1,33 @@ // License: LGPL-3.0-or-later -export {CustomFieldDescription} from './parseFields/customField'; +import {CustomFieldDescription} from './parseFields/customField'; +import { AmountButtonInput } from './amt'; import getParams from './get-params'; export type DedicationData = any; -export type StandardizedParams = ReturnType \ No newline at end of file +export interface StandardizedParams { + campaign_id?:number; + custom_amounts?:AmountButtonInput[]; + custom_fields?: CustomFieldDescription[]; + designation?:string; + designation_desc?:string; + designation_prompt?:string; + embedded?:boolean + gift_option?:{name:string, id?:number}; + gift_option_id?:number + gift_option_name?:string; + hide_anonymous?:boolean; + hide_dedication?:boolean; + manual_cover_fees?:boolean; + hide_cover_fees_option?:boolean; + mode?:string; + modal?:boolean; + multiple_designations?:string[]; + offsite?:boolean; + redirect?:string; + single_amount?:number; + type?: 'recurring'|'one-time'; + tags?:string[]; + weekly?:boolean; +} \ No newline at end of file From e0570bdaf26eb3d183043154275d9983eae76c9e Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 17 Nov 2023 21:46:38 -0600 Subject: [PATCH 032/208] Remove an import --- client/js/nonprofits/donate/page.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/js/nonprofits/donate/page.js b/client/js/nonprofits/donate/page.js index e7c235c2f..49b0ca6d3 100644 --- a/client/js/nonprofits/donate/page.js +++ b/client/js/nonprofits/donate/page.js @@ -5,7 +5,6 @@ const render = require('ff-core/render') const donate = require('./wizard') const snabbdom = require('snabbdom') const flyd = require('flyd') -const R = require('ramda') const url = require('url') const request = require('../../common/request') From 4edd4d4f86ed51e75232483acbedc495919730dc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 20 Nov 2023 12:45:47 -0600 Subject: [PATCH 033/208] Update types --- client/js/nonprofits/donate/types.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/js/nonprofits/donate/types.ts b/client/js/nonprofits/donate/types.ts index a135654eb..57d566547 100644 --- a/client/js/nonprofits/donate/types.ts +++ b/client/js/nonprofits/donate/types.ts @@ -1,8 +1,7 @@ // License: LGPL-3.0-or-later -import {CustomFieldDescription} from './parseFields/customField'; +export { CustomFieldDescription } from './parseFields/customField'; +import { CustomFieldDescription } from './parseFields/customField'; import { AmountButtonInput } from './amt'; -import getParams from './get-params'; - export type DedicationData = any; @@ -30,4 +29,5 @@ export interface StandardizedParams { type?: 'recurring'|'one-time'; tags?:string[]; weekly?:boolean; -} \ No newline at end of file +} + From b80cafac3a33f0e84718aba8b03738f70507f1fe Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 16 Nov 2023 19:17:46 -0600 Subject: [PATCH 034/208] WIP on from_donation? --- app/models/dispute_transaction.rb | 4 ++++ app/models/payment.rb | 13 +++++++++++++ app/models/refund.rb | 5 +++++ 3 files changed, 22 insertions(+) diff --git a/app/models/dispute_transaction.rb b/app/models/dispute_transaction.rb index d55aa0773..9ba7e56e1 100644 --- a/app/models/dispute_transaction.rb +++ b/app/models/dispute_transaction.rb @@ -18,6 +18,10 @@ def fee_total=(fee_total) calculate_net end + def from_donation? + !!dispute&.original_payment&.donation + end + private def calculate_net self.net_amount = gross_amount + fee_total diff --git a/app/models/payment.rb b/app/models/payment.rb index 0efad06b0..54138ff32 100644 --- a/app/models/payment.rb +++ b/app/models/payment.rb @@ -56,6 +56,19 @@ def build(attributes=nil, options={}, &block) end + def from_donation? + if kind == 'Refund' + !!refund&.from_donation? + elsif kind == 'Dispute' || kind == 'DisputeReversal' + !!dispute_transaction&.from_donation? + elsif kind == 'OffsitePayment' + !!donation.present? + else + kind == 'Donation' || kind == 'RecurringDonation' + end + end + + def staff_comment (manual_balance_adjustment&.staff_comment&.present? && manual_balance_adjustment&.staff_comment) || nil end diff --git a/app/models/refund.rb b/app/models/refund.rb index 2a779b4e1..120164627 100644 --- a/app/models/refund.rb +++ b/app/models/refund.rb @@ -22,11 +22,16 @@ class Refund < ApplicationRecord has_one :misc_refund_info has_one :nonprofit, through: :charge has_one :supporter, through: :charge + has_one :original_payment, through: :charge, source: :payment scope :not_disbursed, ->{where(disbursed: [nil, false])} scope :disbursed, ->{where(disbursed: [true])} has_many :manual_balance_adjustments, as: :entity + def from_donation? + !!original_payment&.donation + end + end From 095bb2eb873ecec0b634f9944574f7ee7ace05dd Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 21 Nov 2023 11:20:36 -0600 Subject: [PATCH 035/208] Remove unused legacy refund --- app/legacy_lib/insert_refunds.rb | 64 +------------------------------- 1 file changed, 1 insertion(+), 63 deletions(-) diff --git a/app/legacy_lib/insert_refunds.rb b/app/legacy_lib/insert_refunds.rb index a1a1dcd46..2901f7252 100644 --- a/app/legacy_lib/insert_refunds.rb +++ b/app/legacy_lib/insert_refunds.rb @@ -5,11 +5,7 @@ module InsertRefunds # Refund a given charge, up to its net amount # params: amount, donation obj def self.with_stripe(charge, h) - # if Time.now < FEE_SWITCHOVER_TIME - # legacy_refund(charge, h) - # else - modern_refund(charge, h) - # end + modern_refund(charge, h) end @@ -78,64 +74,6 @@ def self.modern_refund(charge,h) end end - def self.legacy_refund(charge, h) - ParamValidation.new(charge, { - payment_id: {required: true, is_integer: true}, - stripe_charge_id: {required: true, format: /^(test_)?ch_.*$/}, - amount: {required: true, is_integer: true, min: 1}, - id: {required: true, is_integer: true}, - nonprofit_id: {required: true, is_integer: true}, - supporter_id: {required: true, is_integer: true} - }) - ParamValidation.new(h, { amount: {required: true, is_integer: true, min: 1} }) - - original_payment = Qx.select("*").from("payments").where(id: charge['payment_id']).execute.first - raise ActiveRecord::RecordNotFound.new("Cannot find original payment for refund on charge #{charge['id']}") if original_payment.nil? - - if original_payment['refund_total'].to_i + h['amount'].to_i > original_payment['gross_amount'].to_i - raise RuntimeError.new("Refund amount must be less than the net amount of the payment (for charge #{charge['id']})") - end - - stripe_charge = Stripe::Charge.retrieve(charge['stripe_charge_id']) - - refund_post_data = {'amount' => h['amount'], 'refund_application_fee' => true, 'reverse_transfer' => true} - refund_post_data['reason'] = h['reason'] unless h['reason'].blank? # Stripe will error on blank reason field - stripe_refund = stripe_charge.refunds.create(refund_post_data) - h['stripe_refund_id'] = stripe_refund.id - - refund_row = Qx.insert_into(:refunds).values(h.merge(charge_id: charge['id'])).timestamps.returning('*').execute.first - - gross = -(h['amount']) - - fees = (h['amount'] * -original_payment['fee_total'] / original_payment['gross_amount']).ceil - net = gross + fees - # Create a corresponding negative payment record - payment_row = Qx.insert_into(:payments).values({ - gross_amount: gross, - fee_total: fees, - net_amount: net, - kind: 'Refund', - towards: original_payment['towards'], - date: refund_row['created_at'], - nonprofit_id: charge['nonprofit_id'], - supporter_id: charge['supporter_id'] - }) - .timestamps - .returning('*') - .execute.first - - InsertActivities.for_refunds([payment_row['id']]) - - # Update the refund to have the above payment_id - refund_row = Qx.update(:refunds).set(payment_id: payment_row['id']).ts.where(id: refund_row['id']).returning('*').execute.first - # Update original payment to increment its refund_total for any future refund attempts - Qx.update(:payments).set("refund_total=refund_total + #{h['amount'].to_i}").ts.where(id: original_payment['id']).execute - # Send the refund receipts in a delayed job - Delayed::Job.enqueue JobTypes::DonorRefundNotificationJob.new(refund_row['id']) - Delayed::Job.enqueue JobTypes::NonprofitRefundNotificationJob.new(refund_row['id']) - return {'payment' => payment_row, 'refund' => refund_row} - end - # @param [Hash] opts # @option opts [Hash] :refund_data the data to pass to the Stripe::Refund#create method # @option opts [Integer] :nonprofit_id the nonprofit_id that the charge belongs to From b11e67921c901a0aeabcba62d61300d395d02311 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 21 Nov 2023 16:36:04 -0600 Subject: [PATCH 036/208] Add tests for Refund#from_donation? --- app/models/refund.rb | 6 +++++- spec/factories/refunds.rb | 9 ++++++++- spec/models/refund_spec.rb | 10 ++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/app/models/refund.rb b/app/models/refund.rb index 120164627..017667508 100644 --- a/app/models/refund.rb +++ b/app/models/refund.rb @@ -22,13 +22,17 @@ class Refund < ApplicationRecord has_one :misc_refund_info has_one :nonprofit, through: :charge has_one :supporter, through: :charge - has_one :original_payment, through: :charge, source: :payment scope :not_disbursed, ->{where(disbursed: [nil, false])} scope :disbursed, ->{where(disbursed: [true])} has_many :manual_balance_adjustments, as: :entity + + def original_payment + charge&.payment + end + def from_donation? !!original_payment&.donation end diff --git a/spec/factories/refunds.rb b/spec/factories/refunds.rb index de5823ffa..70184f7a1 100644 --- a/spec/factories/refunds.rb +++ b/spec/factories/refunds.rb @@ -1,6 +1,13 @@ # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later FactoryBot.define do factory :refund do - + trait :from_donation do + charge { build(:charge_base, payment: build(:payment, donation: build(:donation)))} + end + + + trait :not_from_donation do + charge { build(:charge_base, payment: build(:payment))} + end end end diff --git a/spec/models/refund_spec.rb b/spec/models/refund_spec.rb index 618b8f457..f6de0b82a 100644 --- a/spec/models/refund_spec.rb +++ b/spec/models/refund_spec.rb @@ -10,4 +10,14 @@ it { is_expected.to have_one(:nonprofit).through(:charge) } it { is_expected.to have_one(:supporter).through(:charge) } it {is_expected.to have_many( :manual_balance_adjustments)} + + describe "#from_donation?" do + it 'is true when refund is associated with a donation' do + expect(build(:refund, :from_donation).from_donation?).to eq true + end + + it 'is true when refund is not associated with a donation' do + expect(build(:refund, :not_from_donation).from_donation?).to eq false + end + end end From 634f880f22c9a7d1069bbfce07f1b1ca334a8808 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 21 Nov 2023 18:24:54 -0600 Subject: [PATCH 037/208] Test DisputeTransaction#from_donation? --- app/models/dispute.rb | 4 ++++ app/models/dispute_transaction.rb | 2 +- spec/factories/dispute_transactions.rb | 8 ++++++++ spec/models/dispute_transaction_spec.rb | 10 ++++++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/app/models/dispute.rb b/app/models/dispute.rb index b6e6e2ee9..91230da11 100644 --- a/app/models/dispute.rb +++ b/app/models/dispute.rb @@ -45,6 +45,10 @@ def reinstatement_transaction ((dispute_transactions&.count == 2) && dispute_transactions[1]) || nil end + def get_original_payment + charge&.payment + end + def build_activity_json(event_type) dispute = self original_payment = dispute.original_payment diff --git a/app/models/dispute_transaction.rb b/app/models/dispute_transaction.rb index 9ba7e56e1..627f8c451 100644 --- a/app/models/dispute_transaction.rb +++ b/app/models/dispute_transaction.rb @@ -19,7 +19,7 @@ def fee_total=(fee_total) end def from_donation? - !!dispute&.original_payment&.donation + !!dispute&.get_original_payment&.donation end private diff --git a/spec/factories/dispute_transactions.rb b/spec/factories/dispute_transactions.rb index 3aa315fcf..57c80e1b1 100644 --- a/spec/factories/dispute_transactions.rb +++ b/spec/factories/dispute_transactions.rb @@ -1,5 +1,13 @@ FactoryBot.define do factory :dispute_transaction do disbursed {false} + + trait :from_donation do + dispute{ build(:dispute, charge: build(:charge_base, payment: build(:payment, donation: build(:donation))))} + end + + trait :not_from_donation do + dispute{ build(:dispute, charge: build(:charge_base, payment: build(:payment)))} + end end end diff --git a/spec/models/dispute_transaction_spec.rb b/spec/models/dispute_transaction_spec.rb index b26fd9dc6..1fe7e169b 100644 --- a/spec/models/dispute_transaction_spec.rb +++ b/spec/models/dispute_transaction_spec.rb @@ -8,4 +8,14 @@ it { is_expected.to have_one(:nonprofit).through(:dispute) } it { is_expected.to have_one(:supporter).through(:dispute) } it {is_expected.to have_many( :manual_balance_adjustments)} + + describe "#from_donation?" do + it 'is true when dispute_transaction is associated with a donation' do + expect(build(:dispute_transaction, :from_donation).from_donation?).to eq true + end + + it 'is true when dispute_transaction is not associated with a donation' do + expect(build(:dispute_transaction, :not_from_donation).from_donation?).to eq false + end + end end From 6faf3608aeb82d9566e20fedc91a627b55ecf89f Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 21 Nov 2023 18:37:20 -0600 Subject: [PATCH 038/208] Add specs on Payment#from_donation? --- spec/models/payment_spec.rb | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/spec/models/payment_spec.rb b/spec/models/payment_spec.rb index ee697b224..8ea26bdf0 100644 --- a/spec/models/payment_spec.rb +++ b/spec/models/payment_spec.rb @@ -168,4 +168,65 @@ expect(create(:fv_poverty_payment, :anonymous_through_supporter, :anonymous_through_donation)).to be_consider_anonymous end end + + describe '#from_donation?' do + + context "for kind == 'Refund" do + it 'is true when refund came from donation' do + expect(build(:payment, kind: 'Refund', refund: build(:refund, :from_donation)).from_donation?).to eq true + end + + it 'is false when refund didnt come from donation' do + expect(build(:payment, kind: 'Refund', refund: build(:refund, :not_from_donation)).from_donation?).to eq false + end + end + + context "for kind == 'Dispute" do + it 'is true when dispute came from donation' do + expect(build(:payment, kind: 'Dispute', dispute_transaction: build(:dispute_transaction, :from_donation)).from_donation?).to eq true + end + + it 'is false when dispute didnt come from donation' do + expect(build(:payment, kind: 'Dispute', dispute_transaction: build(:dispute_transaction, :not_from_donation)).from_donation?).to eq false + end + end + + context "for kind == 'DisputeReversal" do + it 'is true when dispute_reversal came from donation' do + expect(build(:payment, kind: 'DisputeReversal', dispute_transaction: build(:dispute_transaction, :from_donation)).from_donation?).to eq true + end + + it 'is false when dispute didnt come from donation' do + expect(build(:payment, kind: 'DisputeReversal', dispute_transaction: build(:dispute_transaction, :not_from_donation)).from_donation?).to eq false + end + end + + context "for kind == 'OffsitePayment" do + it 'is true when donation is set' do + expect(build(:payment, kind: 'OffsitePayment', donation: build(:donation)).from_donation?).to eq true + end + + it 'is false when donation is not set' do + expect(build(:payment, kind: 'OffsitePayment').from_donation?).to eq false + end + end + + context "for kind == 'Donation" do + it 'is true' do + expect(build(:payment, kind: 'Donation').from_donation?).to eq true + end + end + + context "for kind == 'RecurringDonation" do + it 'is true' do + expect(build(:payment, kind: 'RecurringDonation').from_donation?).to eq true + end + end + + context "for kind == 'FakeKind'" do + it 'is false' do + expect(build(:payment, kind: 'FakeKind').from_donation?).to eq false + end + end + end end \ No newline at end of file From ed315a9ffdf7a1f5214eae7fde97088ae5ff2a12 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 28 Nov 2023 19:01:19 -0600 Subject: [PATCH 039/208] Correct build ref --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a3f567809..f4b8e7821 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,7 +6,7 @@ on: push: branches: ["supporter_level_goal"] concurrency: - group: build--${{ github.head_ref }} + group: build--${{ github.head_ref || github.ref }} cancel-in-progress: true jobs: build: From 2366bac169d29cf5f06401699b6cc41a830f84b1 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 14 Nov 2023 15:31:14 -0600 Subject: [PATCH 040/208] Support for splitting payments into donations, refunds and disputes, and dispute_reversal_payments --- app/mailers/tax_mailer.rb | 8 ++++-- app/models/supporter.rb | 16 ++++++++++++ app/views/tax_mailer/annual_receipt.html.erb | 27 ++++++++++++++------ spec/factories/payments.rb | 2 +- spec/mailers/previews/tax_mailer_preview.rb | 8 ++++-- 5 files changed, 48 insertions(+), 13 deletions(-) diff --git a/app/mailers/tax_mailer.rb b/app/mailers/tax_mailer.rb index 1e8210ff3..61e05989a 100644 --- a/app/mailers/tax_mailer.rb +++ b/app/mailers/tax_mailer.rb @@ -6,11 +6,15 @@ class TaxMailer < ApplicationMailer # # en.tax_mailer.annual_receipt.subject # - def annual_receipt(supporter:, payments:, year:, nonprofit_text:) + def annual_receipt(supporter:, year:, nonprofit_text:, donation_payments: [], refund_payments:[], dispute_payments: [], dispute_reversal_payments: []) @supporter = supporter @nonprofit = supporter.nonprofit - @payments = payments @year = year + + @donation_payments = donation_payments + @refund_payments = refund_payments + @dispute_payments = dispute_payments + @dispute_reversal_payments = dispute_reversal_payments @tax_id = supporter.nonprofit.ein @nonprofit_text = nonprofit_text diff --git a/app/models/supporter.rb b/app/models/supporter.rb index fc07861f9..96272e67d 100644 --- a/app/models/supporter.rb +++ b/app/models/supporter.rb @@ -57,6 +57,22 @@ def during_np_year(year) where('date >= ? and date < ?', Time.zone.local(year), Time.zone.local(year + 1)) end end + + def donation_payments + where('kind IN (?)', ['Donation', 'RecurringDonation']) + end + + def refund_payments + where('kind IN (?)', ['Refund']) + end + + def dispute_payments + where('kind IN (?)', ['Dispute']) + end + + def dispute_reversal_payments + where('kind IN (?)', ['DisputeReversed']) + end end has_many :offsite_payments diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index 59eed46ab..2070e9f0a 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -18,7 +18,7 @@ Total Amount - $<%= Format::Currency.cents_to_dollars(@payments.sum(&:gross_amount)) %> + $ TODO Organization @@ -28,14 +28,25 @@ Tax ID Number <%= @tax_id %> -<% @payments.each do |payment| %> - - - $<%= Format::Currency.cents_to_dollars(payment.gross_amount) %> - <%= payment.date %> - -<% end%> + +<% @donation_payments.each do |payment| %> + +<% end %> + +<% @refund_payments.each do |payment| %> + +<% end %> + + +<% @dispute_payments.each do |payment| %> + +<% end %> + + +<% @dispute_reversal_payments.each do |payment| %> + +<% end %> diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index ed3a06bf5..6a1aeabab 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -28,7 +28,7 @@ amount { 100 + Random.rand(5000)} end - donation { association :donation, amount: amount} + donation { association :donation, amount: amount, supporter: supporter, nonprofit: nonprofit, created_at: date} gross_amount { amount} supporter date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 4e9d0e1be..64d142a8e 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -7,9 +7,13 @@ def annual_receipt tax_id = "12-3456789" supporter = build(:supporter_generator, nonprofit: build(:fv_poverty, ein: tax_id)) tax_year = 2023 - payments = build_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter) + payments = build_list(:donation_payment_generator, Random.rand(5) + 1, + supporter: supporter, + nonprofit: supporter.nonprofit + ) + nonprofit_text = "

#{Faker::Lorem.paragraph(sentence_count: 5)}

" + "

#{Faker::Lorem.paragraph(sentence_count:3)}

" - TaxMailer.annual_receipt(year: tax_year, supporter: supporter, payments:payments, nonprofit_text: nonprofit_text) + TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments) end end From ac23d298116c212141aa747eb7f19a64d8d82df5 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 29 Nov 2023 14:27:56 -0600 Subject: [PATCH 041/208] Get payments related to donations --- app/models/payment.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/models/payment.rb b/app/models/payment.rb index 54138ff32..ecc74d271 100644 --- a/app/models/payment.rb +++ b/app/models/payment.rb @@ -56,6 +56,14 @@ def build(attributes=nil, options={}, &block) end + def self.find_each_related_to_a_donation + find_each.map(&:from_donation?) + end + + def self.each_related_to_a_donation + each.map(&:from_donation?) + end + def from_donation? if kind == 'Refund' !!refund&.from_donation? From 95d320ac3bcb00ce14f42aeadc1341d32173523e Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 14 Nov 2023 15:31:14 -0600 Subject: [PATCH 042/208] Add payment tables to mailer --- .../_dispute_payment_table.html.erb | 26 ++++++ .../_dispute_reversal_payment_table.html.erb | 32 +++++++ .../_donation_payment_table.html.erb | 91 +++++++++++++++++++ .../tax_mailer/_refund_payment_table.html.erb | 26 ++++++ app/views/tax_mailer/annual_receipt.html.erb | 8 +- 5 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 app/views/tax_mailer/_dispute_payment_table.html.erb create mode 100644 app/views/tax_mailer/_dispute_reversal_payment_table.html.erb create mode 100644 app/views/tax_mailer/_donation_payment_table.html.erb create mode 100644 app/views/tax_mailer/_refund_payment_table.html.erb diff --git a/app/views/tax_mailer/_dispute_payment_table.html.erb b/app/views/tax_mailer/_dispute_payment_table.html.erb new file mode 100644 index 000000000..18daeea26 --- /dev/null +++ b/app/views/tax_mailer/_dispute_payment_table.html.erb @@ -0,0 +1,26 @@ +<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> + + + + + + + + + + + + + + + + + + + + + + + + +
Disputed Amount <%= print_currency(payment.gross_amount, payment.nonprofit.currency_symbol) %>
Dispute Date<%= date_and_time(payment.date, payment.nonprofit.timezone) %>
Dispute Payment ID<%= payment.id %>
Information about original payment?TODO
diff --git a/app/views/tax_mailer/_dispute_reversal_payment_table.html.erb b/app/views/tax_mailer/_dispute_reversal_payment_table.html.erb new file mode 100644 index 000000000..39436aa09 --- /dev/null +++ b/app/views/tax_mailer/_dispute_reversal_payment_table.html.erb @@ -0,0 +1,32 @@ +<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Dispute Reversal Amount <%= print_currency(payment.gross_amount, payment.nonprofit.currency_symbol) %>
Dispute Reversal Date<%= date_and_time(payment.date, payment.nonprofit.timezone) %>
Dispute Reversal Payment ID<%= payment.id %>
Information about original dispute?TODO
Information about original payment?TODO
diff --git a/app/views/tax_mailer/_donation_payment_table.html.erb b/app/views/tax_mailer/_donation_payment_table.html.erb new file mode 100644 index 000000000..dec5c8a85 --- /dev/null +++ b/app/views/tax_mailer/_donation_payment_table.html.erb @@ -0,0 +1,91 @@ +<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> + + + + + + <% if payment&.consider_anonymous? %> + + <% else %> + + <% end %> + + + + + + + + + + + <% if payment.donation.campaign %> + <% campaign = payment.donation.campaign %> + + + + + <% end %> + + <% if payment.donation.event %> + <% event = payment.donation.event %> + + + + + <% end %> + + <% if payment.donation.recurring_donation %> + <% recurring_donation = payment.donation.recurring_donation %> + + + + + + + + + <% end %> + + + + + + + <% unless payment.nonprofit.ein.blank? %> + + + + + <% end %> + + <% if payment.donation.comment %> + + + + + <% end %> + + <% if payment.donation.designation %> + + + + + <% end %> + + <% if payment.donation.dedication %> + + + + + + <% end %> + + + + + + + + +
<%= t('mailer.donations.donor_name') %>Anonymous<%= payment.supporter.name %>
<%= t('donation.amount') %> <%= print_currency(payment.gross_amount, payment.nonprofit.currency_symbol) %>
<%= t('donation.date') %><%= date_and_time(payment.date, payment.nonprofit.timezone) %>
<%= t('donation.campaign') %><%= campaign.name %>(Campaign Id: <%= campaign.id%><% if @show_campaign_creator %>, Creator: <%= campaign.profile.user.email %><% end %>)
Event <%= event.name %>
<%= t('donation.recurring_interval') %> Every <%= recurring_donation.interval %> <%= recurring_donation.time_unit %>
<%= t('donation.recurring_since') %> <%= simple_date recurring_donation.created_at %>
<%= t('organization.name') %> <%= payment.nonprofit.name %>
Tax ID Number <%= payment.nonprofit.ein %>
<%= t('donation.comment') %> <%= payment.donation.comment || 'None' %>
Designation <%= payment.donation.designation || 'None' %>
Dedication <%= payment.donation.dedication ? Format::Dedication.from_json(payment.donation.dedication) : 'None' %>
<%= t('donation.payment_id') %> <%= payment.id %>
diff --git a/app/views/tax_mailer/_refund_payment_table.html.erb b/app/views/tax_mailer/_refund_payment_table.html.erb new file mode 100644 index 000000000..2f0acf56f --- /dev/null +++ b/app/views/tax_mailer/_refund_payment_table.html.erb @@ -0,0 +1,26 @@ +<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> + + + + + + + + + + + + + + + + + + + + + + + + +
Refund Amount <%= print_currency(payment.gross_amount, payment.nonprofit.currency_symbol) %>
Refund Date<%= date_and_time(payment.date, payment.nonprofit.timezone) %>
Refund Payment ID<%= payment.id %>
Information about original payment?TODO
diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index 2070e9f0a..d102f5432 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -34,19 +34,19 @@ <% @donation_payments.each do |payment| %> - + <%= render "tax_mailer/donation_payment_table", payment: payment %> <% end %> <% @refund_payments.each do |payment| %> - + <%= render "tax_mailer/refund_payment_table", payment: payment %> <% end %> <% @dispute_payments.each do |payment| %> - + <%= render "tax_mailer/dispute_payment_table", payment: payment %> <% end %> <% @dispute_reversal_payments.each do |payment| %> - + <%= render "tax_mailer/dispute_reversal_payment_table", payment: payment %> <% end %> From cd9fbb29a63259f40eb7164189bb0c8f21ee0cc1 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 30 Nov 2023 16:42:04 -0600 Subject: [PATCH 043/208] Find dupes from the Nonprofit#supporters#dupes_on* --- app/models/nonprofit.rb | 38 ++++++++++++++++++++++++++++++++++- spec/models/nonprofit_spec.rb | 35 ++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+), 1 deletion(-) diff --git a/app/models/nonprofit.rb b/app/models/nonprofit.rb index aba65ad5e..f71dec98e 100755 --- a/app/models/nonprofit.rb +++ b/app/models/nonprofit.rb @@ -74,7 +74,43 @@ def during_np_year(year) end end has_many :transactions, through: :supporters - has_many :supporters, dependent: :destroy + has_many :supporters, dependent: :destroy do + def dupes_on_email(strict_mode = true) + QuerySupporters.dupes_on_email(proxy_association.owner.id, strict_mode) + end + + def dupes_on_name(strict_mode = true) + QuerySupporters.dupes_on_name(proxy_association.owner.id, strict_mode) + end + + def dupes_on_name_and_email(strict_mode = true) + QuerySupporters.dupes_on_name_and_email(proxy_association.owner.id, strict_mode) + end + + def dupes_on_name_and_phone(strict_mode = true) + QuerySupporters.dupes_on_name_and_phone(proxy_association.owner.id, strict_mode) + end + + def dupes_on_name_and_phone_and_address(strict_mode = true) + QuerySupporters.dupes_on_name_and_phone_and_address(proxy_association.owner.id, strict_mode) + end + + def dupes_on_phone_and_email_and_address(strict_mode = true) + QuerySupporters.dupes_on_phone_and_email_and_address(proxy_association.owner.id, strict_mode) + end + + def dupes_on_name_and_address(strict_mode = true) + QuerySupporters.dupes_on_name_and_address(proxy_association.owner.id, strict_mode) + end + + def dupes_on_phone_and_email(strict_mode = true) + QuerySupporters.dupes_on_phone_and_email(proxy_association.owner.id, strict_mode) + end + + def dupes_on_address_without_zip_code(strict_mode = true) + QuerySupporters.dupes_on_address_without_zip_code(proxy_association.owner.id, strict_mode) + end + end has_many :supporter_notes, through: :supporters has_many :profiles, through: :donations has_many :campaigns, dependent: :destroy diff --git a/spec/models/nonprofit_spec.rb b/spec/models/nonprofit_spec.rb index 7f2ae363b..caf3c0d58 100644 --- a/spec/models/nonprofit_spec.rb +++ b/spec/models/nonprofit_spec.rb @@ -712,4 +712,39 @@ expect(nonprofit.supporters_who_have_payments_during_year(Time.new.utc.year)).to contain_exactly(supporter) end end + + describe "#supporters" do + [ + :email, + :name, + :name_and_email, + :name_and_phone, + :name_and_phone_and_address, + :phone_and_email_and_address, + :name_and_address, + :phone_and_email, + :address_without_zip_code + ].each do |type| + method_name = "dupes_on_#{type.to_s}" + let(:nonprofit) {build(:nonprofit, id: 1)} + + describe "##{method_name}" do + it 'is calls with strict_mode default of true' do + expect(QuerySupporters).to receive(method_name.to_sym).with(1, true) + nonprofit.supporters.send(method_name.to_sym) + end + + it 'is calls with strict_mode passed of true' do + expect(QuerySupporters).to receive(method_name.to_sym).with(1, true) + nonprofit.supporters.send(method_name.to_sym, true) + end + + it 'is calls with strict_mode passed of false' do + expect(QuerySupporters).to receive(method_name.to_sym).with(1, false) + nonprofit.supporters.send(method_name.to_sym, false) + end + end + end + + end end From abf9126346edfb445af1c4c266163593c95a51f0 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 30 Nov 2023 17:02:41 -0600 Subject: [PATCH 044/208] Reorganize the payments#during_np_year specs --- spec/models/nonprofit_spec.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/spec/models/nonprofit_spec.rb b/spec/models/nonprofit_spec.rb index caf3c0d58..727f787d9 100644 --- a/spec/models/nonprofit_spec.rb +++ b/spec/models/nonprofit_spec.rb @@ -669,7 +669,8 @@ end end - describe "#payments#during_np_year" do + describe "#payments" do + let(:nonprofit) { create(:nonprofit_base)} let(:supporter) { create(:supporter_base, nonprofit:nonprofit)} let(:payment1) { create(:payment_base, :with_offline_payment, supporter: supporter, nonprofit: nonprofit, date: Time.new.utc.beginning_of_year + 1.second)} @@ -682,14 +683,16 @@ payment3 end - it "has two payments when nonprofit has UTC time zone" do - expect(nonprofit.payments.during_np_year(Time.new.utc.year)).to contain_exactly(payment1, payment2) - end + describe "#during_np_year" do + it "has two payments when nonprofit has UTC time zone" do + expect(nonprofit.payments.during_np_year(Time.new.utc.year)).to contain_exactly(payment1, payment2) + end - it "has 2 payments when nonprofit has Central time zone" do - nonprofit.timezone = "America/Chicago" - nonprofit.save! - expect(nonprofit.payments.during_np_year(Time.new.utc.year)).to contain_exactly(payment2, payment3) + it "has 2 payments when nonprofit has Central time zone" do + nonprofit.timezone = "America/Chicago" + nonprofit.save! + expect(nonprofit.payments.during_np_year(Time.new.utc.year)).to contain_exactly(payment2, payment3) + end end end From c9aa5213d4651ced1c29b0e23d9c3ce9133b3ccc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 30 Nov 2023 17:08:18 -0600 Subject: [PATCH 045/208] Add Nonprofit#payments.prior_to_np_year --- app/models/nonprofit.rb | 11 +++++++++++ spec/models/nonprofit_spec.rb | 12 ++++++++++++ 2 files changed, 23 insertions(+) diff --git a/app/models/nonprofit.rb b/app/models/nonprofit.rb index f71dec98e..a918d6ac8 100755 --- a/app/models/nonprofit.rb +++ b/app/models/nonprofit.rb @@ -72,6 +72,12 @@ def during_np_year(year) where('date >= ? and date < ?', Time.zone.local(year), Time.zone.local(year + 1)) end end + + def prior_to_np_year(year) + proxy_association.owner.use_zone do + where('date < ?', Time.zone.local(year)) + end + end end has_many :transactions, through: :supporters has_many :supporters, dependent: :destroy do @@ -505,6 +511,11 @@ def supporters_who_have_payments_during_year(year, tickets:false) end payments_during_year.group("supporter_id").select('supporter_id, COUNT(id)').each.map(&:supporter) end + + def supporters_who_have_payments_prior_to_year(year, tickets: false) + payments_during_year = self.payments.during_np_year(year) + payments_during_year.group("supporter_id").select('supporter_id, COUNT(id)').each.map(&:supporter) + end end private diff --git a/spec/models/nonprofit_spec.rb b/spec/models/nonprofit_spec.rb index 727f787d9..e59796fca 100644 --- a/spec/models/nonprofit_spec.rb +++ b/spec/models/nonprofit_spec.rb @@ -694,6 +694,18 @@ expect(nonprofit.payments.during_np_year(Time.new.utc.year)).to contain_exactly(payment2, payment3) end end + + describe "#prior_to_np_year" do + it "has no payments when nonprofit has UTC time zone" do + expect(nonprofit.payments.prior_to_np_year(Time.new.utc.year)).to contain_exactly() + end + + it "has 1 payment when nonprofit has Central time zone" do + nonprofit.timezone = "America/Chicago" + nonprofit.save! + expect(nonprofit.payments.prior_to_np_year(Time.new.utc.year)).to contain_exactly(payment1) + end + end end From e8db6d8f9bbbf483c8ae48a9648991797c011668 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 1 Dec 2023 14:39:22 -0600 Subject: [PATCH 046/208] Remove full contact worker --- Procfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Procfile b/Procfile index 948e54291..c27265cf4 100644 --- a/Procfile +++ b/Procfile @@ -1,3 +1,2 @@ web: bundle exec puma -C ./config/puma.rb worker: bundle exec rake jobs:work -full_contact_worker: bundle exec rake work_full_contact_queue From badb97066f3c07c7251bf2f9e3607f5322867f4f Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 1 Dec 2023 14:47:46 -0600 Subject: [PATCH 047/208] Turn InsertFullContactInfos.enqueue into a noop --- app/legacy_lib/insert_full_contact_infos.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/legacy_lib/insert_full_contact_infos.rb b/app/legacy_lib/insert_full_contact_infos.rb index 5bd28126c..b6a2ac577 100644 --- a/app/legacy_lib/insert_full_contact_infos.rb +++ b/app/legacy_lib/insert_full_contact_infos.rb @@ -16,9 +16,7 @@ def self.work_queue # Enqueue full contact jobs for a set of supporter ids def self.enqueue(supporter_ids) - Qx.insert_into(:full_contact_jobs) - .values(supporter_ids.map{|id| {supporter_id: id}}) - .ex + # noop since we don't use this any more end From dcaf0a82aba942b7f63453f20037b20e67796a20 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 1 Dec 2023 15:02:20 -0600 Subject: [PATCH 048/208] Remove additional full-contact worker stuff --- app/legacy_lib/insert_import.rb | 2 +- app/legacy_lib/insert_supporter.rb | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/legacy_lib/insert_import.rb b/app/legacy_lib/insert_import.rb index 67ae61d97..5cebae80e 100644 --- a/app/legacy_lib/insert_import.rb +++ b/app/legacy_lib/insert_import.rb @@ -167,7 +167,7 @@ def self.from_csv(data) .where(id: import['id']) .returning('*') .execute.first - InsertFullContactInfos.enqueue(supporter_ids) if supporter_ids.any? + ImportMailer.delay.import_completed_notification(import['id']) return import end diff --git a/app/legacy_lib/insert_supporter.rb b/app/legacy_lib/insert_supporter.rb index 24f3d92bb..32b2e8810 100644 --- a/app/legacy_lib/insert_supporter.rb +++ b/app/legacy_lib/insert_supporter.rb @@ -28,8 +28,6 @@ def self.create_or_update(np_id, data, update=false) InsertTagJoins.find_or_create(np_id, [supporter['id']], tags) if tags.present? #GeocodeModel.delay.supporter(supporter['id']) - InsertFullContactInfos.enqueue([supporter['id']]) - return supporter end From ea248192f91759973d4d92bf95429363607418a6 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 1 Dec 2023 15:03:01 -0600 Subject: [PATCH 049/208] Remove references in heroku.yml --- heroku.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/heroku.yml b/heroku.yml index ebb70d734..ea6927628 100644 --- a/heroku.yml +++ b/heroku.yml @@ -9,7 +9,3 @@ run: command: - bundle exec rake jobs:work image: web - full_contact_worker: - command: - - bundle exec rake work_full_contact_queue - image: web From 790421873487b26e4b394b677a1b364d9e5b39bc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 1 Dec 2023 15:03:56 -0600 Subject: [PATCH 050/208] Remove work_full_contact_queue --- lib/tasks/full_contact.rake | 17 ----------------- 1 file changed, 17 deletions(-) delete mode 100644 lib/tasks/full_contact.rake diff --git a/lib/tasks/full_contact.rake b/lib/tasks/full_contact.rake deleted file mode 100644 index 7ef13ced4..000000000 --- a/lib/tasks/full_contact.rake +++ /dev/null @@ -1,17 +0,0 @@ -# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -desc "For generating Full Contact data" - -# Clear old activerecord sessions tables daily -task :work_full_contact_queue => :environment do - - loop do - sleep(10) until Qx.select("COUNT(*)").from("full_contact_jobs").execute.first['count'] > 0 - puts "working..." - - begin - InsertFullContactInfos.work_queue - rescue Exception => e - puts "Exception thrown: #{e}" - end - end -end From 8a5b31d76e076fd13f0934acc96fbd16c9efde9e Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 1 Dec 2023 15:04:36 -0600 Subject: [PATCH 051/208] Remove insert-full-contact infos --- app/legacy_lib/insert_full_contact_infos.rb | 181 -------------------- 1 file changed, 181 deletions(-) delete mode 100644 app/legacy_lib/insert_full_contact_infos.rb diff --git a/app/legacy_lib/insert_full_contact_infos.rb b/app/legacy_lib/insert_full_contact_infos.rb deleted file mode 100644 index b6a2ac577..000000000 --- a/app/legacy_lib/insert_full_contact_infos.rb +++ /dev/null @@ -1,181 +0,0 @@ -# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -require 'qx' -require 'httparty' -module InsertFullContactInfos - include HTTParty - format :json - logger Rails.logger, :info, :full_contact - - # Work off of the full_contact_jobs queue - def self.work_queue - ids = Qx.select('supporter_id').from('full_contact_jobs').ex.map{|h| h['supporter_id']} - Qx.delete_from('full_contact_jobs').where('TRUE').execute - self.bulk(ids) if ids.any? - end - - - # Enqueue full contact jobs for a set of supporter ids - def self.enqueue(supporter_ids) - # noop since we don't use this any more - end - - - # We need to throttle our requests by 10ms since that is our rate limit on FullContact - def self.bulk(supporter_ids) - created_ids = [] - supporter_ids.each do |id| - now = Time.current - result = InsertFullContactInfos.single id - created_ids.push(GetData.hash(result, 'full_contact_info', 'id')) if result.is_a?(Hash) - interval = 0.1 - (Time.current - now) # account for time taken in .single - sleep interval if interval > 0 - end - return created_ids - end - - - # Fetch and persist a single full contact record for a single supporter - # return an exception if 404 or something else went poop - def self.single(supporter_id) - supp = Qx.select('email', 'nonprofit_id').from('supporters').where(id: supporter_id).execute.first - return if supp.nil? || supp['email'].blank? - - begin - response = post("https://api.fullcontact.com/v3/person.enrich", - body: { - "email" => supp['email'], - }.to_json, - headers: { - :authorization => "Bearer #{FULL_CONTACT_KEY}", - "Reporting-Key" => supp['nonprofit_id'].to_s - }) - result = JSON::parse(response.body) - rescue Exception => e - return e - end - - location = result['location'] && result['details']['locations'] && result['details']['locations'][0] - existing = Qx.select('id').from('full_contact_infos').where(supporter_id: supporter_id).ex.first - info_data = { - full_name: result['fullName'], - gender: result['gender'], - city: location && location['city'], - state_code: location && location['regionCode'], - country: location && location['countryCode'], - age_range: result['ageRange'], - location_general: result['location'], - websites: ((result['details'] && result['details']['urls']) || []).map{|h| h['value']}.join(','), - supporter_id: supporter_id - } - - if existing - full_contact_info = Qx.update(:full_contact_infos) - .set(info_data) - .timestamps - .where(id: existing['id']) - .returning('*') - .execute.first - else - full_contact_info = Qx.insert_into(:full_contact_infos) - .values(info_data) - .returning('*') - .timestamps - .execute.first - end - - if result['details']['photos'].present? - photo_data = result['details']['photos'].map{|h| {type_id: h['label'], url: h['value']}} - Qx.delete_from("full_contact_photos") - .where(full_contact_info_id: full_contact_info['id']) - .execute - full_contact_photos = Qx.insert_into(:full_contact_photos) - .values(photo_data) - .common_values(full_contact_info_id: full_contact_info['id']) - .timestamps - .returning("*") - .execute - end - - if result['details']['profiles'].present? - profile_data = result['details']['profiles'].map{|k,v| {type_id: v['service'], username: v['username'], uid: v['userid'], bio: v['bio'], url: v['url'], followers: v['followers'], following: v['following']} } - Qx.delete_from("full_contact_social_profiles") - .where(full_contact_info_id: full_contact_info['id']) - .execute - full_contact_social_profiles = Qx.insert_into(:full_contact_social_profiles) - .values(profile_data) - .common_values(full_contact_info_id: full_contact_info['id']) - .timestamps - .returning("*") - .execute - end - - if result['details'].present? && result['details']['employment'].present? - Qx.delete_from('full_contact_orgs') - .where(full_contact_info_id: full_contact_info['id']) - .execute - org_data = result['details']['employment'].map{|h| - start_date = nil - end_date = nil - start_date = h['start'] && [h['start']['year'], h['start']['month'], h['start']['day']].select{|i| i.present?}.join('-') - end_date = h['end'] && [h['end']['year'], h['end']['month'], h['end']['day']].select{|i| i.present?}.join('-') - { - name: h['name'], - start_date: start_date, - end_date: end_date, - title: h['title'], - current: h['current'] - } } - .map{|h| h[:end_date] = Format::Date.parse_partial_str(h[:end_date]); h} - .map{|h| h[:start_date] = Format::Date.parse_partial_str(h[:start_date]); h} - - full_contact_orgs = Qx.insert_into(:full_contact_orgs) - .values(org_data) - .common_values(full_contact_info_id: full_contact_info['id']) - .timestamps - .returning('*') - .execute - end - - return { - 'full_contact_info' => full_contact_info, - 'full_contact_photos' => full_contact_photos, - 'full_contact_social_profiles' => full_contact_social_profiles, - 'full_contact_orgs' => full_contact_orgs - } - end - - # Delete all orphaned full contact infos that do not have supporters - # or full_contact photos, social_profiles, topics, orgs, etc that do not have a parent info - def self.cleanup_orphans - Qx.delete_from("full_contact_infos") - .where("id IN ($ids)", ids: Qx.select("full_contact_infos.id") - .from("full_contact_infos") - .left_join("supporters", "full_contact_infos.supporter_id=supporters.id") - .where("supporters.id IS NULL") - ).ex - Qx.delete_from("full_contact_photos") - .where("id IN ($ids)", ids: Qx.select("full_contact_photos.id") - .from("full_contact_photos") - .left_join("full_contact_infos", "full_contact_infos.id=full_contact_photos.full_contact_info_id") - .where("full_contact_infos.id IS NULL") - ).ex - Qx.delete_from("full_contact_social_profiles") - .where("id IN ($ids)", ids: Qx.select("full_contact_social_profiles.id") - .from("full_contact_social_profiles") - .left_join("full_contact_infos", "full_contact_infos.id=full_contact_social_profiles.full_contact_info_id") - .where("full_contact_infos.id IS NULL") - ).ex - Qx.delete_from("full_contact_topics") - .where("id IN ($ids)", ids: Qx.select("full_contact_topics.id") - .from("full_contact_topics") - .left_join("full_contact_infos", "full_contact_infos.id=full_contact_topics.full_contact_info_id") - .where("full_contact_infos.id IS NULL") - ).ex - Qx.delete_from("full_contact_orgs") - .where("id IN ($ids)", ids: Qx.select("full_contact_orgs.id") - .from("full_contact_orgs") - .left_join("full_contact_infos", "full_contact_infos.id=full_contact_orgs.full_contact_info_id") - .where("full_contact_infos.id IS NULL") - ).ex - end -end From 8200892148562a0c7783a6c6d9e3afabccf02ed3 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 1 Dec 2023 15:07:07 -0600 Subject: [PATCH 052/208] Drop full_contact_jobs table since we don't use the worker anymore --- .../20231201210543_drop_full_contact_jobs_table.rb | 5 +++++ db/schema.rb | 12 ++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 db/migrate/20231201210543_drop_full_contact_jobs_table.rb diff --git a/db/migrate/20231201210543_drop_full_contact_jobs_table.rb b/db/migrate/20231201210543_drop_full_contact_jobs_table.rb new file mode 100644 index 000000000..23efe8b9d --- /dev/null +++ b/db/migrate/20231201210543_drop_full_contact_jobs_table.rb @@ -0,0 +1,5 @@ +class DropFullContactJobsTable < ActiveRecord::Migration + def change + drop_table :full_contact_jobs + end +end diff --git a/db/schema.rb b/db/schema.rb index ce19a3a2d..6e04a3b92 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20231019183536) do +ActiveRecord::Schema.define(version: 20231201210543) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -512,10 +512,6 @@ add_index "full_contact_infos", ["supporter_id"], name: "index_full_contact_infos_on_supporter_id", using: :btree - create_table "full_contact_jobs", force: :cascade do |t| - t.integer "supporter_id" - end - create_table "full_contact_orgs", force: :cascade do |t| t.boolean "is_primary" t.string "name", limit: 255 @@ -1454,10 +1450,10 @@ create_trigger :update_donations_fts, sql_definition: <<-SQL CREATE TRIGGER update_donations_fts BEFORE INSERT OR UPDATE ON public.donations FOR EACH ROW EXECUTE FUNCTION update_fts_on_donations() SQL - create_trigger :update_supporters_fts, sql_definition: <<-SQL - CREATE TRIGGER update_supporters_fts BEFORE INSERT OR UPDATE ON public.supporters FOR EACH ROW EXECUTE FUNCTION update_fts_on_supporters() - SQL create_trigger :update_supporters_phone_index, sql_definition: <<-SQL CREATE TRIGGER update_supporters_phone_index BEFORE INSERT OR UPDATE ON public.supporters FOR EACH ROW EXECUTE FUNCTION update_phone_index_on_supporters() SQL + create_trigger :update_supporters_fts, sql_definition: <<-SQL + CREATE TRIGGER update_supporters_fts BEFORE INSERT OR UPDATE ON public.supporters FOR EACH ROW EXECUTE FUNCTION update_fts_on_supporters() + SQL end From f33aa8a052e908b98907469223c4a4df62e5e333 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 3 Dec 2023 13:40:11 -0600 Subject: [PATCH 053/208] Add logging for issues Factorybot creation issues --- config/environments/development.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/environments/development.rb b/config/environments/development.rb index 19fcea5a8..1b982534f 100755 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -75,4 +75,9 @@ config.middleware.use Rack::Attack NONPROFIT_VERIFICATION_SEND_EMAIL_DELAY = 5.minutes + + ActiveSupport::Notifications.subscribe("factory_bot.run_factory") do |name, start, finish, id, payload| + Rails.logger.debug(payload) + + end end From 54db241f35ebe4c6c0bd495e977a73aebb96c78e Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 3 Dec 2023 13:41:27 -0600 Subject: [PATCH 054/208] Create the actual objects used by TaxMailer preview --- spec/mailers/previews/tax_mailer_preview.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 64d142a8e..dd7295128 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -5,9 +5,10 @@ class TaxMailerPreview < ActionMailer::Preview # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt def annual_receipt tax_id = "12-3456789" - supporter = build(:supporter_generator, nonprofit: build(:fv_poverty, ein: tax_id)) + supporter = create(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) + tax_year = 2023 - payments = build_list(:donation_payment_generator, Random.rand(5) + 1, + payments = create_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit ) From 9b942f72f7d84a16f3991bc229864dbe1dc3ebbc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 3 Dec 2023 13:41:48 -0600 Subject: [PATCH 055/208] Put a heading above donations for the tax mailer receipt --- app/views/tax_mailer/annual_receipt.html.erb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index d102f5432..7aef772ef 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -31,10 +31,11 @@ - - -<% @donation_payments.each do |payment| %> - <%= render "tax_mailer/donation_payment_table", payment: payment %> +<% if @donation_payments.any? %> +

Donations

+ <% @donation_payments.each do |payment| %> + <%= render "tax_mailer/donation_payment_table", payment: payment %> + <% end %> <% end %> <% @refund_payments.each do |payment| %> From 4146e36836f1cd12cd3a17de247f7299557f86ec Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 3 Dec 2023 14:03:45 -0600 Subject: [PATCH 056/208] Format refunds in tax mailer --- app/views/tax_mailer/annual_receipt.html.erb | 7 +++++-- spec/factories/payments.rb | 12 +++++++++++ spec/factories/refunds.rb | 2 +- spec/mailers/previews/tax_mailer_preview.rb | 21 ++++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index 7aef772ef..705e9e824 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -38,8 +38,11 @@ <% end %> <% end %> -<% @refund_payments.each do |payment| %> - <%= render "tax_mailer/refund_payment_table", payment: payment %> +<% if @refund_payments.any? %> +

Refunds

+ <% @refund_payments.each do |payment| %> + <%= render "tax_mailer/refund_payment_table", payment: payment %> + <% end %> <% end %> diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index 6a1aeabab..696a4b5b1 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -34,6 +34,18 @@ date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} end + factory :refund_payment_generator, class: "Payment" do + + transient do + amount { 100 + Random.rand(5000)} + end + + refund { association :refund_base, amount: amount, created_at: date} + gross_amount { amount} + supporter + date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} + end + factory :fv_poverty_payment, class: "Payment" do diff --git a/spec/factories/refunds.rb b/spec/factories/refunds.rb index 70184f7a1..a2e7f8380 100644 --- a/spec/factories/refunds.rb +++ b/spec/factories/refunds.rb @@ -1,6 +1,6 @@ # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later FactoryBot.define do - factory :refund do + factory :refund, aliases: [:refund_base] do trait :from_donation do charge { build(:charge_base, payment: build(:payment, donation: build(:donation)))} end diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index dd7295128..9a1106b7a 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -17,4 +17,25 @@ def annual_receipt TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments) end + # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt_with_refunds + def annual_receipt_with_refunds + tax_id = "12-3456789" + supporter = create(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) + + tax_year = 2023 + payments = create_list(:donation_payment_generator, Random.rand(5) + 1, + supporter: supporter, + nonprofit: supporter.nonprofit + ) + + refund_payments = create_list(:refund_payment_generator, Random.rand(5) + 1, + supporter: supporter, + nonprofit: supporter.nonprofit + ) + + nonprofit_text = "

#{Faker::Lorem.paragraph(sentence_count: 5)}

" + "

#{Faker::Lorem.paragraph(sentence_count:3)}

" + TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments, refund_payments: refund_payments) + end + + end From 75ccf621dbc99812ec0dccce0f5d0b3db9333408 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 4 Dec 2023 13:24:56 -0600 Subject: [PATCH 057/208] Add dispute tables to the Tax Mailer --- app/views/tax_mailer/annual_receipt.html.erb | 14 +++++++--- spec/factories/dispute_transactions.rb | 2 +- spec/factories/payments.rb | 29 ++++++++++++++++++-- spec/mailers/previews/tax_mailer_preview.rb | 25 +++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index 705e9e824..689bb2170 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -46,11 +46,17 @@ <% end %> -<% @dispute_payments.each do |payment| %> - <%= render "tax_mailer/dispute_payment_table", payment: payment %> +<% if @dispute_payments.any? %> +

Disputed Payments

+ <% @dispute_payments.each do |payment| %> + <%= render "tax_mailer/dispute_payment_table", payment: payment %> + <% end %> <% end %> -<% @dispute_reversal_payments.each do |payment| %> - <%= render "tax_mailer/dispute_reversal_payment_table", payment: payment %> +<% if @dispute_reversal_payments.any? %> +

Dispute reversal payments

+ <% @dispute_reversal_payments.each do |payment| %> + <%= render "tax_mailer/dispute_reversal_payment_table", payment: payment %> + <% end %> <% end %> diff --git a/spec/factories/dispute_transactions.rb b/spec/factories/dispute_transactions.rb index 57c80e1b1..685cac466 100644 --- a/spec/factories/dispute_transactions.rb +++ b/spec/factories/dispute_transactions.rb @@ -1,5 +1,5 @@ FactoryBot.define do - factory :dispute_transaction do + factory :dispute_transaction, aliases: [:dispute_transaction_base] do disbursed {false} trait :from_donation do diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index 696a4b5b1..390525c61 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -37,16 +37,39 @@ factory :refund_payment_generator, class: "Payment" do transient do - amount { 100 + Random.rand(5000)} + amount { (100 + Random.rand(5000)) * -1} + end + + refund { association :refund_base, amount: amount * -1, created_at: date} + gross_amount { amount} + supporter + date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} + end + + + factory :dispute_payment_generator, class: "Payment" do + + transient do + amount { (100 + Random.rand(5000)) * -1} end - refund { association :refund_base, amount: amount, created_at: date} + dispute_transaction { association :dispute_transaction_base, created_at: date} gross_amount { amount} supporter date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} end - + factory :dispute_reversal_payment_generator, class: "Payment" do + + transient do + amount { 100 + Random.rand(5000)} + end + + dispute_transaction { association :dispute_transaction_base, created_at: date} + gross_amount { amount} + supporter + date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} + end factory :fv_poverty_payment, class: "Payment" do donation {build(:fv_poverty_donation, nonprofit: nonprofit, supporter: supporter) } diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 9a1106b7a..c9608215d 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -37,5 +37,30 @@ def annual_receipt_with_refunds TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments, refund_payments: refund_payments) end + # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt_with_disputes + def annual_receipt_with_disputes + tax_id = "12-3456789" + supporter = create(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) + + tax_year = 2023 + payments = create_list(:donation_payment_generator, Random.rand(5) + 1, + supporter: supporter, + nonprofit: supporter.nonprofit + ) + + dispute_payments = create_list(:dispute_payment_generator, Random.rand(5) + 1, + supporter: supporter, + nonprofit: supporter.nonprofit + ) + + dispute_reversal_payments = create_list(:dispute_reversal_payment_generator, Random.rand(5) + 0, + supporter: supporter, + nonprofit: supporter.nonprofit + ) + + nonprofit_text = "

#{Faker::Lorem.paragraph(sentence_count: 5)}

" + "

#{Faker::Lorem.paragraph(sentence_count:3)}

" + TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments, dispute_payments: dispute_payments, dispute_reversal_payments: dispute_reversal_payments) + end + end From d46769594892533cdd19192e8dc8ac5fdb4d4b08 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 4 Dec 2023 13:28:06 -0600 Subject: [PATCH 058/208] Add Total to tax mailer --- app/mailers/tax_mailer.rb | 8 ++++++++ app/views/tax_mailer/annual_receipt.html.erb | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/mailers/tax_mailer.rb b/app/mailers/tax_mailer.rb index 61e05989a..f38b52634 100644 --- a/app/mailers/tax_mailer.rb +++ b/app/mailers/tax_mailer.rb @@ -11,6 +11,8 @@ def annual_receipt(supporter:, year:, nonprofit_text:, donation_payments: [], re @nonprofit = supporter.nonprofit @year = year + + @total = get_payment_sum(donation_payments, refund_payments, dispute_payments, dispute_reversal_payments) @donation_payments = donation_payments @refund_payments = refund_payments @dispute_payments = dispute_payments @@ -20,4 +22,10 @@ def annual_receipt(supporter:, year:, nonprofit_text:, donation_payments: [], re mail(to: @supporter.email, subject: "#{@year} Tax Receipt from #{@nonprofit.name}") end + + + private + def get_payment_sum(*payments) + payments.flatten.sum(&:gross_amount) + end end diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index d102f5432..61e111861 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -18,7 +18,7 @@ Total Amount - $ TODO + $<%= Format::Currency.cents_to_dollars(@total) %> Organization From e0d9c7a18e7502e20268da836277e419b6515a30 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 5 Dec 2023 18:28:36 -0600 Subject: [PATCH 059/208] Add email_customizations to Nonprofit --- app/models/nonprofit.rb | 2 ++ spec/models/nonprofit_spec.rb | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/models/nonprofit.rb b/app/models/nonprofit.rb index a918d6ac8..e27c9141b 100755 --- a/app/models/nonprofit.rb +++ b/app/models/nonprofit.rb @@ -145,6 +145,8 @@ def dupes_on_address_without_zip_code(strict_mode = true) has_one :nonprofit_deactivation has_one :stripe_account, foreign_key: :stripe_account_id, primary_key: :stripe_account_id + has_many :email_customizations + has_many :associated_object_events, class_name: 'ObjectEvent' validates :name, presence: true diff --git a/spec/models/nonprofit_spec.rb b/spec/models/nonprofit_spec.rb index e59796fca..0fdf8eeb4 100644 --- a/spec/models/nonprofit_spec.rb +++ b/spec/models/nonprofit_spec.rb @@ -20,6 +20,8 @@ it {is_expected.to have_many(:email_lists)} it {is_expected.to have_one(:nonprofit_key)} + it {is_expected.to have_many(:email_customizations)} + it {is_expected.to have_many(:associated_object_events).class_name("ObjectEvent")} describe 'with cards' do From d57f88af0ae28dcc7bd926b1abe3bea35c00058a Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 6 Dec 2023 09:35:08 -0600 Subject: [PATCH 060/208] Remove straggling support for email creation and updating --- ...reate_or_update_email_draft_modal.html.erb | 37 ---------------- .../_manage_email_drafts_modal.html.erb | 44 ------------------- .../nonprofits/supporters/index.html.erb | 3 -- 3 files changed, 84 deletions(-) delete mode 100644 app/views/nonprofits/supporters/_create_or_update_email_draft_modal.html.erb delete mode 100644 app/views/nonprofits/supporters/_manage_email_drafts_modal.html.erb diff --git a/app/views/nonprofits/supporters/_create_or_update_email_draft_modal.html.erb b/app/views/nonprofits/supporters/_create_or_update_email_draft_modal.html.erb deleted file mode 100644 index 1e97111f3..000000000 --- a/app/views/nonprofits/supporters/_create_or_update_email_draft_modal.html.erb +++ /dev/null @@ -1,37 +0,0 @@ -<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> - - - diff --git a/app/views/nonprofits/supporters/_manage_email_drafts_modal.html.erb b/app/views/nonprofits/supporters/_manage_email_drafts_modal.html.erb deleted file mode 100644 index 90e0ef339..000000000 --- a/app/views/nonprofits/supporters/_manage_email_drafts_modal.html.erb +++ /dev/null @@ -1,44 +0,0 @@ -<%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> - diff --git a/app/views/nonprofits/supporters/index.html.erb b/app/views/nonprofits/supporters/index.html.erb index 1d61ddb2d..1e9a5f580 100644 --- a/app/views/nonprofits/supporters/index.html.erb +++ b/app/views/nonprofits/supporters/index.html.erb @@ -78,9 +78,6 @@ <%= render 'map_modal' %> -<%= render 'manage_email_drafts_modal' %> -<%= render 'create_or_update_email_draft_modal' %> - <%= render 'donations/new_offline_modal' %>
From ef7c09336694608c4300964f61a360293ddaf861 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 6 Dec 2023 15:52:46 -0600 Subject: [PATCH 061/208] Remove an unused import --- client/js/nonprofits/supporters/index/list_supporters.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/js/nonprofits/supporters/index/list_supporters.js b/client/js/nonprofits/supporters/index/list_supporters.js index d0442899a..882c8c0ee 100644 --- a/client/js/nonprofits/supporters/index/list_supporters.js +++ b/client/js/nonprofits/supporters/index/list_supporters.js @@ -2,7 +2,6 @@ const flyd = require('flimflam/flyd') // for ajaxing /index_metrics, line 27 const request = require('../../../common/request') // for ajaxing /index_metrics var map = require('../../../components/maps/cc_map') -var npo_coords = require('../../../components/maps/npo_coordinates')() appl.def('supporters.selected', []) From af9fbee4be21f2157a36eb677321f1ef27de5136 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 6 Dec 2023 15:53:02 -0600 Subject: [PATCH 062/208] Change an import var to a const --- client/js/nonprofits/supporters/index/list_supporters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/nonprofits/supporters/index/list_supporters.js b/client/js/nonprofits/supporters/index/list_supporters.js index 882c8c0ee..5d08c7247 100644 --- a/client/js/nonprofits/supporters/index/list_supporters.js +++ b/client/js/nonprofits/supporters/index/list_supporters.js @@ -1,7 +1,7 @@ // License: LGPL-3.0-or-later const flyd = require('flimflam/flyd') // for ajaxing /index_metrics, line 27 const request = require('../../../common/request') // for ajaxing /index_metrics -var map = require('../../../components/maps/cc_map') +const map = require('../../../components/maps/cc_map') appl.def('supporters.selected', []) From 89c3ee3da4955d44ab9d72fe66634a280dd9d729 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 7 Dec 2023 11:12:44 -0600 Subject: [PATCH 063/208] Removed unneeded import in client/js/settings/index/integrations/index.js --- client/js/settings/index/integrations/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/client/js/settings/index/integrations/index.js b/client/js/settings/index/integrations/index.js index 8ae977764..a39e36417 100644 --- a/client/js/settings/index/integrations/index.js +++ b/client/js/settings/index/integrations/index.js @@ -3,7 +3,6 @@ const h = require('snabbdom/h') const R = require('ramda') const flyd = require('flyd') const request = require('../../../common/request') -const flyd_lift = require('flyd/module/lift') const colors = require('../../../common/colors') function init() { From 6dbd9905f365da86d7457dca9ae630c7ec5a789e Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 7 Dec 2023 13:55:00 -0600 Subject: [PATCH 064/208] Add ability to get ids for test payments and to sort payments --- app/mailers/tax_mailer.rb | 8 ++++---- spec/factories/payments.rb | 18 ++++++++++++++---- spec/factories/supporters.rb | 8 +++++++- spec/mailers/previews/tax_mailer_preview.rb | 2 +- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/app/mailers/tax_mailer.rb b/app/mailers/tax_mailer.rb index f38b52634..d012d6f9c 100644 --- a/app/mailers/tax_mailer.rb +++ b/app/mailers/tax_mailer.rb @@ -13,10 +13,10 @@ def annual_receipt(supporter:, year:, nonprofit_text:, donation_payments: [], re @total = get_payment_sum(donation_payments, refund_payments, dispute_payments, dispute_reversal_payments) - @donation_payments = donation_payments - @refund_payments = refund_payments - @dispute_payments = dispute_payments - @dispute_reversal_payments = dispute_reversal_payments + @donation_payments = donation_payments.sort_by(&:date) + @refund_payments = refund_payments.sort_by(&:date) + @dispute_payments = dispute_payments.sort_by(&:date) + @dispute_reversal_payments = dispute_reversal_payments.sort_by(&:date) @tax_id = supporter.nonprofit.ein @nonprofit_text = nonprofit_text diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index 6a1aeabab..3a344c10e 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -22,16 +22,26 @@ net_amount { gross_amount + fee_total} end - - factory :donation_payment_generator, class: "Payment" do + factory :payment_generator_with_id, class: "Payment" do transient do amount { 100 + Random.rand(5000)} + end - - donation { association :donation, amount: amount, supporter: supporter, nonprofit: nonprofit, created_at: date} + + sequence(:id) + gross_amount { amount} supporter date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} + + before(:create) do |payment| + payment.id = nil if payment.id + end + + factory :donation_payment_generator do + donation { association :donation, amount: amount, supporter: supporter, nonprofit: nonprofit, created_at: date} + end + end diff --git a/spec/factories/supporters.rb b/spec/factories/supporters.rb index bfef669bf..2f0ed3a1c 100644 --- a/spec/factories/supporters.rb +++ b/spec/factories/supporters.rb @@ -11,10 +11,16 @@ end end - factory :supporter_generator, class: 'Supporter' do + factory :supporter_generator, class: 'Supporter' do + sequence(:id) + name { Faker::Name.name } email { Faker::Internet.email } nonprofit + + before(:create) do |supporter| + supporter.id = nil if supporter.id + end end trait :with_primary_address do diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index dd7295128..54bed475a 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -8,7 +8,7 @@ def annual_receipt supporter = create(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) tax_year = 2023 - payments = create_list(:donation_payment_generator, Random.rand(5) + 1, + payments = build_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit ) From 18123ae46a726b9cc184f8fdb415b254d3e1c8fb Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 7 Dec 2023 14:04:21 -0600 Subject: [PATCH 065/208] Only generate a supporter in the tax mailer --- spec/mailers/previews/tax_mailer_preview.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 54bed475a..85bb286e3 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -5,7 +5,7 @@ class TaxMailerPreview < ActionMailer::Preview # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt def annual_receipt tax_id = "12-3456789" - supporter = create(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) + supporter = build(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) tax_year = 2023 payments = build_list(:donation_payment_generator, Random.rand(5) + 1, From 5af1d0d41f29fb5d56c2114c1f175206b0c621e1 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 7 Dec 2023 14:09:48 -0600 Subject: [PATCH 066/208] Simplify refund payment generator --- spec/factories/payments.rb | 14 ++++---------- spec/mailers/previews/tax_mailer_preview.rb | 6 +++--- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index a5fb97a49..9f058d6e7 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -42,20 +42,14 @@ donation { association :donation, amount: amount, supporter: supporter, nonprofit: nonprofit, created_at: date} end - end - - factory :refund_payment_generator, class: "Payment" do - - transient do - amount { 100 + Random.rand(5000)} + factory :refund_payment_generator do + refund { association :refund_base, amount: amount, created_at: date} end + - refund { association :refund_base, amount: amount, created_at: date} - gross_amount { amount} - supporter - date { Faker::Time.between(from: Time.current.beginning_of_year, to: Time.current.end_of_year)} end + factory :fv_poverty_payment, class: "Payment" do diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 363390749..2ea1ee379 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -20,15 +20,15 @@ def annual_receipt # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt_with_refunds def annual_receipt_with_refunds tax_id = "12-3456789" - supporter = create(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) + supporter = build(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) tax_year = 2023 - payments = create_list(:donation_payment_generator, Random.rand(5) + 1, + payments = build_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit ) - refund_payments = create_list(:refund_payment_generator, Random.rand(5) + 1, + refund_payments = build_list(:refund_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit ) From bd786b09c3b4511d88920a001b58ab169b021f84 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 7 Dec 2023 14:12:30 -0600 Subject: [PATCH 067/208] Make refund amounts negative --- spec/factories/payments.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/factories/payments.rb b/spec/factories/payments.rb index 9f058d6e7..1bbfef0df 100644 --- a/spec/factories/payments.rb +++ b/spec/factories/payments.rb @@ -43,7 +43,8 @@ end factory :refund_payment_generator do - refund { association :refund_base, amount: amount, created_at: date} + refund { association :refund_base, amount: amount * -1, created_at: date} + gross_amount { amount * -1 } end From 64fdae47fdfc65a3048ef60ecbf6a976d610bb34 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 7 Dec 2023 14:16:34 -0600 Subject: [PATCH 068/208] Use build for tax mailer preview --- spec/mailers/previews/tax_mailer_preview.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 393e4e3f0..7fa21beb3 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -40,20 +40,20 @@ def annual_receipt_with_refunds # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt_with_disputes def annual_receipt_with_disputes tax_id = "12-3456789" - supporter = create(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) + supporter = build(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) tax_year = 2023 - payments = create_list(:donation_payment_generator, Random.rand(5) + 1, + payments = build_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit ) - dispute_payments = create_list(:dispute_payment_generator, Random.rand(5) + 1, + dispute_payments = build_list(:dispute_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit ) - dispute_reversal_payments = create_list(:dispute_reversal_payment_generator, Random.rand(5) + 0, + dispute_reversal_payments = build_list(:dispute_reversal_payment_generator, Random.rand(5) + 0, supporter: supporter, nonprofit: supporter.nonprofit ) From f7122b477e5cd84021670edd9e286c8ceb3facdc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 7 Dec 2023 14:44:53 -0600 Subject: [PATCH 069/208] Customize tax mailer receipt --- spec/mailers/previews/tax_mailer_preview.rb | 37 ++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 7fa21beb3..8a79cafde 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -4,25 +4,15 @@ class TaxMailerPreview < ActionMailer::Preview include FactoryBot::Syntax::Methods # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt def annual_receipt - tax_id = "12-3456789" - supporter = build(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) - - tax_year = 2023 payments = build_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit ) - - nonprofit_text = "

#{Faker::Lorem.paragraph(sentence_count: 5)}

" + "

#{Faker::Lorem.paragraph(sentence_count:3)}

" TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments) end # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt_with_refunds def annual_receipt_with_refunds - tax_id = "12-3456789" - supporter = build(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) - - tax_year = 2023 payments = build_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit @@ -32,17 +22,11 @@ def annual_receipt_with_refunds supporter: supporter, nonprofit: supporter.nonprofit ) - - nonprofit_text = "

#{Faker::Lorem.paragraph(sentence_count: 5)}

" + "

#{Faker::Lorem.paragraph(sentence_count:3)}

" TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments, refund_payments: refund_payments) end # Preview this email at http://localhost:5000/rails/mailers/tax_mailer/annual_receipt_with_disputes def annual_receipt_with_disputes - tax_id = "12-3456789" - supporter = build(:supporter_generator, nonprofit: build(:nonprofit_base, ein: tax_id)) - - tax_year = 2023 payments = build_list(:donation_payment_generator, Random.rand(5) + 1, supporter: supporter, nonprofit: supporter.nonprofit @@ -56,11 +40,26 @@ def annual_receipt_with_disputes dispute_reversal_payments = build_list(:dispute_reversal_payment_generator, Random.rand(5) + 0, supporter: supporter, nonprofit: supporter.nonprofit - ) - - nonprofit_text = "

#{Faker::Lorem.paragraph(sentence_count: 5)}

" + "

#{Faker::Lorem.paragraph(sentence_count:3)}

" + ) TaxMailer.annual_receipt(year: tax_year, supporter: supporter, nonprofit_text: nonprofit_text, donation_payments: payments, dispute_payments: dispute_payments, dispute_reversal_payments: dispute_reversal_payments) end + private + + def nonprofit + @nonprofit ||= Nonprofit.find(3693) + end + + def nonprofit_text + @nonprofit_text ||= nonprofit.email_customizations.where(name: "2023 Tax Receipt").first&.contents + end + + def supporter + @supporter ||= build(:supporter_generator, nonprofit: nonprofit) + end + + def tax_year + 2023 + end end From 37a2c78b053491f1578a7883894521fb46ae55f0 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 8 Dec 2023 12:52:53 -0600 Subject: [PATCH 070/208] Reformat --- spec/mailers/previews/tax_mailer_preview.rb | 24 ++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/mailers/previews/tax_mailer_preview.rb b/spec/mailers/previews/tax_mailer_preview.rb index 8a79cafde..b62dffd35 100644 --- a/spec/mailers/previews/tax_mailer_preview.rb +++ b/spec/mailers/previews/tax_mailer_preview.rb @@ -47,19 +47,19 @@ def annual_receipt_with_disputes private - def nonprofit - @nonprofit ||= Nonprofit.find(3693) - end + def nonprofit + @nonprofit ||= Nonprofit.find(3693) + end - def nonprofit_text - @nonprofit_text ||= nonprofit.email_customizations.where(name: "2023 Tax Receipt").first&.contents - end + def nonprofit_text + @nonprofit_text ||= nonprofit.email_customizations.where(name: "2023 Tax Receipt").first&.contents + end - def supporter - @supporter ||= build(:supporter_generator, nonprofit: nonprofit) - end + def supporter + @supporter ||= build(:supporter_generator, nonprofit: nonprofit) + end - def tax_year - 2023 - end + def tax_year + 2023 + end end From 877ce7fe519699e09c2c6c734eba99c203f1e3e4 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sat, 9 Dec 2023 12:44:31 -0600 Subject: [PATCH 071/208] Add mailer previews to staging --- config/environments/staging.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/environments/staging.rb b/config/environments/staging.rb index 99842d000..63897cc65 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -92,6 +92,9 @@ config.assets.compile = false config.dependency_loading = true if $rails_rake_task + + # we want to be able to show mailer previews + config.action_mailer.show_previews = true # Compress json # config.middleware.use Rack::Deflater From e504c0ce671da452e42311fc91d15dfbbbfa9f87 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 12 Dec 2023 15:49:03 -0600 Subject: [PATCH 072/208] Correct name of spec --- spec/lib/pay_recurring_donation_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/pay_recurring_donation_spec.rb b/spec/lib/pay_recurring_donation_spec.rb index 2eca6e374..6a1be8d4b 100644 --- a/spec/lib/pay_recurring_donation_spec.rb +++ b/spec/lib/pay_recurring_donation_spec.rb @@ -13,7 +13,7 @@ end - describe '.with_donation' do + describe '.with_stripe' do include_context :shared_donation_charge_context around (:each) do |example| From 518c87a6cc969498ab19ef9b4e32cd949c115de2 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 13 Dec 2023 19:28:55 -0600 Subject: [PATCH 073/208] Add spec for nonprofit rd notification not sending --- spec/lib/pay_recurring_donation_spec.rb | 63 +++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/spec/lib/pay_recurring_donation_spec.rb b/spec/lib/pay_recurring_donation_spec.rb index 6a1be8d4b..047e0629c 100644 --- a/spec/lib/pay_recurring_donation_spec.rb +++ b/spec/lib/pay_recurring_donation_spec.rb @@ -74,6 +74,10 @@ PayRecurringDonation.with_stripe(recurring_donation.id, true) } + let!(:admin_user) do + create(:user, id: 540) + end + context 'result when fees covered' do it { expect(covered_result).to_not eq false @@ -144,6 +148,65 @@ ))) } end + + + context 'n_failures = 0 and failed again' do + + before(:each) do + recurring_donation.n_failures = 0 + recurring_donation.save! + StripeMockHelper.prepare_card_error(:card_declined) + end + + it 'sets n_failures to 1' do + + PayRecurringDonation.with_stripe(recurring_donation.id, true) + + recurring_donation.reload + + expect(recurring_donation.n_failures).to eq 1 + end + + it 'sends an email to the donor but not nonprofit' do + delayed_mailer = double(DonationMailer) + expect(DonationMailer).to receive(:delay).once.and_return(delayed_mailer) + + expect(delayed_mailer).to receive(:donor_failed_recurring_donation).with(recurring_donation.donation_id) + + expect(delayed_mailer).to_not receive(:nonprofit_failed_recurring_donation) + + PayRecurringDonation.with_stripe(recurring_donation.id, true) + end + end + + context 'n_failures = 2 and failed again' do + + before(:each) do + recurring_donation.n_failures = 2 + recurring_donation.save! + StripeMockHelper.prepare_card_error(:card_declined) + end + + it 'sets n_failures to 3' do + + PayRecurringDonation.with_stripe(recurring_donation.id, true) + + recurring_donation.reload + + expect(recurring_donation.n_failures).to eq 3 + end + + it 'sends an email to the nonprofit' do + delayed_mailer = double(DonationMailer) + allow(DonationMailer).to receive(:delay).and_return(delayed_mailer) + + expect(delayed_mailer).to receive(:donor_failed_recurring_donation).with(recurring_donation.donation_id) + + expect(delayed_mailer).to receive(:nonprofit_failed_recurring_donation).with(recurring_donation.donation_id) + + PayRecurringDonation.with_stripe(recurring_donation.id, true) + end + end end describe '.pay_all_due_with_stripe', :pending => true do From ab52640aa8b4f3b9beea679f294bb60d17e523dd Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 13 Dec 2023 19:47:19 -0600 Subject: [PATCH 074/208] Fix nonprofits not receiving recurring donation failure notifications --- app/legacy_lib/pay_recurring_donation.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/legacy_lib/pay_recurring_donation.rb b/app/legacy_lib/pay_recurring_donation.rb index fb4b60b13..1ee1d5cff 100644 --- a/app/legacy_lib/pay_recurring_donation.rb +++ b/app/legacy_lib/pay_recurring_donation.rb @@ -107,6 +107,7 @@ def self.with_stripe(rd_id, force_run=false) .where("id=$id", id: rd_id).returning('*') ).first DonationMailer.delay.donor_failed_recurring_donation(rd['donation_id']) + rd.reload if rd['n_failures'] >= 3 DonationMailer.delay.nonprofit_failed_recurring_donation(rd['donation_id']) end From a0e7fa16c93669849a10a057359e0aae49a75a4b Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 14 Dec 2023 10:32:12 -0600 Subject: [PATCH 075/208] Remove placeholder info about original payment for refunds, disputes and dispute reversals --- app/views/tax_mailer/_dispute_payment_table.html.erb | 6 ------ .../_dispute_reversal_payment_table.html.erb | 11 ----------- app/views/tax_mailer/_refund_payment_table.html.erb | 6 ------ 3 files changed, 23 deletions(-) diff --git a/app/views/tax_mailer/_dispute_payment_table.html.erb b/app/views/tax_mailer/_dispute_payment_table.html.erb index 18daeea26..849d71ecb 100644 --- a/app/views/tax_mailer/_dispute_payment_table.html.erb +++ b/app/views/tax_mailer/_dispute_payment_table.html.erb @@ -15,12 +15,6 @@ <%= payment.id %> - - - Information about original payment? - TODO - - diff --git a/app/views/tax_mailer/_dispute_reversal_payment_table.html.erb b/app/views/tax_mailer/_dispute_reversal_payment_table.html.erb index 39436aa09..2292cbd78 100644 --- a/app/views/tax_mailer/_dispute_reversal_payment_table.html.erb +++ b/app/views/tax_mailer/_dispute_reversal_payment_table.html.erb @@ -16,17 +16,6 @@ <%= payment.id %> - - - Information about original dispute? - TODO - - - - Information about original payment? - TODO - - diff --git a/app/views/tax_mailer/_refund_payment_table.html.erb b/app/views/tax_mailer/_refund_payment_table.html.erb index 2f0acf56f..836ce8c33 100644 --- a/app/views/tax_mailer/_refund_payment_table.html.erb +++ b/app/views/tax_mailer/_refund_payment_table.html.erb @@ -15,12 +15,6 @@ <%= payment.id %> - - - Information about original payment? - TODO - - From a2dbc4662f503352b3022b6169ecb959f5290978 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 14 Dec 2023 13:16:22 -0600 Subject: [PATCH 076/208] Remove EmailDraft --- app/models/email_draft.rb | 16 ---------------- 1 file changed, 16 deletions(-) delete mode 100644 app/models/email_draft.rb diff --git a/app/models/email_draft.rb b/app/models/email_draft.rb deleted file mode 100644 index cc4951b0c..000000000 --- a/app/models/email_draft.rb +++ /dev/null @@ -1,16 +0,0 @@ -# License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -class EmailDraft < ApplicationRecord - - attr_accessible \ - :nonprofit, :nonprofit_id, - :name, - :deleted, - :value, - :created_at - - belongs_to :nonprofit - - scope :not_deleted, ->{where(deleted: [nil,false])} - -end - From 3f3e249340adecefe6909a4b8724ce70a99c1a5c Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 14 Dec 2023 13:27:38 -0600 Subject: [PATCH 077/208] Use a common method in supporter interpolation dictionary --- app/legacy_lib/supporter_interpolation_dictionary.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/legacy_lib/supporter_interpolation_dictionary.rb b/app/legacy_lib/supporter_interpolation_dictionary.rb index 3e2c848ff..f8440eafd 100644 --- a/app/legacy_lib/supporter_interpolation_dictionary.rb +++ b/app/legacy_lib/supporter_interpolation_dictionary.rb @@ -6,8 +6,8 @@ class SupporterInterpolationDictionary < InterpolationDictionary def set_supporter(supporter) if supporter.is_a?(Supporter) && supporter&.name&.present? add_entry('NAME', supporter&.name&.strip) - if supporter.name&.strip.split(' ')[0].present? - add_entry('FIRSTNAME', supporter.name&.strip.split(' ')[0]) + if supporter.calculated_first_name.present? + add_entry('FIRSTNAME', supporter.calculated_first_name) end end end From b0c92aee5caaf8135694be6b391aa30004bd799d Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 15 Dec 2023 20:11:48 -0600 Subject: [PATCH 078/208] Add first name template support --- app/mailers/tax_mailer.rb | 6 +++++- app/views/tax_mailer/annual_receipt.html.erb | 6 ++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/mailers/tax_mailer.rb b/app/mailers/tax_mailer.rb index d012d6f9c..59eee07bc 100644 --- a/app/mailers/tax_mailer.rb +++ b/app/mailers/tax_mailer.rb @@ -18,7 +18,11 @@ def annual_receipt(supporter:, year:, nonprofit_text:, donation_payments: [], re @dispute_payments = dispute_payments.sort_by(&:date) @dispute_reversal_payments = dispute_reversal_payments.sort_by(&:date) @tax_id = supporter.nonprofit.ein - @nonprofit_text = nonprofit_text + + dict = SupporterInterpolationDictionary.new('NAME'=> 'Supporter', 'FIRSTNAME' => 'Supporter') + dict.set_supporter(supporter) + + @nonprofit_text = dict.interpolate(nonprofit_text) mail(to: @supporter.email, subject: "#{@year} Tax Receipt from #{@nonprofit.name}") end diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index b14ccffc7..1aa8a9ab9 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -1,13 +1,11 @@ <%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> -

- Dear <%= @supporter.name %>, -

<%= @nonprofit_text.html_safe %> +

<%= @nonprofit.name %> is a 501c3 tax-exempt organization.

- +

<%= @year %> Donation Receipt

From 065eede3e74ac6c8ab4bb63afae50f33766ac7e8 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 20 Dec 2023 14:58:11 -0600 Subject: [PATCH 079/208] Try postgres 13 on build --- .github/workflows/build.yml | 2 +- .github/workflows/deploy.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4b8e7821..73dd818b8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: - name: Setup PostgreSQL with PostgreSQL extensions and unprivileged user uses: Daniel-Marynicz/postgresql-action@1.0.0 with: - postgres_image_tag: 12-alpine + postgres_image_tag: 13-alpine postgres_user: admin postgres_password: password - uses: actions/setup-node@v4 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3fc1e0a78..4929eaade 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -37,7 +37,7 @@ jobs: - name: Setup PostgreSQL with PostgreSQL extensions and unprivileged user uses: Daniel-Marynicz/postgresql-action@1.0.0 with: - postgres_image_tag: 12-alpine + postgres_image_tag: 13-alpine postgres_user: admin postgres_password: password - uses: actions/setup-node@v4 From f8e4079a2875a5af2a94f0d4c41ced6a6bbb90d1 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 20 Dec 2023 15:04:03 -0600 Subject: [PATCH 080/208] Tweak a file to force the ruby specs to run --- app/legacy_lib/fetch_campaign.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/legacy_lib/fetch_campaign.rb b/app/legacy_lib/fetch_campaign.rb index 52bd775e0..62c0a2e19 100644 --- a/app/legacy_lib/fetch_campaign.rb +++ b/app/legacy_lib/fetch_campaign.rb @@ -12,3 +12,4 @@ def self.with_params(params, nonprofit=nil) end + From 67d48f88f6e38d417f3fc9b9f1f36880ea4f6d07 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 8 Jan 2024 10:36:52 -0600 Subject: [PATCH 081/208] Update Puma for GHSA-c2f4-cvqm-65w2 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 5662c55c7..4765d9cf2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -329,7 +329,7 @@ GEM net-http-persistent (3.1.0) connection_pool (~> 2.2) netrc (0.11.0) - nio4r (2.5.9) + nio4r (2.7.0) orm_adapter (0.5.0) pg (0.21.0) power_assert (1.1.1) @@ -349,7 +349,7 @@ GEM byebug (~> 11.0) pry (~> 0.13.0) public_suffix (4.0.6) - puma (5.6.7) + puma (5.6.8) nio4r (~> 2.0) puma_worker_killer (0.3.1) get_process_mem (~> 0.2) From c5c8c9f0dc7ae1245da334e0325df94c79a36c2b Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 11 Jan 2024 14:29:10 -0600 Subject: [PATCH 082/208] Remove unneeded tax receipt details --- app/views/tax_mailer/annual_receipt.html.erb | 44 +++++++++++--------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index 1aa8a9ab9..9ce4a9488 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -29,32 +29,38 @@ -<% if @donation_payments.any? %> -

Donations

- <% @donation_payments.each do |payment| %> - <%= render "tax_mailer/donation_payment_table", payment: payment %> +<% +# just ignore and don't handle these + if false +%> + <% if @donation_payments.any? %> +

Donations

+ <% @donation_payments.each do |payment| %> + <%= render "tax_mailer/donation_payment_table", payment: payment %> + <% end %> <% end %> -<% end %> -<% if @refund_payments.any? %> -

Refunds

- <% @refund_payments.each do |payment| %> - <%= render "tax_mailer/refund_payment_table", payment: payment %> + <% if @refund_payments.any? %> +

Refunds

+ <% @refund_payments.each do |payment| %> + <%= render "tax_mailer/refund_payment_table", payment: payment %> + <% end %> <% end %> -<% end %> -<% if @dispute_payments.any? %> -

Disputed Payments

- <% @dispute_payments.each do |payment| %> - <%= render "tax_mailer/dispute_payment_table", payment: payment %> + <% if @dispute_payments.any? %> +

Disputed Payments

+ <% @dispute_payments.each do |payment| %> + <%= render "tax_mailer/dispute_payment_table", payment: payment %> + <% end %> <% end %> -<% end %> -<% if @dispute_reversal_payments.any? %> -

Dispute reversal payments

- <% @dispute_reversal_payments.each do |payment| %> - <%= render "tax_mailer/dispute_reversal_payment_table", payment: payment %> + <% if @dispute_reversal_payments.any? %> +

Dispute reversal payments

+ <% @dispute_reversal_payments.each do |payment| %> + <%= render "tax_mailer/dispute_reversal_payment_table", payment: payment %> + <% end %> <% end %> + <% end %> From 194725413110a68312d045d395f3256c40e50301 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 12 Jan 2024 11:10:54 -0600 Subject: [PATCH 083/208] Add #dupes_on_last_name_and_address --- app/legacy_lib/query_supporters.rb | 17 +++++++++++++++++ app/models/nonprofit.rb | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 0d7290cad..1ba6ff56a 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -610,6 +610,19 @@ def self.dupes_on_name_and_address(np_id, strict_mode = true) .map { |arr_group| arr_group.flatten.sort } end + def self.dupes_on_last_name_and_address(np_id) + dupes_expr(np_id) + .and_where( + "name IS NOT NULL\ + AND name != ''\ + AND address IS NOT NULL \ + AND address != ''" + ) + .group_by(self.calculated_last_name + " || '_____' || address") + .execute(format: 'csv')[1..-1] + .map { |arr_group| arr_group.flatten.sort } + end + def self.dupes_on_phone_and_email(np_id, strict_mode = true) group_by_clause = [(strict_mode ? strict_email_match : loose_email_match), "phone_index"].join(', ') dupes_expr(np_id) @@ -665,6 +678,10 @@ def self.loose_name_match "regexp_replace (lower(name),'[^0-9a-z]','','g')" end + def self.calculated_last_name + "substring(trim(both from supporters.name) from '^.+ ([^\s]+)$')" + end + # Create an export that lists donors with their total contributed amounts # Underneath each donor, we separately list each individual payment # Only including payments for the given year diff --git a/app/models/nonprofit.rb b/app/models/nonprofit.rb index e27c9141b..fad2dcc90 100755 --- a/app/models/nonprofit.rb +++ b/app/models/nonprofit.rb @@ -116,6 +116,14 @@ def dupes_on_phone_and_email(strict_mode = true) def dupes_on_address_without_zip_code(strict_mode = true) QuerySupporters.dupes_on_address_without_zip_code(proxy_association.owner.id, strict_mode) end + + def dupes_on_last_name_and_address + QuerySupporters.dupes_on_last_name_and_address(proxy_association.owner.id) + end + + def for_export_enumerable(query, chunk_limit=15000) + QuerySupporters.for_export_enumerable(proxy_association.owner.id, query, chunk_limit) + end end has_many :supporter_notes, through: :supporters has_many :profiles, through: :donations From e7f321c6d0b0ec32624cc43f1207e2e9ddabbe61 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 12 Jan 2024 13:05:27 -0600 Subject: [PATCH 084/208] Add a normal-margin paragraphs in emails --- app/assets/stylesheets/emails.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/stylesheets/emails.css b/app/assets/stylesheets/emails.css index 8a54e0228..8cc3f8656 100644 --- a/app/assets/stylesheets/emails.css +++ b/app/assets/stylesheets/emails.css @@ -178,3 +178,8 @@ img.fr-fin { overflow: auto; padding: 10px 0; } + + +p.normal-margin { + margin-bottom: 1em +} From 05a4c0cfa157832162fdd78c0faa805d34f771c5 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 18 Jan 2024 11:28:44 -0600 Subject: [PATCH 085/208] Add a normal line height class for emails --- app/assets/stylesheets/emails.css | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/emails.css b/app/assets/stylesheets/emails.css index 8cc3f8656..2248fcab8 100644 --- a/app/assets/stylesheets/emails.css +++ b/app/assets/stylesheets/emails.css @@ -183,3 +183,7 @@ img.fr-fin { p.normal-margin { margin-bottom: 1em } + +p.normal-line-height { + line-height: 1; +} From 61ee57bfe0fffaa945b36b65e4b6d3a837c9c699 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 18 Jan 2024 11:28:56 -0600 Subject: [PATCH 086/208] Add the duplicate language to receipts --- app/views/tax_mailer/annual_receipt.html.erb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/views/tax_mailer/annual_receipt.html.erb b/app/views/tax_mailer/annual_receipt.html.erb index 9ce4a9488..9c56d02af 100644 --- a/app/views/tax_mailer/annual_receipt.html.erb +++ b/app/views/tax_mailer/annual_receipt.html.erb @@ -16,7 +16,7 @@ Total Amount - $<%= Format::Currency.cents_to_dollars(@total) %> + $<%= Format::Currency.cents_to_dollars(@total) %>* Organization @@ -64,3 +64,6 @@ <% end %> <% end %> + +
+

*This receipt reflects all payments attached to your Supporter record in CommitChange. In the event you receive multiple receipts, your amount donated for the year will be the sum of all totals.

From 9cccc026059d8e48cc9d1b4b32fee37b16916f26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Jan 2024 02:08:07 +0000 Subject: [PATCH 087/208] Bump dorny/paths-filter from 2 to 3 Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 2 to 3. - [Release notes](https://github.com/dorny/paths-filter/releases) - [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md) - [Commits](https://github.com/dorny/paths-filter/compare/v2...v3) --- updated-dependencies: - dependency-name: dorny/paths-filter dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f4b8e7821..4d2080d77 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: fail-fast: false steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: changes with: filters: | From f1a5af4724fc6a33f9e7b1ff782fc34d4d58fc41 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 29 Jan 2024 19:46:28 -0600 Subject: [PATCH 088/208] Update Stripe account controller for the new stripe policies --- app/controllers/nonprofits/stripe_accounts_controller.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/controllers/nonprofits/stripe_accounts_controller.rb b/app/controllers/nonprofits/stripe_accounts_controller.rb index 86515d5c2..0c7805971 100644 --- a/app/controllers/nonprofits/stripe_accounts_controller.rb +++ b/app/controllers/nonprofits/stripe_accounts_controller.rb @@ -60,7 +60,10 @@ def account_link refresh_url: nonprofits_stripe_account_url(current_nonprofit.id, {return_location: params[:return_location]}), return_url: confirm_nonprofits_stripe_account_url(current_nonprofit.id, {return_location: params[:return_location]}), type: 'account_onboarding', - collect: 'eventually_due' + collection_options: { + fields: 'eventually_due', + future_requirements: 'include', + } }).to_json, status: 200 else render json:{error: "No Stripe account could be found or created. Please contact support@commitchange.com for assistance."}, status: 400 From 381744eca560355a9f9027cb3832de0bf4769b99 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 29 Jan 2024 19:58:10 -0600 Subject: [PATCH 089/208] Clarify the payments.date references in #during_np_year --- app/models/nonprofit.rb | 2 +- app/models/supporter.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/nonprofit.rb b/app/models/nonprofit.rb index fad2dcc90..353d35828 100755 --- a/app/models/nonprofit.rb +++ b/app/models/nonprofit.rb @@ -69,7 +69,7 @@ def pending_totals def during_np_year(year) proxy_association.owner.use_zone do - where('date >= ? and date < ?', Time.zone.local(year), Time.zone.local(year + 1)) + where('payments.date >= ? and payments.date < ?', Time.zone.local(year), Time.zone.local(year + 1)) end end diff --git a/app/models/supporter.rb b/app/models/supporter.rb index 96272e67d..32a52e91a 100644 --- a/app/models/supporter.rb +++ b/app/models/supporter.rb @@ -54,7 +54,7 @@ class Supporter < ApplicationRecord has_many :payments do def during_np_year(year) proxy_association.owner.nonprofit.use_zone do - where('date >= ? and date < ?', Time.zone.local(year), Time.zone.local(year + 1)) + where('payments.date >= ? and payments.date < ?', Time.zone.local(year), Time.zone.local(year + 1)) end end From bbf05e060f77d69e22ccff95aa9d8e37d7f55fe8 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 18 Jan 2024 11:07:31 -0600 Subject: [PATCH 090/208] Add manual cover fees to nonprofit pages --- app/models/event.rb | 9 +++++++++ app/views/campaigns/show.html.erb | 6 +++--- app/views/events/show.html.erb | 9 +++++---- app/views/nonprofits/donate.html.erb | 3 ++- app/views/nonprofits/show.html.erb | 3 ++- app/views/recurring_donations/edit.html.erb | 3 ++- client/js/nonprofits/donate/page.js | 1 + client/js/nonprofits/donate/wizard.d.ts | 1 + client/js/nonprofits/show/page.js | 2 +- 9 files changed, 26 insertions(+), 11 deletions(-) diff --git a/app/models/event.rb b/app/models/event.rb index c67fd51bc..9cb9762b6 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -106,6 +106,15 @@ def hide_cover_fees? nonprofit.hide_cover_fees? || misc_event_info&.hide_cover_fees_option end + def fee_coverage_option + @fee_coverage_option ||= misc_event_info&.fee_coverage_option_config || nonprofit.fee_coverage_option + end + + # generally, don't use + def fee_coverage_option=(option) + @fee_coverage_option = option + end + def get_tickets_button_label misc_event_info&.custom_get_tickets_button_label || 'Get Tickets' end diff --git a/app/views/campaigns/show.html.erb b/app/views/campaigns/show.html.erb index 5ff147b15..522dac0b1 100644 --- a/app/views/campaigns/show.html.erb +++ b/app/views/campaigns/show.html.erb @@ -32,9 +32,9 @@ <% end %> - app.manual_cover_fees = <%= @campaign&.misc_campaign_info&.manual_cover_fees || false %>; - app.hide_cover_fees_option = <%= !!@campaign.hide_cover_fees? %> - appl.hide_cover_fees_option = <%= !!@campaign.hide_cover_fees? %> + app.manual_cover_fees = <%= @campaign.fee_coverage_option == 'manual' %>; + app.hide_cover_fees_option = <%= @campaign.fee_coverage_option == 'none' %>; + appl.hide_cover_fees_option = <%= @campaign.fee_coverage_option == 'none' %> if (app.campaign) { app.campaign.paused = <%= @campaign.paused? || false %> diff --git a/app/views/events/show.html.erb b/app/views/events/show.html.erb index 9152cd720..283aa668a 100644 --- a/app/views/events/show.html.erb +++ b/app/views/events/show.html.erb @@ -61,8 +61,9 @@ disableLink: true, }) ticketPaymentCard.mount("#ticketPaymentCard") - appl.hide_cover_fees_option = <%= !!@event.hide_cover_fees? %>; - app.hide_cover_fees_option = <%= !!@event.hide_cover_fees? %>; + app.manual_cover_fees = <%= @event.fee_coverage_option == 'manual' %>; + appl.hide_cover_fees_option = <%= @event.fee_coverage_option == 'none' %>; + app.hide_cover_fees_option = <%= @event.fee_coverage_option == 'none' %>; <%= IncludeAsset.js '/client/js/events/show/page.js' %> <%= IncludeAsset.js '/client/js/nonprofits/donate/page.js' %> <% end %> diff --git a/app/views/nonprofits/show.html.erb b/app/views/nonprofits/show.html.erb index 42e33faa4..f9c1a9aac 100755 --- a/app/views/nonprofits/show.html.erb +++ b/app/views/nonprofits/show.html.erb @@ -42,7 +42,8 @@ app.nonprofit = app.nonprofit || {} app.nonprofit.feeStructure = <%= raw @nonprofit.fee_coverage_details_with_json_safe_keys.to_json %> - app.hide_cover_fees_option = <%= !!@nonprofit&.hide_cover_fees? %>; + app.manual_cover_fees = <%= @nonprofit.fee_coverage_option == 'manual' %>; + app.hide_cover_fees_option = <%= @nonprofit.fee_coverage_option == 'none' %>; <%= render 'schema', npo: @nonprofit %> <%= render 'common/froala' if current_nonprofit_user? %> diff --git a/app/views/recurring_donations/edit.html.erb b/app/views/recurring_donations/edit.html.erb index d9bd438f8..8e96d8365 100644 --- a/app/views/recurring_donations/edit.html.erb +++ b/app/views/recurring_donations/edit.html.erb @@ -9,7 +9,8 @@ app.nonprofit.feeStructure = <%= raw(@nonprofit.fee_coverage_details_with_json_safe_keys.to_json) %> app.nonprofit.email = '<%= @data['nonprofit'].email.blank? ? @data['nonprofit'].users.first.email : @data['nonprofit'].email %>' app.pageLoadData = <%= raw @data.to_json %> - app.hide_cover_fees_option = <%= !!@data['nonprofit'].hide_cover_fees? %> + app.manual_cover_fees = <%= @data['nonprofit'].fee_coverage_option == 'manual' %> + app.hide_cover_fees_option = <%= @data['nonprofit'].fee_coverage_option == 'none' %> <%= IncludeAsset.js '/client/js/recurring_donations/edit/page.js' %> <% end %> diff --git a/client/js/nonprofits/donate/page.js b/client/js/nonprofits/donate/page.js index 49b0ca6d3..cc7ec8356 100644 --- a/client/js/nonprofits/donate/page.js +++ b/client/js/nonprofits/donate/page.js @@ -18,6 +18,7 @@ const patch = snabbdom.init([ const params = url.parse(location.href, true).query params.hide_cover_fees_option = params.hide_cover_fees_option || app.hide_cover_fees_option +params.manual_cover_fees = params.manual_cover_fees || app.manual_cover_fees const params$ = flyd.stream(params) app.params$ = params$ if(params.campaign_id && params.gift_option_id) { diff --git a/client/js/nonprofits/donate/wizard.d.ts b/client/js/nonprofits/donate/wizard.d.ts index e96383adb..f82cee43f 100644 --- a/client/js/nonprofits/donate/wizard.d.ts +++ b/client/js/nonprofits/donate/wizard.d.ts @@ -18,6 +18,7 @@ interface InitState { clickLogout$:() => void; clickFinish$:() => void; hide_cover_fees_option: boolean | undefined ; + manual_cover_fees: boolean | undefined; hide_anonymous: boolean| undefined; selected_payment$: (payment:string) => string; amountStep: ReturnType diff --git a/client/js/nonprofits/show/page.js b/client/js/nonprofits/show/page.js index 2fe4ddc59..6f9a5eb57 100755 --- a/client/js/nonprofits/show/page.js +++ b/client/js/nonprofits/show/page.js @@ -47,7 +47,7 @@ const branding = require('../../components/nonprofit-branding') function init() { var state = {} - state.donateWiz = donateWiz.init(flyd.stream({hide_cover_fees_option: app.hide_cover_fees_option})) + state.donateWiz = donateWiz.init(flyd.stream({hide_cover_fees_option: app.hide_cover_fees_option, manual_cover_fees: app.manual_cover_fees})) state.modalID$ = flyd.stream() return state } From 31ab365d5db14171f986277237f1b4b1595a3422 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 5 Feb 2024 20:57:18 -0600 Subject: [PATCH 091/208] Properly stub const in specs --- spec/requests/cards_spec.rb | 2 +- spec/requests/nonprofits/supporters_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/cards_spec.rb b/spec/requests/cards_spec.rb index 5b7944770..b8d631a48 100644 --- a/spec/requests/cards_spec.rb +++ b/spec/requests/cards_spec.rb @@ -4,7 +4,7 @@ describe CardsController, type: :request do describe 'throttling' do before(:each) do - FORCE_THROTTLE = true + stub_const(FORCE_THROTTLE, true) end it 'test number of card throttle' do 6.times { diff --git a/spec/requests/nonprofits/supporters_spec.rb b/spec/requests/nonprofits/supporters_spec.rb index 23b1d8544..14e8f82d2 100644 --- a/spec/requests/nonprofits/supporters_spec.rb +++ b/spec/requests/nonprofits/supporters_spec.rb @@ -6,7 +6,7 @@ describe 'throttling' do before(:each) do - FORCE_THROTTLE = true + stub_const(FORCE_THROTTLE, true) end it 'test number of supporter throttle' do 11.times { From c9d593f86463444714e8b018f9ffa36d911a6c7c Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 6 Feb 2024 09:54:45 -0600 Subject: [PATCH 092/208] Correctly correct the const warnings --- spec/requests/cards_spec.rb | 2 +- spec/requests/nonprofits/supporters_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/cards_spec.rb b/spec/requests/cards_spec.rb index b8d631a48..dec4542e6 100644 --- a/spec/requests/cards_spec.rb +++ b/spec/requests/cards_spec.rb @@ -4,7 +4,7 @@ describe CardsController, type: :request do describe 'throttling' do before(:each) do - stub_const(FORCE_THROTTLE, true) + stub_const('FORCE_THROTTLE', true) end it 'test number of card throttle' do 6.times { diff --git a/spec/requests/nonprofits/supporters_spec.rb b/spec/requests/nonprofits/supporters_spec.rb index 14e8f82d2..4c1f7081a 100644 --- a/spec/requests/nonprofits/supporters_spec.rb +++ b/spec/requests/nonprofits/supporters_spec.rb @@ -6,7 +6,7 @@ describe 'throttling' do before(:each) do - stub_const(FORCE_THROTTLE, true) + stub_const('FORCE_THROTTLE', true) end it 'test number of supporter throttle' do 11.times { From 039cf9a08c30b22c19583dffa33503e7cb9c37e1 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 8 Feb 2024 19:13:43 -0600 Subject: [PATCH 093/208] Add Fee Coverage Option Config to Event --- ...057_add_fee_option_cover_option_config_to_event_misc.rb | 5 +++++ db/schema.rb | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20240209011057_add_fee_option_cover_option_config_to_event_misc.rb diff --git a/db/migrate/20240209011057_add_fee_option_cover_option_config_to_event_misc.rb b/db/migrate/20240209011057_add_fee_option_cover_option_config_to_event_misc.rb new file mode 100644 index 000000000..2fcc5e97a --- /dev/null +++ b/db/migrate/20240209011057_add_fee_option_cover_option_config_to_event_misc.rb @@ -0,0 +1,5 @@ +class AddFeeOptionCoverOptionConfigToEventMisc < ActiveRecord::Migration + def change + add_column :misc_event_infos, :fee_coverage_option_config, :string, default: nil, nullable: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 6e04a3b92..767a31d7b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20231201210543) do +ActiveRecord::Schema.define(version: 20240209011057) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -512,6 +512,10 @@ add_index "full_contact_infos", ["supporter_id"], name: "index_full_contact_infos_on_supporter_id", using: :btree + create_table "full_contact_jobs", force: :cascade do |t| + t.integer "supporter_id" + end + create_table "full_contact_orgs", force: :cascade do |t| t.boolean "is_primary" t.string "name", limit: 255 @@ -618,6 +622,7 @@ t.datetime "created_at", null: false t.datetime "updated_at", null: false t.string "custom_get_tickets_button_label" + t.string "fee_coverage_option_config" end create_table "misc_payment_infos", force: :cascade do |t| From 6871b31a3e6fc6789c251bd17572c3f4bcbd0784 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 11 Feb 2024 11:20:57 -0600 Subject: [PATCH 094/208] Correct host for mailchimp RD URLs --- app/views/mailchimp/list.json.jbuilder | 2 +- spec/lib/mailchimp_spec.rb | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/views/mailchimp/list.json.jbuilder b/app/views/mailchimp/list.json.jbuilder index fad14c135..b9284aac3 100644 --- a/app/views/mailchimp/list.json.jbuilder +++ b/app/views/mailchimp/list.json.jbuilder @@ -8,6 +8,6 @@ json.merge_fields do @supporter.recurring_donations.active.order('start_date DESC').each_with_index do |item, i| json.set! "RD_URL_#{i+1}", # we use i+1 because we want this to start at RD_URL_1 - edit_recurring_donation_url(id: item.id, t: item.edit_token) + edit_recurring_donation_url(id: item.id, t: item.edit_token, host: "us.commitchange.com") end end \ No newline at end of file diff --git a/spec/lib/mailchimp_spec.rb b/spec/lib/mailchimp_spec.rb index 0392cbd11..265768da3 100644 --- a/spec/lib/mailchimp_spec.rb +++ b/spec/lib/mailchimp_spec.rb @@ -165,7 +165,7 @@ 'merge_fields' => { 'F_NAME' => "Penelope Rebecca", 'L_NAME' => "Schultz", - 'RD_URL_1' => an_instance_of(String).and(ending_with("recurring_donations/#{active_recurring_donation_1.id}/edit?t=#{active_recurring_donation_1.edit_token}")), + 'RD_URL_1' => "http://us.commitchange.com/recurring_donations/#{active_recurring_donation_1.id}/edit?t=#{active_recurring_donation_1.edit_token}", } }) @@ -181,9 +181,8 @@ 'merge_fields' => { 'F_NAME' => "Penelope Rebecca", 'L_NAME' => "Schultz", - 'RD_URL_1' => an_instance_of(String).and(ending_with("recurring_donations/#{active_recurring_donation_2.id}/edit?t=#{active_recurring_donation_2.edit_token}")), - 'RD_URL_2' => an_instance_of(String).and(ending_with("recurring_donations/#{active_recurring_donation_1.id}/edit?t=#{active_recurring_donation_1.edit_token}")) - } + 'RD_URL_1' => "http://us.commitchange.com/recurring_donations/#{active_recurring_donation_2.id}/edit?t=#{active_recurring_donation_2.edit_token}", + 'RD_URL_2' => "http://us.commitchange.com/recurring_donations/#{active_recurring_donation_1.id}/edit?t=#{active_recurring_donation_1.edit_token}" } }) end From 9e5dd8441638a5b144f57cc3a41f9a1fdb130c52 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 26 Feb 2024 16:01:45 -0600 Subject: [PATCH 095/208] Fix issues around hide anonymous --- client/js/nonprofits/donate/info-step.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/js/nonprofits/donate/info-step.js b/client/js/nonprofits/donate/info-step.js index b9fbeab18..3403d373b 100644 --- a/client/js/nonprofits/donate/info-step.js +++ b/client/js/nonprofits/donate/info-step.js @@ -24,6 +24,7 @@ function init(donation$, parentState) { , currentStep$: flyd.stream() , selectedPayment$: parentState.selectedPayment$ , donationAmount$: parentState.donationAmount$ + , hide_anonymous: parentState.hide_anonymous } // Save supporter for dedication logic @@ -138,7 +139,7 @@ function paymentButton(options, label, state){ } function anonField(state) { - if (state.params$().hide_anonymous) return ''; + if (state.hide_anonymous) return ''; state.anon_id = state.anon_id || uuid.v1() // we need a unique id in case there are multiple supporter forms on the page -- the label 'for' attribute needs to be unique return h('div.u-marginTop--10.u-centered', [ From a966182893245ebba0066a7a7ead1ccb62a8e6a2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 26 Feb 2024 23:32:22 +0000 Subject: [PATCH 096/208] Bump es5-ext from 0.10.42 to 0.10.63 Bumps [es5-ext](https://github.com/medikoo/es5-ext) from 0.10.42 to 0.10.63. - [Release notes](https://github.com/medikoo/es5-ext/releases) - [Changelog](https://github.com/medikoo/es5-ext/blob/main/CHANGELOG.md) - [Commits](https://github.com/medikoo/es5-ext/compare/v0.10.42...v0.10.63) --- updated-dependencies: - dependency-name: es5-ext dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 72 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 14 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5a4d165b1..a97e146d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3214,6 +3214,14 @@ d@1: dependencies: es5-ext "^0.10.9" +d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -3671,16 +3679,17 @@ es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14: - version "0.10.42" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.42.tgz#8c07dd33af04d5dcd1310b5cef13bea63a89ba8d" - integrity sha512-AJxO1rmPe1bDEfSR6TJ/FgMFYuTBhR5R57KW58iCkYACMyFbrkqVyzXSurYoScDGvgyMpk7uRF/lPUPPTmsRSA== +es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.50, es5-ext@^0.10.62, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.63" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.63.tgz#9c222a63b6a332ac80b1e373b426af723b895bd6" + integrity sha512-hUCZd2Byj/mNKjfP9jXrdVZ62B8KuA/VoK7X8nUh5qT+AxDmcbvZz041oDVZdbIN1qW6XY9VDNwzkvKnZvK2TQ== dependencies: - es6-iterator "~2.0.3" - es6-symbol "~3.1.1" - next-tick "1" + es6-iterator "^2.0.3" + es6-symbol "^3.1.3" + esniff "^2.0.1" + next-tick "^1.1.0" -es6-iterator@^2.0.1, es6-iterator@~2.0.1, es6-iterator@~2.0.3: +es6-iterator@^2.0.1, es6-iterator@^2.0.3, es6-iterator@~2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g== @@ -3725,6 +3734,14 @@ es6-symbol@3.1.1, es6-symbol@^3.1.1, es6-symbol@~3.1.1: d "1" es5-ext "~0.10.14" +es6-symbol@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + es6-weak-map@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" @@ -3777,6 +3794,16 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + esprima@^2.6.0: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -3821,7 +3848,7 @@ ev-store@^7.0.0: dependencies: individual "^3.0.0" -event-emitter@~0.3.5: +event-emitter@^0.3.5, event-emitter@~0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" integrity sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA== @@ -3931,6 +3958,13 @@ exports-loader@^0.6.3: loader-utils "^1.0.2" source-map "0.5.x" +ext@^1.1.2: + version "1.7.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.7.0.tgz#0ea4383c0103d60e70be99e9a7f11027a33c4f5f" + integrity sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw== + dependencies: + type "^2.7.2" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -6506,16 +6540,16 @@ neo-async@^2.5.0: resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.1.tgz#acb909e327b1e87ec9ef15f41b8a269512ad41ee" integrity sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA== -next-tick@1: - version "1.0.0" - resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" - integrity sha512-mc/caHeUcdjnC/boPWJefDr4KUIWQNv+tlnFnJd38QMou86QtxQzBJfxgGRzvx8jazYRqrVlaHarfO72uNxPOg== - next-tick@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-0.2.2.tgz#75da4a927ee5887e39065880065b7336413b310d" integrity sha512-f7h4svPtl+QidoBv4taKXUjJ70G2asaZ8G28nS0OkqaalX8dwwrtWtyxEDPK62AC00ur/+/E0pUwBwY5EPn15Q== +next-tick@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.1.0.tgz#1836ee30ad56d67ef281b22bd199f709449b35eb" + integrity sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -9750,6 +9784,16 @@ type-fest@^1.2.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.7.2: + version "2.7.2" + resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" + integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== + typedarray-to-buffer@^3.1.5: version "3.1.5" resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" From f546f40faa12bb6084ec02d93c99411ddfe12b75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:06:36 +0000 Subject: [PATCH 097/208] Bump yard from 0.9.27 to 0.9.35 Bumps [yard](https://github.com/lsegal/yard) from 0.9.27 to 0.9.35. - [Release notes](https://github.com/lsegal/yard/releases) - [Changelog](https://github.com/lsegal/yard/blob/main/CHANGELOG.md) - [Commits](https://github.com/lsegal/yard/compare/v0.9.27...v0.9.35) --- updated-dependencies: - dependency-name: yard dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4765d9cf2..d28e4a4ea 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -506,9 +506,7 @@ GEM webmock (1.21.0) addressable (>= 2.3.6) crack (>= 0.3.2) - webrick (1.7.0) - yard (0.9.27) - webrick (~> 1.7.0) + yard (0.9.35) PLATFORMS ruby From 6d95bc657e508799a32858fb3ef8e72b0f654ae3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:16:21 +0000 Subject: [PATCH 098/208] Bump yard from 0.9.35 to 0.9.36 Bumps [yard](https://github.com/lsegal/yard) from 0.9.35 to 0.9.36. - [Release notes](https://github.com/lsegal/yard/releases) - [Changelog](https://github.com/lsegal/yard/blob/main/CHANGELOG.md) - [Commits](https://github.com/lsegal/yard/compare/v0.9.35...v0.9.36) --- updated-dependencies: - dependency-name: yard dependency-type: direct:development ... Signed-off-by: dependabot[bot] --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index d28e4a4ea..1716d71b9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -506,7 +506,7 @@ GEM webmock (1.21.0) addressable (>= 2.3.6) crack (>= 0.3.2) - yard (0.9.35) + yard (0.9.36) PLATFORMS ruby From 1f67bd53cdb91b9befa1d2539ed444dbc53be2c8 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 17 Mar 2024 15:32:08 -0500 Subject: [PATCH 099/208] Fix bug where donations paid without a name don't include the email in the subject line sent to nonprofit --- app/mailers/donation_mailer.rb | 2 +- spec/mailers/donation_mailer_spec.rb | 63 ++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/app/mailers/donation_mailer.rb b/app/mailers/donation_mailer.rb index 34b9b9cab..8f8c83127 100644 --- a/app/mailers/donation_mailer.rb +++ b/app/mailers/donation_mailer.rb @@ -65,7 +65,7 @@ def nonprofit_payment_notification(donation_id, payment_id, user_id=nil) @emails = [em] end if @emails.any? - mail(to: @emails, subject: "Donation receipt for #{@donation.supporter.name}") + mail(to: @emails, subject: "Donation receipt for #{@donation.supporter.name || @donation.supporter.email}") end end diff --git a/spec/mailers/donation_mailer_spec.rb b/spec/mailers/donation_mailer_spec.rb index 804540925..63c6be6cb 100644 --- a/spec/mailers/donation_mailer_spec.rb +++ b/spec/mailers/donation_mailer_spec.rb @@ -194,4 +194,67 @@ end end + + describe "#nonprofit_payment_notification" do + + context 'when supporter name is set' do + before(:each) { + expect(QueryUsers).to receive(:nonprofit_user_emails).and_return(['anything@example.com']) + } + + let(:supporter) { create(:supporter_base)} + let(:donation) { create(:donation_base, supporter: supporter, nonprofit: supporter.nonprofit)} + let(:payment) { create(:payment_base, donation: donation)} + + let!(:mail) { DonationMailer.nonprofit_payment_notification( + donation.id, + payment.id + ) } + + it 'uses the supporter name in subject' do + expect(mail.subject).to include supporter.name + end + end + + + context 'when supporter name is nil' do + before(:each) { + expect(QueryUsers).to receive(:nonprofit_user_emails).and_return(['anything@example.com']) + } + + let(:supporter) { create(:supporter_base, email: "supporter@example.com", name: nil)} + let(:donation) { create(:donation_base, supporter: supporter, nonprofit: supporter.nonprofit)} + let(:payment) { create(:payment_base, donation: donation)} + + let!(:mail) { DonationMailer.nonprofit_payment_notification( + donation.id, + payment.id + ) } + + it 'uses the supporter email instead of name in subject' do + expect(mail.subject).to include supporter.email + end + end + + context 'when supporter name is blank', pending: true do + before(:each) { + expect(QueryUsers).to receive(:nonprofit_user_emails).and_return(['anything@example.com']) + } + + let(:supporter) { create(:supporter_base, email: "supporter@example.com", name: "")} + let(:donation) { create(:donation_base, supporter: supporter, nonprofit: supporter.nonprofit)} + let(:payment) { create(:payment_base, donation: donation)} + + let!(:mail) { DonationMailer.nonprofit_payment_notification( + donation.id, + payment.id + ) } + + + it 'uses the supporter email instead of name in subject' do + expect(mail.subject).to include supporter.email + end + end + + end end \ No newline at end of file From 918c928ad93afdc3df523166a331e18bf2224cc4 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 17 Mar 2024 15:45:23 -0500 Subject: [PATCH 100/208] Test on Node 16 since we'll need to update to it --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index efa0f4500..bec7e315c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - node: [14.19.1] + node: [14.19.1, 16] ruby: ['2.6.10'] fail-fast: false steps: From b42d1414721e55a8b1a8cdf3db0162dfacdf7ad6 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 18 Mar 2024 15:00:35 -0500 Subject: [PATCH 101/208] Remove no longer needed message --- spec/lib/stripe_account_utils_spec.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spec/lib/stripe_account_utils_spec.rb b/spec/lib/stripe_account_utils_spec.rb index e169b313c..db26c78c4 100644 --- a/spec/lib/stripe_account_utils_spec.rb +++ b/spec/lib/stripe_account_utils_spec.rb @@ -76,15 +76,6 @@ {:key => :np, :name => :is_a}]) }) end - - it 'uses the redacted nonprofit info' do - StripeMockHelper.prepare_error(Stripe::InvalidRequestError.new("things are bad", ''), :new_account) - result = StripeAccountUtils.create(nonprofit_with_bad_values) - acct = Stripe::Account.retrieve(result) - expect(acct.company.address.to_h.has_key?(:zip_code)).to be_falsy - expect(acct.company.address.to_h.has_key?(:state_code)).to be_falsey - expect(acct.business_profile.to_h.has_key?(:url)).to be_falsey - end end describe 'testing with valid nonprofit' do From f290174c58760ce13c23eef736054f06861a4c42 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 4 Apr 2024 18:46:30 -0500 Subject: [PATCH 102/208] Add Stripe Verification link --- app/views/settings/_payouts.html.erb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/views/settings/_payouts.html.erb b/app/views/settings/_payouts.html.erb index 8ca042273..ba00a488e 100644 --- a/app/views/settings/_payouts.html.erb +++ b/app/views/settings/_payouts.html.erb @@ -20,6 +20,10 @@

View your payout history

+ +

+ Complete or update Stripe verification information +

<%= render 'nonprofits/bank_accounts/modal' %> From f4f9bb8a29390148db76357babab45cdd54fc4fc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 4 Apr 2024 18:49:09 -0500 Subject: [PATCH 103/208] Correct the Stripe verification link and wording --- app/views/settings/_payouts.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/settings/_payouts.html.erb b/app/views/settings/_payouts.html.erb index ba00a488e..568f052da 100644 --- a/app/views/settings/_payouts.html.erb +++ b/app/views/settings/_payouts.html.erb @@ -22,7 +22,7 @@

- Complete or update Stripe verification information + Complete Stripe verification or update verification info

From f07a9a26aba80b1e2d81fd46f8089b9166186645 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 4 Apr 2024 18:50:09 -0500 Subject: [PATCH 104/208] Adjust language again --- app/views/settings/_payouts.html.erb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/settings/_payouts.html.erb b/app/views/settings/_payouts.html.erb index 568f052da..d3198f4ae 100644 --- a/app/views/settings/_payouts.html.erb +++ b/app/views/settings/_payouts.html.erb @@ -22,7 +22,7 @@

- Complete Stripe verification or update verification info + Update your Stripe verification info

From 78fcbf2e2daae0b5484a87f2a5181cc55925964c Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 18 Jun 2024 13:16:03 -0500 Subject: [PATCH 105/208] Begin testing on PG 16 --- .github/workflows/build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bec7e315c..598e67966 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,6 +16,7 @@ jobs: os: [ubuntu-20.04] node: [14.19.1, 16] ruby: ['2.6.10'] + postgres: ['13', '16'] fail-fast: false steps: - uses: actions/checkout@v4 @@ -54,7 +55,7 @@ jobs: - name: Setup PostgreSQL with PostgreSQL extensions and unprivileged user uses: Daniel-Marynicz/postgresql-action@1.0.0 with: - postgres_image_tag: 13-alpine + postgres_image_tag: ${{ matrix.postgres }}-alpine postgres_user: admin postgres_password: password - uses: actions/setup-node@v4 From 0d42b8f8cbaa6dc1a52d3c67f36e0bbe8889c32d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:22:53 +0000 Subject: [PATCH 106/208] Bump ws from 7.5.9 to 7.5.10 Bumps [ws](https://github.com/websockets/ws) from 7.5.9 to 7.5.10. - [Release notes](https://github.com/websockets/ws/releases) - [Commits](https://github.com/websockets/ws/compare/7.5.9...7.5.10) --- updated-dependencies: - dependency-name: ws dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index a97e146d1..7622e5c44 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10326,9 +10326,9 @@ write-file-atomic@^3.0.0: typedarray-to-buffer "^3.1.5" ws@^7.4.4: - version "7.5.9" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" - integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== x-is-array@0.1.0: version "0.1.0" From b8ec21a166a05c8e27d9f65197e4dcb1e293f031 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 26 Jun 2024 15:04:07 -0500 Subject: [PATCH 107/208] Remove polyfill.io --- app/views/layouts/_javascripts.html.erb | 2 -- app/views/layouts/apified.html.erb | 1 - app/views/layouts/static.html.erb | 1 - 3 files changed, 4 deletions(-) diff --git a/app/views/layouts/_javascripts.html.erb b/app/views/layouts/_javascripts.html.erb index da8b441e1..4939e8312 100644 --- a/app/views/layouts/_javascripts.html.erb +++ b/app/views/layouts/_javascripts.html.erb @@ -1,6 +1,4 @@ <%- # License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later -%> - - <%= render 'layouts/app_data' %> diff --git a/app/views/layouts/apified.html.erb b/app/views/layouts/apified.html.erb index b5c4a7872..831b9ba37 100644 --- a/app/views/layouts/apified.html.erb +++ b/app/views/layouts/apified.html.erb @@ -6,7 +6,6 @@ <%= "#{yield(:title)} - #{Settings.general.name}" %> - <%= IncludeAsset.js '/client/js/i18n.js' %> <%= render 'layouts/stylesheets' %> <%= IncludeAsset.css '/client/css/global/page.css' %> <%= IncludeAsset.css '/client/css/bootstrap.css' %> From 75840e0e021dc5b49a21e9aac0ac693dbf8baf29 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 20 Aug 2024 16:34:22 -0500 Subject: [PATCH 108/208] Correct heroku download command --- script/restore_from_heroku.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/restore_from_heroku.sh b/script/restore_from_heroku.sh index ad6478549..dd02c4241 100755 --- a/script/restore_from_heroku.sh +++ b/script/restore_from_heroku.sh @@ -2,5 +2,5 @@ set -e -curl -o latest.dump `heroku pg:backups public-url -a commitchange` +curl -o latest.dump `heroku pg:backups:url -a commitchange` script/pg_restore_local_from_production.sh From b61a0776938854e54eec4c4675d44de08d323696 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 20 Aug 2024 19:48:48 -0500 Subject: [PATCH 109/208] Update the pg version for the deploy --- .github/workflows/deploy.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4929eaade..bef5f4fa2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -27,6 +27,7 @@ jobs: os: [ubuntu-20.04] node: [14.19.1] ruby: ['2.6.10'] + postgres: ['16'] fail-fast: false steps: - name: 'Checkout our repo' @@ -37,7 +38,7 @@ jobs: - name: Setup PostgreSQL with PostgreSQL extensions and unprivileged user uses: Daniel-Marynicz/postgresql-action@1.0.0 with: - postgres_image_tag: 13-alpine + postgres_image_tag: ${{ matrix.postgres }}-alpine postgres_user: admin postgres_password: password - uses: actions/setup-node@v4 From 7b5a676c71cb79a86bf534197aa4e4b1321fc208 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Aug 2024 00:52:00 +0000 Subject: [PATCH 110/208] Bump elliptic from 6.5.4 to 6.5.7 Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.4 to 6.5.7. - [Commits](https://github.com/indutny/elliptic/compare/v6.5.4...v6.5.7) --- updated-dependencies: - dependency-name: elliptic dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 7622e5c44..d85f18a22 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3523,9 +3523,9 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.45, electron-to-chromium@ integrity sha512-r20dUOtZ4vUPTqAajDGonIM1uas5tf85Up+wPdtNBNvBSqGCfkpvMVvQ1T8YJzPV9/Y9g3FbUDcXb94Rafycow== elliptic@^6.0.0, elliptic@^6.5.4: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + version "6.5.7" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" + integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== dependencies: bn.js "^4.11.9" brorand "^1.1.0" From 8d8a81c23e353c9317cdf68e38826369be778168 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 17:54:03 +0000 Subject: [PATCH 111/208] Bump path-to-regexp from 6.2.1 to 6.3.0 Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) from 6.2.1 to 6.3.0. - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v6.2.1...v6.3.0) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index d85f18a22..2a11a01d7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7041,9 +7041,9 @@ path-parse@^1.0.7: integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@^6.2.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.1.tgz#d54934d6798eb9e5ef14e7af7962c945906918e5" - integrity sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw== + version "6.3.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.3.0.tgz#2b6a26a337737a8e1416f9272ed0766b1c0389f4" + integrity sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ== path-type@^2.0.0: version "2.0.0" From 63fe1afc879df27e1d4d4fc90d7eedee875ea25f Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 17 Sep 2024 12:15:24 -0500 Subject: [PATCH 112/208] Use asdf to simplify things --- .tool-versions | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..dba7bbdd9 --- /dev/null +++ b/.tool-versions @@ -0,0 +1,2 @@ +ruby 2.6.10 +nodejs 14.19.1 From b35933c3a8f7730f20727573b455a91cbed10d8a Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 20 Sep 2024 15:10:32 -0500 Subject: [PATCH 113/208] Update puma to fix GHSA-9hf4-67fc-4vf4 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1716d71b9..5a29e372f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -349,7 +349,7 @@ GEM byebug (~> 11.0) pry (~> 0.13.0) public_suffix (4.0.6) - puma (5.6.8) + puma (5.6.9) nio4r (~> 2.0) puma_worker_killer (0.3.1) get_process_mem (~> 0.2) From c2e54ffd11f1dd6f13ad3e456d5e4810da612d5a Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 20 Sep 2024 15:19:32 -0500 Subject: [PATCH 114/208] Remove tests for postgres 13 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 598e67966..5c7c791ac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,7 +16,7 @@ jobs: os: [ubuntu-20.04] node: [14.19.1, 16] ruby: ['2.6.10'] - postgres: ['13', '16'] + postgres: ['16'] fail-fast: false steps: - uses: actions/checkout@v4 From 5520212015f2fdd57939d9cac446d9fbb0f17cc5 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 23 Sep 2024 13:24:07 -0500 Subject: [PATCH 115/208] Delayed Job run time is now 5 minutes --- config/initializers/delayed_job_config.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/initializers/delayed_job_config.rb b/config/initializers/delayed_job_config.rb index a65764d5c..7f3014799 100644 --- a/config/initializers/delayed_job_config.rb +++ b/config/initializers/delayed_job_config.rb @@ -2,3 +2,5 @@ Delayed::Worker.max_attempts = 1 Delayed::Worker.destroy_failed_jobs = false + +Delayed::Worker.max_run_time = 5.minutes From 348f446e5068a26ccee4ffdc470a2a1a0df97d31 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 23 Sep 2024 14:52:39 -0500 Subject: [PATCH 116/208] This should optimize a bit --- app/legacy_lib/query_supporters.rb | 36 +++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 1ba6ff56a..d4d08302e 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -160,6 +160,28 @@ def self._full_search(np_id, query) # return new_supporters # end + def self.undeleted_supporters(np_id) + + Qx.select('id') + .from(:supporters) + .where("supporters.nonprofit_id = $id and deleted != 'true'", id: np_id ) + end + + def self.tag_joins_only_for_nonprofit_query(np_id) + Qx.select("tag_joins.id, tag_joins.supporter_id, tag_joins.tag_master_id") + .from(:tag_joins) + .join(undeleted_supporters(np_id).as('tags_to_supporters'), "tags_to_supporters.id = tag_joins.supporter_id") + end + + def self.build_tag_query(np_id) + + tags_query = Qx.select("tag_joins.supporter_id", "ARRAY_AGG(tag_masters.id) AS ids", "ARRAY_AGG(tag_masters.name::text) AS names") + .from(tag_joins_only_for_nonprofit_query(np_id).as(:tag_joins)) + .join(:tag_masters, "tag_masters.id=tag_joins.tag_master_id") + .where("tag_masters.nonprofit_id = $id AND NOT tag_masters.deleted ", id: np_id.to_i) + .group_by("tag_joins.supporter_id") + .as(:tags) + end # Perform all filters and search for /nonprofits/id/supporters dashboard and export def self.full_filter_expr(np_id, query) @@ -178,12 +200,14 @@ def self.full_filter_expr(np_id, query) .group_by(:supporter_id) .as(:payments) - tags_subquery = Qx.select("tag_joins.supporter_id", "ARRAY_AGG(tag_masters.id) AS ids", "ARRAY_AGG(tag_masters.name::text) AS names") - .from(:tag_joins) - .join(:tag_masters, "tag_masters.id=tag_joins.tag_master_id") - .where("tag_masters.nonprofit_id = $id AND NOT tag_masters.deleted", id: np_id.to_i) - .group_by("tag_joins.supporter_id") - .as(:tags) + # tags_subquery = Qx.select("tag_joins.supporter_id", "ARRAY_AGG(tag_masters.id) AS ids", "ARRAY_AGG(tag_masters.name::text) AS names") + # .from(:tag_joins) + # .join(:tag_masters, "tag_masters.id=tag_joins.tag_master_id") + # .where("tag_masters.nonprofit_id = $id AND NOT tag_masters.deleted", id: np_id.to_i) + # .group_by("tag_joins.supporter_id") + # .as(:tags) + + tags_subquery = build_tag_query(np_id) expr = Qx.select('supporters.id') .from(:supporters) From 2c07222094d02ee11110803b7953274f06c38f38 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 11:44:30 -0500 Subject: [PATCH 117/208] Add CTE support to Qx --- gems/ruby-qx/lib/qx.rb | 98 ++++++++++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 37 deletions(-) diff --git a/gems/ruby-qx/lib/qx.rb b/gems/ruby-qx/lib/qx.rb index 1db46b597..8499ba7f1 100644 --- a/gems/ruby-qx/lib/qx.rb +++ b/gems/ruby-qx/lib/qx.rb @@ -60,48 +60,57 @@ def self.parse(expr) return expr # already parsed elsif expr.is_a?(Array) return expr.join(',') - elsif expr[:INSERT_INTO] - str = "INSERT INTO #{expr[:INSERT_INTO]} (#{expr[:INSERT_COLUMNS].join(', ')})" - throw ArgumentError.new('VALUES (or SELECT) clause is missing for INSERT INTO') unless expr[:VALUES] || expr[:SELECT] - throw ArgumentError.new("For safety, you can't use SELECT without insert columns for an INSERT INTO") if !expr[:INSERT_COLUMNS] && expr[:SELECT] - if expr[:SELECT] - str += ' ' + parse_select(expr) - else - str += " VALUES #{expr[:VALUES].map { |vals| "(#{vals.join(', ')})" }.join(', ')}" + else + str = "" + if expr[:WITHS].present? + str += "WITH " + str += expr[:WITHS].map do |with| + parse_with(with) + end.join(", ") end - if expr[:ON_CONFLICT] - str += ' ON CONFLICT' - - if expr[:CONFLICT_COLUMNS] - str += " (#{expr[:CONFLICT_COLUMNS].join(', ')})" - elsif expr[:ON_CONSTRAINT] - str += " ON CONSTRAINT #{expr[:ON_CONSTRAINT]}" + if expr[:INSERT_INTO] + str += "INSERT INTO #{expr[:INSERT_INTO]} (#{expr[:INSERT_COLUMNS].join(', ')})" + throw ArgumentError.new('VALUES (or SELECT) clause is missing for INSERT INTO') unless expr[:VALUES] || expr[:SELECT] + throw ArgumentError.new("For safety, you can't use SELECT without insert columns for an INSERT INTO") if !expr[:INSERT_COLUMNS] && expr[:SELECT] + if expr[:SELECT] + str += ' ' + parse_select(expr) + else + str += " VALUES #{expr[:VALUES].map { |vals| "(#{vals.join(', ')})" }.join(', ')}" end - str += ' DO NOTHING' if !expr[:CONFLICT_UPSERT] - if expr[:CONFLICT_UPSERT] - set_str = expr[:INSERT_COLUMNS].select{|i| i != 'created_at'}.map{|i| "#{i} = EXCLUDED.#{i}" } - str += " DO UPDATE SET #{set_str.join(', ')}" + if expr[:ON_CONFLICT] + str += ' ON CONFLICT' + + if expr[:CONFLICT_COLUMNS] + str += " (#{expr[:CONFLICT_COLUMNS].join(', ')})" + elsif expr[:ON_CONSTRAINT] + str += " ON CONSTRAINT #{expr[:ON_CONSTRAINT]}" + end + str += ' DO NOTHING' if !expr[:CONFLICT_UPSERT] + if expr[:CONFLICT_UPSERT] + set_str = expr[:INSERT_COLUMNS].select{|i| i != 'created_at'}.map{|i| "#{i} = EXCLUDED.#{i}" } + str += " DO UPDATE SET #{set_str.join(', ')}" + end end + str += ' RETURNING ' + expr[:RETURNING].join(', ') if expr[:RETURNING] + elsif expr[:SELECT] + str += parse_select(expr) + elsif expr[:DELETE_FROM] + str += 'DELETE FROM ' + expr[:DELETE_FROM] + throw ArgumentError.new('WHERE clause is missing for DELETE FROM') unless expr[:WHERE] + str += ' WHERE ' + expr[:WHERE].map { |w| "(#{w})" }.join(' AND ') + str += ' RETURNING ' + expr[:RETURNING].join(', ') if expr[:RETURNING] + elsif expr[:UPDATE] + str += 'UPDATE ' + expr[:UPDATE] + throw ArgumentError.new('SET clause is missing for UPDATE') unless expr[:SET] + throw ArgumentError.new('WHERE clause is missing for UPDATE') unless expr[:WHERE] + str += ' SET ' + expr[:SET] + str += ' FROM ' + expr[:FROM] if expr[:FROM] + str += ' WHERE ' + expr[:WHERE].map { |w| "(#{w})" }.join(' AND ') + str += ' ' + expr[:ON_CONFLICT] if expr[:ON_CONFLICT] + str += ' RETURNING ' + expr[:RETURNING].join(', ') if expr[:RETURNING] end - str += ' RETURNING ' + expr[:RETURNING].join(', ') if expr[:RETURNING] - elsif expr[:SELECT] - str = parse_select(expr) - elsif expr[:DELETE_FROM] - str = 'DELETE FROM ' + expr[:DELETE_FROM] - throw ArgumentError.new('WHERE clause is missing for DELETE FROM') unless expr[:WHERE] - str += ' WHERE ' + expr[:WHERE].map { |w| "(#{w})" }.join(' AND ') - str += ' RETURNING ' + expr[:RETURNING].join(', ') if expr[:RETURNING] - elsif expr[:UPDATE] - str = 'UPDATE ' + expr[:UPDATE] - throw ArgumentError.new('SET clause is missing for UPDATE') unless expr[:SET] - throw ArgumentError.new('WHERE clause is missing for UPDATE') unless expr[:WHERE] - str += ' SET ' + expr[:SET] - str += ' FROM ' + expr[:FROM] if expr[:FROM] - str += ' WHERE ' + expr[:WHERE].map { |w| "(#{w})" }.join(' AND ') - str += ' ' + expr[:ON_CONFLICT] if expr[:ON_CONFLICT] - str += ' RETURNING ' + expr[:RETURNING].join(', ') if expr[:RETURNING] + str end - str end # An instance method version of the above @@ -409,6 +418,17 @@ def explain self end + def with(name, expr, materialized:nil) + materialized_text = !materialized.nil? ? (materialized ? "MATERIALIZED" : "NOT MATERIALIZED") : "" + + raise "expr is not a Qx, that's not safe!" unless expr.is_a? Qx + @tree[:WITHS] ||= [] + @tree[:WITHS].push({name: name, expr: expr, materialized: materialized}) + + self + end + + # -- Helpers! def self.fetch(table_name, data, options = {}) @@ -495,6 +515,10 @@ def self.parse_joins(js) js.map { |table, cond, data| [table.is_a?(Qx) ? table.parse : table, Qx.interpolate_expr(cond, data)] } end + def self.parse_with(with) + with[:name] + " AS (#{with[:expr].parse})" + end + # Given an array, determine if it has the form # [[join_table, join_on, data], ...] # or From 5c6620588271bd4934ea40de9d032fb4522b8775 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 11:50:36 -0500 Subject: [PATCH 118/208] Make sure symbols are converted to string in CTE names --- gems/ruby-qx/lib/qx.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gems/ruby-qx/lib/qx.rb b/gems/ruby-qx/lib/qx.rb index 8499ba7f1..aadb67867 100644 --- a/gems/ruby-qx/lib/qx.rb +++ b/gems/ruby-qx/lib/qx.rb @@ -516,7 +516,7 @@ def self.parse_joins(js) end def self.parse_with(with) - with[:name] + " AS (#{with[:expr].parse})" + with[:name].to_s + " AS (#{with[:expr].parse})" end # Given an array, determine if it has the form From 2f8a1a3f009b996996aa09bc8c69636a6eb4925c Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 12:04:41 -0500 Subject: [PATCH 119/208] Add splitting space before the rest of the query --- gems/ruby-qx/lib/qx.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/gems/ruby-qx/lib/qx.rb b/gems/ruby-qx/lib/qx.rb index aadb67867..b600bd2e7 100644 --- a/gems/ruby-qx/lib/qx.rb +++ b/gems/ruby-qx/lib/qx.rb @@ -67,6 +67,7 @@ def self.parse(expr) str += expr[:WITHS].map do |with| parse_with(with) end.join(", ") + str += " " # add a splitting space before the rest of the query end if expr[:INSERT_INTO] str += "INSERT INTO #{expr[:INSERT_INTO]} (#{expr[:INSERT_COLUMNS].join(', ')})" From 78577fbb9e9c151f1b16f321ac6411bbcf12f909 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 12:04:41 -0500 Subject: [PATCH 120/208] Add splitting space before the rest of the query --- gems/ruby-qx/lib/qx.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/gems/ruby-qx/lib/qx.rb b/gems/ruby-qx/lib/qx.rb index aadb67867..b600bd2e7 100644 --- a/gems/ruby-qx/lib/qx.rb +++ b/gems/ruby-qx/lib/qx.rb @@ -67,6 +67,7 @@ def self.parse(expr) str += expr[:WITHS].map do |with| parse_with(with) end.join(", ") + str += " " # add a splitting space before the rest of the query end if expr[:INSERT_INTO] str += "INSERT INTO #{expr[:INSERT_INTO]} (#{expr[:INSERT_COLUMNS].join(', ')})" From 4921aea3c65dbc8e7403736cc8c453954a4b0740 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 12:02:30 -0500 Subject: [PATCH 121/208] Add nonprofit cte --- app/legacy_lib/query_supporters.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index d4d08302e..096d76a9f 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -210,6 +210,7 @@ def self.full_filter_expr(np_id, query) tags_subquery = build_tag_query(np_id) expr = Qx.select('supporters.id') + .with(:nonprofits, Qx.select("*").from(:nonprofits).where("id = $id", id: np_id.to_i)) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .where( From b65076fa6f1321647bb1a5d845934422a08c52ce Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 12:15:03 -0500 Subject: [PATCH 122/208] Add tag_masters cte --- app/legacy_lib/query_supporters.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 096d76a9f..d38156c95 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -211,6 +211,7 @@ def self.full_filter_expr(np_id, query) expr = Qx.select('supporters.id') .with(:nonprofits, Qx.select("*").from(:nonprofits).where("id = $id", id: np_id.to_i)) + .with(:tag_masters, Qx.select("*").from(:tag_masters).where("nonprofit_id = $id AND NOT deleted", id: np_id.to_i)) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .where( From b0c3cce35be46a8028fc99f9507a23e7624ea1d6 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 13:27:32 -0500 Subject: [PATCH 123/208] Add supporters CTE to QuerySupporters --- app/legacy_lib/query_supporters.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index d38156c95..3f37b50a2 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -160,6 +160,12 @@ def self._full_search(np_id, query) # return new_supporters # end + def self.supporters(np_id) + Qx.select('supporters.*') + .from(:supporters) + .where("supporters.nonprofit_id = $id and deleted != 'true'", id: np_id ) + end + def self.undeleted_supporters(np_id) Qx.select('id') @@ -212,6 +218,7 @@ def self.full_filter_expr(np_id, query) expr = Qx.select('supporters.id') .with(:nonprofits, Qx.select("*").from(:nonprofits).where("id = $id", id: np_id.to_i)) .with(:tag_masters, Qx.select("*").from(:tag_masters).where("nonprofit_id = $id AND NOT deleted", id: np_id.to_i)) + .with(:supporters, supporters(np_id)) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .where( From 6d45b7c61fee177ccbc2e0f6d5333372b78e0e65 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 13:35:26 -0500 Subject: [PATCH 124/208] Add a NonprofitQueryGenerator --- app/legacy_lib/nonprofit_query_generator.rb | 19 +++++++++++++++++++ app/legacy_lib/query_supporters.rb | 5 +++-- 2 files changed, 22 insertions(+), 2 deletions(-) create mode 100644 app/legacy_lib/nonprofit_query_generator.rb diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb new file mode 100644 index 000000000..8a19ade4b --- /dev/null +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -0,0 +1,19 @@ +class NonprofitQueryGenerator + def initialize(id) + @id = id.to_i + end + + def nonprofits + Qx.select("*").from(:nonprofits).where("id = $id", id: @id) + end + + def supporters + Qx.select('supporters.*') + .from(:supporters) + .where("supporters.nonprofit_id = $id and deleted != 'true'", id: @id ) + end + + def tag_masters + Qx.select("*").from(:tag_masters).where("nonprofit_id = $id AND NOT deleted", id: @id) + end +end \ No newline at end of file diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index d38156c95..67bd4bedf 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -208,10 +208,11 @@ def self.full_filter_expr(np_id, query) # .as(:tags) tags_subquery = build_tag_query(np_id) + np_queries = NonprofitQueryGenerator.new(np_id) expr = Qx.select('supporters.id') - .with(:nonprofits, Qx.select("*").from(:nonprofits).where("id = $id", id: np_id.to_i)) - .with(:tag_masters, Qx.select("*").from(:tag_masters).where("nonprofit_id = $id AND NOT deleted", id: np_id.to_i)) + .with(:nonprofits, np_queries.nonprofits(np_id)) + .with(:tag_masters, np_queries.tag_masters(np_id)) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .where( From d2a443a620318cdea8ef30cc2167a4fea8d3f7fc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 13:43:01 -0500 Subject: [PATCH 125/208] Correct calls to queries --- app/legacy_lib/query_supporters.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 67bd4bedf..e6374a3a5 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -211,8 +211,8 @@ def self.full_filter_expr(np_id, query) np_queries = NonprofitQueryGenerator.new(np_id) expr = Qx.select('supporters.id') - .with(:nonprofits, np_queries.nonprofits(np_id)) - .with(:tag_masters, np_queries.tag_masters(np_id)) + .with(:nonprofits, np_queries.nonprofits) + .with(:tag_masters, np_queries.tag_masters) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .where( From 3a77bfa7b893522ef1f22d69dd994fd823d27530 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 13:49:37 -0500 Subject: [PATCH 126/208] Remove unneeded supporters tweaks --- app/legacy_lib/query_supporters.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index b7fd69416..c0a69c300 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -215,10 +215,6 @@ def self.full_filter_expr(np_id, query) .with(:supporters, np_queries.supporters) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') - .where( - ["supporters.nonprofit_id=$id", id: np_id.to_i], - ["supporters.deleted != true"] - ) .left_join( [tags_subquery, "tags.supporter_id=supporters.id"], [payments_subquery, "payments.supporter_id=supporters.id"] From a2f7ffe22a5dd46e68c356677eef6f0a37da2dcb Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 13:54:09 -0500 Subject: [PATCH 127/208] Add supporter_notes CTE --- app/legacy_lib/nonprofit_query_generator.rb | 5 +++++ app/legacy_lib/query_supporters.rb | 1 + 2 files changed, 6 insertions(+) diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb index 8a19ade4b..08cb81801 100644 --- a/app/legacy_lib/nonprofit_query_generator.rb +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -13,6 +13,11 @@ def supporters .where("supporters.nonprofit_id = $id and deleted != 'true'", id: @id ) end + def supporter_notes + Qx.select("*").from(:supporters).join(:supporter_notes, "supporters.id = supporters_notes.supporter_id") + end + + def tag_masters Qx.select("*").from(:tag_masters).where("nonprofit_id = $id AND NOT deleted", id: @id) end diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index c0a69c300..11b4f4762 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -213,6 +213,7 @@ def self.full_filter_expr(np_id, query) .with(:nonprofits, np_queries.nonprofits) .with(:tag_masters, np_queries.tag_masters) .with(:supporters, np_queries.supporters) + .with(:supporter_notes, np_queries.supporter_notes) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .left_join( From 0105d6106da61b8afc847cfff43cbbad9e30c0c8 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 14:07:05 -0500 Subject: [PATCH 128/208] Correct supporter_notes reference --- app/legacy_lib/nonprofit_query_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb index 08cb81801..8c32be520 100644 --- a/app/legacy_lib/nonprofit_query_generator.rb +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -14,7 +14,7 @@ def supporters end def supporter_notes - Qx.select("*").from(:supporters).join(:supporter_notes, "supporters.id = supporters_notes.supporter_id") + Qx.select("*").from(:supporters).join(:supporter_notes, "supporters.id = supporter_notes.supporter_id") end From 8477eadde39b7016f88ddc90ffa5693589c7a878 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 14:12:49 -0500 Subject: [PATCH 129/208] Make supporter_notes clear in select --- app/legacy_lib/nonprofit_query_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb index 8c32be520..f0ce820b8 100644 --- a/app/legacy_lib/nonprofit_query_generator.rb +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -14,7 +14,7 @@ def supporters end def supporter_notes - Qx.select("*").from(:supporters).join(:supporter_notes, "supporters.id = supporter_notes.supporter_id") + Qx.select("supporter_notes.*").from(:supporters).join(:supporter_notes, "supporters.id = supporter_notes.supporter_id") end From c1254785037e6994e1cf6dc883a1266de9f3bb97 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 14:18:57 -0500 Subject: [PATCH 130/208] Optimize payment query --- app/legacy_lib/nonprofit_query_generator.rb | 4 ++++ app/legacy_lib/query_supporters.rb | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb index f0ce820b8..8688c18fc 100644 --- a/app/legacy_lib/nonprofit_query_generator.rb +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -13,6 +13,10 @@ def supporters .where("supporters.nonprofit_id = $id and deleted != 'true'", id: @id ) end + def payments + Qx.select('*').from(:payments).where("nonprofit_id = $id", id: @id) + end + def supporter_notes Qx.select("supporter_notes.*").from(:supporters).join(:supporter_notes, "supporters.id = supporter_notes.supporter_id") end diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 11b4f4762..b7de3e9fe 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -188,11 +188,11 @@ def self.full_filter_expr(np_id, query) Qx.select("supporter_id", "SUM(gross_amount)", "MAX(date) AS max_date", "MIN(date) AS min_date", "COUNT(*) AS count") .from( Qx.select("supporter_id", "date", "gross_amount") - .from(Qx.select('*').from(:payments).where("nonprofit_id = $id", id: np_id).as(:payments).parse) + .from(:nonprofit_payments) .join(Qx.select('id') .from(:supporters) .where("supporters.nonprofit_id = $id and deleted != 'true'", id: np_id ) - .as("payments_to_supporters"), "payments_to_supporters.id = payments.supporter_id" + .as("payments_to_supporters"), "payments_to_supporters.id = nonprofit_payments.supporter_id" ) .as("outer_from_payment_to_supporter") .parse) @@ -214,6 +214,7 @@ def self.full_filter_expr(np_id, query) .with(:tag_masters, np_queries.tag_masters) .with(:supporters, np_queries.supporters) .with(:supporter_notes, np_queries.supporter_notes) + .with(:nonprofit_payments, np_queries.payments) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .left_join( @@ -404,7 +405,7 @@ def self.get_chunk_of_export(np_id, query, offset=nil, limit=nil, skip_header=fa get_last_payment_query = Qx.select('supporter_id', "MAX(date) AS date") - .from(:payments) + .from(:nonprofit_payments) .group_by("supporter_id") .as("last_payment") From 0f795450f378e44dd1ca6567c6811ed0830bf0f4 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 25 Aug 2023 16:16:42 -0500 Subject: [PATCH 131/208] Update node to 16 --- .github/workflows/build.yml | 2 +- .github/workflows/deploy.yml | 2 +- .nvmrc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c7c791ac..ba1d843e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - node: [14.19.1, 16] + node: [16] ruby: ['2.6.10'] postgres: ['16'] fail-fast: false diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index bef5f4fa2..390b04e63 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -25,7 +25,7 @@ jobs: strategy: matrix: os: [ubuntu-20.04] - node: [14.19.1] + node: [16] ruby: ['2.6.10'] postgres: ['16'] fail-fast: false diff --git a/.nvmrc b/.nvmrc index 49319ad46..b5794c5ec 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1,2 +1,2 @@ -14.19.1 +16 From 8fbd7944074f53108dab5d22ae30ba05de6d9fcd Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 15:16:19 -0500 Subject: [PATCH 132/208] Remove redundant tag_masters check --- app/legacy_lib/query_supporters.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 11b4f4762..5082777a7 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -177,7 +177,6 @@ def self.build_tag_query(np_id) tags_query = Qx.select("tag_joins.supporter_id", "ARRAY_AGG(tag_masters.id) AS ids", "ARRAY_AGG(tag_masters.name::text) AS names") .from(tag_joins_only_for_nonprofit_query(np_id).as(:tag_joins)) .join(:tag_masters, "tag_masters.id=tag_joins.tag_master_id") - .where("tag_masters.nonprofit_id = $id AND NOT tag_masters.deleted ", id: np_id.to_i) .group_by("tag_joins.supporter_id") .as(:tags) end @@ -191,7 +190,6 @@ def self.full_filter_expr(np_id, query) .from(Qx.select('*').from(:payments).where("nonprofit_id = $id", id: np_id).as(:payments).parse) .join(Qx.select('id') .from(:supporters) - .where("supporters.nonprofit_id = $id and deleted != 'true'", id: np_id ) .as("payments_to_supporters"), "payments_to_supporters.id = payments.supporter_id" ) .as("outer_from_payment_to_supporter") From 80367a77b98e3c390a5ee064ed88b5d37f021b0a Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 15:17:20 -0500 Subject: [PATCH 133/208] Cleanup --- app/legacy_lib/nonprofit_query_generator.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb index f0ce820b8..66eddd7d8 100644 --- a/app/legacy_lib/nonprofit_query_generator.rb +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -16,7 +16,6 @@ def supporters def supporter_notes Qx.select("supporter_notes.*").from(:supporters).join(:supporter_notes, "supporters.id = supporter_notes.supporter_id") end - def tag_masters Qx.select("*").from(:tag_masters).where("nonprofit_id = $id AND NOT deleted", id: @id) From 21fbaaa5956b13d270c2683d246754b51b4f2f1b Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 15:20:14 -0500 Subject: [PATCH 134/208] Optimize tag queries --- app/legacy_lib/nonprofit_query_generator.rb | 6 ++++++ app/legacy_lib/query_supporters.rb | 11 +++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb index 66eddd7d8..01ccdf971 100644 --- a/app/legacy_lib/nonprofit_query_generator.rb +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -20,4 +20,10 @@ def supporter_notes def tag_masters Qx.select("*").from(:tag_masters).where("nonprofit_id = $id AND NOT deleted", id: @id) end + + def tag_joins_through_supporters + Qx.select("tag_joins.id, tag_joins.supporter_id, tag_joins.tag_master_id") + .from(:tag_joins) + .join(:supporters, "tags_to_supporters.id = tag_joins.supporter_id") + end end \ No newline at end of file diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 5082777a7..42bea053c 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -159,23 +159,17 @@ def self._full_search(np_id, query) # # return new_supporters # end - def self.undeleted_supporters(np_id) - - Qx.select('id') - .from(:supporters) - .where("supporters.nonprofit_id = $id and deleted != 'true'", id: np_id ) - end def self.tag_joins_only_for_nonprofit_query(np_id) Qx.select("tag_joins.id, tag_joins.supporter_id, tag_joins.tag_master_id") .from(:tag_joins) - .join(undeleted_supporters(np_id).as('tags_to_supporters'), "tags_to_supporters.id = tag_joins.supporter_id") + .join(:supporters, "supporters.id = tag_joins.supporter_id") end def self.build_tag_query(np_id) tags_query = Qx.select("tag_joins.supporter_id", "ARRAY_AGG(tag_masters.id) AS ids", "ARRAY_AGG(tag_masters.name::text) AS names") - .from(tag_joins_only_for_nonprofit_query(np_id).as(:tag_joins)) + .from(:tag_joins) .join(:tag_masters, "tag_masters.id=tag_joins.tag_master_id") .group_by("tag_joins.supporter_id") .as(:tags) @@ -212,6 +206,7 @@ def self.full_filter_expr(np_id, query) .with(:tag_masters, np_queries.tag_masters) .with(:supporters, np_queries.supporters) .with(:supporter_notes, np_queries.supporter_notes) + .with(:tag_joins, np_queries.tag_joins_through_supporters) .from(:supporters) .join('nonprofits', 'nonprofits.id=supporters.nonprofit_id') .left_join( From 7de5f4308eefe259ad3036f2ef40c7f90085e03a Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 15:23:42 -0500 Subject: [PATCH 135/208] Correct supporters reference --- app/legacy_lib/nonprofit_query_generator.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/legacy_lib/nonprofit_query_generator.rb b/app/legacy_lib/nonprofit_query_generator.rb index 01ccdf971..a01542b17 100644 --- a/app/legacy_lib/nonprofit_query_generator.rb +++ b/app/legacy_lib/nonprofit_query_generator.rb @@ -24,6 +24,6 @@ def tag_masters def tag_joins_through_supporters Qx.select("tag_joins.id, tag_joins.supporter_id, tag_joins.tag_master_id") .from(:tag_joins) - .join(:supporters, "tags_to_supporters.id = tag_joins.supporter_id") + .join(:supporters, "supporters.id = tag_joins.supporter_id") end end \ No newline at end of file From ec869e6fde39363b5355f92aeefb02501ec7198d Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 25 Sep 2024 15:51:23 -0500 Subject: [PATCH 136/208] Correct payment references --- app/legacy_lib/query_supporters.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 6b187ad6c..847c57e16 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -184,7 +184,7 @@ def self.full_filter_expr(np_id, query) .from(:nonprofit_payments) .join(Qx.select('id') .from(:supporters) - .as("payments_to_supporters"), "payments_to_supporters.id = payments.supporter_id" + .as("payments_to_supporters"), "payments_to_supporters.id = nonprofit_payments.supporter_id" ) .as("outer_from_payment_to_supporter") .parse) @@ -284,7 +284,7 @@ def self.full_filter_expr(np_id, query) if query[:recurring].present? rec_ps_subquery = Qx.select("payments.count", "payments.supporter_id") - .from(:payments) + .from(:nonprofit_payments) .where("kind='RecurringDonation'") .group_by("payments.supporter_id") .as(:rec_ps) From c09791db06bcab379b8739b4b0ee910f5b7a8ba6 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 1 Oct 2024 17:55:23 -0500 Subject: [PATCH 137/208] Fix queries to payments on recurring donations --- app/legacy_lib/query_supporters.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 847c57e16..ef0b93975 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -283,7 +283,7 @@ def self.full_filter_expr(np_id, query) end if query[:recurring].present? - rec_ps_subquery = Qx.select("payments.count", "payments.supporter_id") + rec_ps_subquery = Qx.select("nonprofit_payments.count", "nonprofit_payments.supporter_id") .from(:nonprofit_payments) .where("kind='RecurringDonation'") .group_by("payments.supporter_id") From e177991f5c83fab8cd76925639920a12ad777678 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 1 Oct 2024 17:57:17 -0500 Subject: [PATCH 138/208] Correct in the group by as well --- app/legacy_lib/query_supporters.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index ef0b93975..838c68e17 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -286,7 +286,7 @@ def self.full_filter_expr(np_id, query) rec_ps_subquery = Qx.select("nonprofit_payments.count", "nonprofit_payments.supporter_id") .from(:nonprofit_payments) .where("kind='RecurringDonation'") - .group_by("payments.supporter_id") + .group_by("nonprofit_payments.supporter_id") .as(:rec_ps) expr = expr.add_left_join(rec_ps_subquery, "rec_ps.supporter_id=supporters.id") .and_where('rec_ps.count > 0') From fa71a41fad8e5daaa8059ec24087d73d14ffeb69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Oct 2024 21:21:24 +0000 Subject: [PATCH 139/208] Bump cookie from 0.3.1 to 0.7.0 Bumps [cookie](https://github.com/jshttp/cookie) from 0.3.1 to 0.7.0. - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.3.1...v0.7.0) --- updated-dependencies: - dependency-name: cookie dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 758885772..f3123493d 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "chart.js": "2.1.4", "color": "^3.1.0", "commons.css": "0.1.8", - "cookie": "0.3.1", + "cookie": "0.7.0", "cropperjs": "1.0.0-beta.2", "csstype": "^2.6.20", "data-tooltip": "0.0.1", diff --git a/yarn.lock b/yarn.lock index 2a11a01d7..ebf56a5ab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2753,10 +2753,10 @@ convert-source-map@^1.4.0, convert-source-map@^1.5.1, convert-source-map@^1.6.0, dependencies: safe-buffer "~5.1.1" -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - integrity sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw== +cookie@0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.0.tgz#2148f68a77245d5c2c0005d264bc3e08cfa0655d" + integrity sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ== cookie@^0.4.1: version "0.4.2" From 096a0e623391a096be1d4c4754e485c5a6e9db26 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 24 Oct 2024 13:23:01 -0500 Subject: [PATCH 140/208] allow title_image_url param in widget --- .../nonprofits/donation_form/title_row.scss | 12 ++++++--- client/js/nonprofits/donate/wizard.js | 26 ++++++++++++------- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss index 698bde4f9..bc578504f 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss @@ -6,9 +6,10 @@ position: relative; overflow: hidden; background: $fog; + display: flex; + gap: 15px; } .titleRow-info { - float: left; max-width: 85%; } .titleRow h2 { @@ -30,11 +31,14 @@ font-size: 14px; color: rgba($logo-blue, 0.9); } -.titleRow img { - float: left; - padding-right: 15px; +.titleRow img.logo { max-width: 15%; max-height: 55px; + flex-shrink: 0; +} +.titleRow-info img { + max-width: 85%; + max-height: 55px; } #donate .closeButton { z-index: 1; diff --git a/client/js/nonprofits/donate/wizard.js b/client/js/nonprofits/donate/wizard.js index cf3ffcce7..07e63db07 100644 --- a/client/js/nonprofits/donate/wizard.js +++ b/client/js/nonprofits/donate/wizard.js @@ -154,6 +154,21 @@ const postDedication = (dedication, donor, donation) => { }).load) } +const titleInfo = state => { + if (state.params$().title_image_url) { + return [h('img', { props: { src: state.params$().title_image_url } })]; + } + + return [ + h('h2', app.campaign.name || app.nonprofit.name) + , h('p', [ + state.params$().designation && !state.params$().single_amount + ? headerDesignation(state) + : app.campaign.tagline || app.nonprofit.tagline || '', + ]), + ]; +} + const view = state => { return h('div.js-donateForm', { class: {'is-modal': state.params$().offsite} @@ -164,15 +179,8 @@ const view = state => { , class: {'u-hide': (state.params$().embedded || state.params$().mode === 'embedded') || !state.params$().offsite } }) , h('div.titleRow', [ - h('img', {props: {src: app.nonprofit.logo.normal.url}}) - , h('div.titleRow-info', [ - h('h2', app.campaign.name || app.nonprofit.name ) - , h('p', [ - state.params$().designation && !state.params$().single_amount - ? headerDesignation(state) - : app.campaign.tagline || app.nonprofit.tagline || '' - ]) - ]) + h('img.logo', {props: {src: app.nonprofit.logo.normal.url}}) + , h('div.titleRow-info', titleInfo(state)) ]) , wizardWrapper(state) , h('footer.donateForm-footer', { From d247566a7352384c4d92a124e02ae5bee76a8f70 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 24 Oct 2024 15:57:12 -0500 Subject: [PATCH 141/208] allow title_image_alt param in widget --- client/js/nonprofits/donate/wizard.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/client/js/nonprofits/donate/wizard.js b/client/js/nonprofits/donate/wizard.js index 07e63db07..546915d47 100644 --- a/client/js/nonprofits/donate/wizard.js +++ b/client/js/nonprofits/donate/wizard.js @@ -156,7 +156,14 @@ const postDedication = (dedication, donor, donation) => { const titleInfo = state => { if (state.params$().title_image_url) { - return [h('img', { props: { src: state.params$().title_image_url } })]; + return [ + h('img', { + props: { + src: state.params$().title_image_url + , alt: state.params$().title_image_alt || app.campaign.tagline || app.nonprofit.tagline || '' + }, + }), + ]; } return [ From 53f965a64233828cdae85ba5f0a172af3da3e723 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 28 Oct 2024 11:04:14 -0500 Subject: [PATCH 142/208] widget titlerow - center images vertically, do not distort --- .../stylesheets/nonprofits/donation_form/title_row.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss index bc578504f..a882b2716 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss @@ -11,6 +11,9 @@ } .titleRow-info { max-width: 85%; + display: flex; + flex-direction: column; + justify-content: center; } .titleRow h2 { margin: 0 0 1px 0; @@ -35,10 +38,12 @@ max-width: 15%; max-height: 55px; flex-shrink: 0; + object-fit: contain; } .titleRow-info img { max-width: 85%; max-height: 55px; + object-fit: contain; } #donate .closeButton { z-index: 1; From c957f1abd007b6843e4427ad5887a64548654991 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 29 Oct 2024 10:19:23 -0500 Subject: [PATCH 143/208] Move isCustomFieldDescription guard into the customField directory --- .../donate/parseFields/customField/index.ts | 9 +++++++++ .../parseFields/customFields/JsonStringParser.ts | 13 +++---------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/client/js/nonprofits/donate/parseFields/customField/index.ts b/client/js/nonprofits/donate/parseFields/customField/index.ts index c56f93266..ae0e090dd 100644 --- a/client/js/nonprofits/donate/parseFields/customField/index.ts +++ b/client/js/nonprofits/donate/parseFields/customField/index.ts @@ -1,5 +1,7 @@ // License: LGPL-3.0-or-later import { parseCustomField } from "./legacy"; +import has from 'lodash/has'; +import get from 'lodash/get'; export interface CustomFieldDescription { name: NonNullable; @@ -7,5 +9,12 @@ export interface CustomFieldDescription { type: 'supporter'; } +export function isCustomFieldDescription(item:unknown) : item is CustomFieldDescription { + return typeof item == 'object' && + has(item, 'name') && + has(item, 'label') && + ['supporter'].includes(get(item, 'type')); +} + export default parseCustomField; \ No newline at end of file diff --git a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts index eac6da492..6a8d9fc11 100644 --- a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts +++ b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts @@ -1,15 +1,8 @@ // License: LGPL-3.0-or-later -import has from 'lodash/has'; -import get from 'lodash/get'; import { parse } from 'json5'; -import { CustomFieldDescription } from '../customField'; +import { CustomFieldDescription, isCustomFieldDescription } from '../customField'; + -function isCustomFieldDesc(item:unknown) : item is CustomFieldDescription { - return typeof item == 'object' && - has(item, 'name') && - has(item, 'label') && - ['supporter'].includes(get(item, 'type')); -} export default class JsonStringParser { public errors:SyntaxError[] = []; public readonly results: CustomFieldDescription[] = []; @@ -27,7 +20,7 @@ export default class JsonStringParser { const result = parse(this.fieldsString) if (result instanceof Array) { result.forEach((i) => { - if (isCustomFieldDesc(i)) { + if (isCustomFieldDescription(i)) { this.results.push({ ...i}); } From 969472908f1c139771faef1980d64d9e38390849 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Tue, 29 Oct 2024 10:23:22 -0500 Subject: [PATCH 144/208] remove extra line break --- .../donate/parseFields/customFields/JsonStringParser.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts index 6a8d9fc11..756db880f 100644 --- a/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts +++ b/client/js/nonprofits/donate/parseFields/customFields/JsonStringParser.ts @@ -2,7 +2,6 @@ import { parse } from 'json5'; import { CustomFieldDescription, isCustomFieldDescription } from '../customField'; - export default class JsonStringParser { public errors:SyntaxError[] = []; public readonly results: CustomFieldDescription[] = []; From f8da858d63a784a5073a4ae775decd18338f79db Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 28 Oct 2024 14:28:59 -0500 Subject: [PATCH 145/208] allow custom amounts with highlight icons in widget custom_amounts param --- client/js/nonprofits/donate/get-params.js | 10 ++--- .../js/nonprofits/donate/get-params.spec.js | 5 +++ .../customAmounts/JsonStringParser.spec.ts | 42 +++++++++++++++++++ .../customAmounts/JsonStringParser.ts | 40 ++++++++++++++++++ .../donate/parseFields/customAmounts/index.ts | 22 ++++++++++ .../js/nonprofits/donate/parseFields/index.ts | 1 + 6 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts create mode 100644 client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts create mode 100644 client/js/nonprofits/donate/parseFields/customAmounts/index.ts diff --git a/client/js/nonprofits/donate/get-params.js b/client/js/nonprofits/donate/get-params.js index 65c8b755d..9fe05dcf5 100644 --- a/client/js/nonprofits/donate/get-params.js +++ b/client/js/nonprofits/donate/get-params.js @@ -1,18 +1,14 @@ // License: LGPL-3.0-or-later const R = require('ramda') -const {getDefaultAmounts} = require('./custom_amounts'); -const { parseCustomFields, splitParam } = require('./parseFields'); +const { parseCustomFields, parseCustomAmounts, splitParam } = require('./parseFields'); module.exports = params => { - const defaultAmts = getDefaultAmounts().join() // Set defaults - const merge = R.merge({ - custom_amounts: '' - }) + const merge = R.merge({ custom_amounts: '' }) // Preprocess data const evolve = R.evolve({ multiple_designations: splitParam - , custom_amounts: amts => R.compose(R.map(Number), splitParam)(amts || defaultAmts) + , custom_amounts: parseCustomAmounts , custom_fields: parseCustomFields , tags: tags => R.map(tag => { return tag.trim() diff --git a/client/js/nonprofits/donate/get-params.spec.js b/client/js/nonprofits/donate/get-params.spec.js index b33595bef..ff8a274a3 100644 --- a/client/js/nonprofits/donate/get-params.spec.js +++ b/client/js/nonprofits/donate/get-params.spec.js @@ -19,6 +19,11 @@ describe('.getParams', () => { it('splits properly', () => { expect(getParams({custom_amounts: '3.5, 600\n;400;3'})).toHaveProperty('custom_amounts', [3.5, 600, 400, 3]); }); + + it('accepts custom amounts with highlight icons properly', () => { + expect(getParams({custom_amounts: "5,{amount:30,highlight:'car'},50"})) + .toHaveProperty('custom_amounts', [5, { amount: 30, highlight: 'car'}, 50]); + }); }); diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts new file mode 100644 index 000000000..014536f3f --- /dev/null +++ b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts @@ -0,0 +1,42 @@ +// License: LGPL-3.0-or-later +import JsonStringParser from './JsonStringParser'; + +describe('JsonStringParser', () => { + + describe.each([ + ["with bracket", "["], + ["with brace", "[{]"], + ["with no amount given", "[{name:'name', label: 'LABEL'}]"], + ])("when invalid %s", (_n, input)=> { + const parser = new JsonStringParser(input) + it('has correct result', () => { + expect(parser.results).toStrictEqual([]); + }); + + it('has error', () => { + expect(parser.errors).not.toBeEmpty(); + }); + + it('is marked not valid', () => { + expect(parser.isValid).toBe(false) + }); + }); + + describe.each([ + ['when an empty array', '[]', []], + ])("when valid %s", (_name, input, result) => { + const parser = new JsonStringParser(input); + + it('has no errors', () => { + expect(parser.errors).toBeEmpty(); + }); + + it('has is marked valid', () => { + expect(parser.isValid).toStrictEqual(true); + }); + + it('matches expected result', () => { + expect(parser.results).toStrictEqual(result); + }); + }); +}); \ No newline at end of file diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts new file mode 100644 index 000000000..4c11a3ab5 --- /dev/null +++ b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts @@ -0,0 +1,40 @@ +// License: LGPL-3.0-or-later +import has from 'lodash/has'; +import { parse } from 'json5'; +import { Amount, CustomAmount } from '../customAmounts'; + +function isCustomAmountObject(item: unknown): item is CustomAmount { + return typeof item == 'object' && has(item, 'amount'); +} +export default class JsonStringParser { + public errors: SyntaxError[] = []; + public readonly results: Amount[] = []; + constructor(public readonly fieldsString: string) { + this._parse(); + } + + get isValid(): boolean { + return this.errors.length == 0; + } + + private _parse = (): void => { + try { + const result = parse(this.fieldsString); + if (result instanceof Array) { + result.forEach((i) => { + if (isCustomAmountObject(i)) { + this.results.push({ ...i }); + } else if (typeof i == 'number') { + this.results.push(i); + } else { + this.errors.push(new SyntaxError(JSON.stringify(i) + ' is not a valid custom amount')); + } + }); + } else { + this.errors.push(new SyntaxError('Input did not parse to an array')); + } + } catch (e: any) { + this.errors.push(e); + } + }; +} diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts new file mode 100644 index 000000000..530f926a6 --- /dev/null +++ b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts @@ -0,0 +1,22 @@ +// License: LGPL-3.0-or-later +import JsonStringParser from './JsonStringParser'; +const R = require('ramda'); +const { getDefaultAmounts } = require('../../custom_amounts'); +import { splitParam } from '..'; + +export type Amount = number | CustomAmount; + +export interface CustomAmount { + amount: NonNullable; + highlight: string; +} + +export default function parseCustomFields(amountsString: string): Amount[] { + const defaultAmts = getDefaultAmounts().join(); + + if (amountsString.includes('{')) { + return new JsonStringParser(`[${amountsString}]`).results; + } else { + return R.compose(R.map(Number), splitParam)(amountsString || defaultAmts); + } +} diff --git a/client/js/nonprofits/donate/parseFields/index.ts b/client/js/nonprofits/donate/parseFields/index.ts index d0340a0c2..8c795fcc5 100644 --- a/client/js/nonprofits/donate/parseFields/index.ts +++ b/client/js/nonprofits/donate/parseFields/index.ts @@ -1,6 +1,7 @@ // License: LGPL-3.0-or-later export {default as parseCustomField, CustomFieldDescription} from "./customField"; export {default as parseCustomFields} from "./customFields"; +export {default as parseCustomAmounts} from './customAmounts'; export function splitParam(param:string) : string[] { return param.split(/[_;,]/); From 50743c3c68f0da347d211bc1a5b22fe5bb2cb55d Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 28 Oct 2024 15:23:17 -0500 Subject: [PATCH 146/208] PR fixes --- .../js/nonprofits/donate/parseFields/customAmounts/index.ts | 5 ++--- client/js/nonprofits/donate/parseFields/index.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts index 530f926a6..68a20b58b 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts @@ -1,6 +1,5 @@ // License: LGPL-3.0-or-later import JsonStringParser from './JsonStringParser'; -const R = require('ramda'); const { getDefaultAmounts } = require('../../custom_amounts'); import { splitParam } from '..'; @@ -11,12 +10,12 @@ export interface CustomAmount { highlight: string; } -export default function parseCustomFields(amountsString: string): Amount[] { +export default function parseCustomAmounts(amountsString: string): Amount[] { const defaultAmts = getDefaultAmounts().join(); if (amountsString.includes('{')) { return new JsonStringParser(`[${amountsString}]`).results; } else { - return R.compose(R.map(Number), splitParam)(amountsString || defaultAmts); + return splitParam(amountsString || defaultAmts).map(Number); } } diff --git a/client/js/nonprofits/donate/parseFields/index.ts b/client/js/nonprofits/donate/parseFields/index.ts index 8c795fcc5..43a9458c1 100644 --- a/client/js/nonprofits/donate/parseFields/index.ts +++ b/client/js/nonprofits/donate/parseFields/index.ts @@ -1,7 +1,7 @@ // License: LGPL-3.0-or-later export {default as parseCustomField, CustomFieldDescription} from "./customField"; export {default as parseCustomFields} from "./customFields"; -export {default as parseCustomAmounts} from './customAmounts'; +export {default as parseCustomAmounts, CustomAmount, Amount} from './customAmounts'; export function splitParam(param:string) : string[] { return param.split(/[_;,]/); From 43b9f854d70bf5dcbd04a786edf674087b30d535 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 28 Oct 2024 16:47:12 -0500 Subject: [PATCH 147/208] custom amounts always returns objects --- .../js/nonprofits/donate/get-params.spec.js | 27 ++++++++++++++----- .../customAmounts/JsonStringParser.ts | 9 ++++--- .../donate/parseFields/customAmounts/index.ts | 11 ++++---- 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/client/js/nonprofits/donate/get-params.spec.js b/client/js/nonprofits/donate/get-params.spec.js index ff8a274a3..9e39055c0 100644 --- a/client/js/nonprofits/donate/get-params.spec.js +++ b/client/js/nonprofits/donate/get-params.spec.js @@ -5,24 +5,37 @@ const {getDefaultAmounts} = require('./custom_amounts'); describe('.getParams', () => { describe('custom_amounts:', () => { it('gives custom_amounts defaults if not passed in', () => { - expect(getParams({})).toHaveProperty('custom_amounts', getDefaultAmounts()); + expect(getParams({})).toHaveProperty( + 'custom_amounts', + getDefaultAmounts().map((a) => ({ amount: a, highlight: false })) + ); }); it('accepts integers', () => { - expect(getParams({custom_amounts: '3'})).toHaveProperty('custom_amounts', [3]); + expect(getParams({custom_amounts: '3'})).toHaveProperty('custom_amounts', [{ amount: 3, highlight: false }]); }); it('accepts floats', () => { - expect(getParams({custom_amounts: '3.5'})).toHaveProperty('custom_amounts', [3.5]); + expect(getParams({ custom_amounts: '3.5' })).toHaveProperty('custom_amounts', [ + { amount: 3.5, highlight: false }, + ]); }); it('splits properly', () => { - expect(getParams({custom_amounts: '3.5, 600\n;400;3'})).toHaveProperty('custom_amounts', [3.5, 600, 400, 3]); + expect(getParams({ custom_amounts: '3.5, 600\n;400;3' })).toHaveProperty('custom_amounts', [ + { amount: 3.5, highlight: false }, + { amount: 600, highlight: false }, + { amount: 400, highlight: false }, + { amount: 3, highlight: false }, + ]); }); it('accepts custom amounts with highlight icons properly', () => { - expect(getParams({custom_amounts: "5,{amount:30,highlight:'car'},50"})) - .toHaveProperty('custom_amounts', [5, { amount: 30, highlight: 'car'}, 50]); + expect(getParams({ custom_amounts: "5,{amount:30,highlight:'car'},50" })).toHaveProperty('custom_amounts', [ + { amount: 5, highlight: false }, + { amount: 30, highlight: 'car' }, + { amount: 50, highlight: false }, + ]); }); }); @@ -62,4 +75,4 @@ describe('.getParams', () => { expect(getParams({tags: ' \tA tag name\n'})).toHaveProperty('tags', ['A tag name']); }); }); -}); \ No newline at end of file +}); diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts index 4c11a3ab5..de85a04d5 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts @@ -1,14 +1,14 @@ // License: LGPL-3.0-or-later import has from 'lodash/has'; import { parse } from 'json5'; -import { Amount, CustomAmount } from '../customAmounts'; +import { CustomAmount } from '../customAmounts'; function isCustomAmountObject(item: unknown): item is CustomAmount { return typeof item == 'object' && has(item, 'amount'); } export default class JsonStringParser { public errors: SyntaxError[] = []; - public readonly results: Amount[] = []; + public readonly results: CustomAmount[] = []; constructor(public readonly fieldsString: string) { this._parse(); } @@ -20,12 +20,13 @@ export default class JsonStringParser { private _parse = (): void => { try { const result = parse(this.fieldsString); + const emptyCustomAmount = { highlight: false }; if (result instanceof Array) { result.forEach((i) => { if (isCustomAmountObject(i)) { - this.results.push({ ...i }); + this.results.push({ ...emptyCustomAmount, ...i }); } else if (typeof i == 'number') { - this.results.push(i); + this.results.push({ amount: i, highlight: false }); } else { this.errors.push(new SyntaxError(JSON.stringify(i) + ' is not a valid custom amount')); } diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts index 68a20b58b..f89968318 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts @@ -3,19 +3,20 @@ import JsonStringParser from './JsonStringParser'; const { getDefaultAmounts } = require('../../custom_amounts'); import { splitParam } from '..'; -export type Amount = number | CustomAmount; - export interface CustomAmount { amount: NonNullable; - highlight: string; + highlight: NonNullable; } -export default function parseCustomAmounts(amountsString: string): Amount[] { +export default function parseCustomAmounts(amountsString: string): CustomAmount[] { const defaultAmts = getDefaultAmounts().join(); if (amountsString.includes('{')) { return new JsonStringParser(`[${amountsString}]`).results; } else { - return splitParam(amountsString || defaultAmts).map(Number); + const commaParams = splitParam(amountsString || defaultAmts) + .map(Number) + .join(','); + return new JsonStringParser(`[${commaParams}]`).results; } } From 084977a4a9054e61b524710d8249e60a3cc89e90 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 28 Oct 2024 17:14:01 -0500 Subject: [PATCH 148/208] clean up leftover import --- client/js/nonprofits/donate/parseFields/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/nonprofits/donate/parseFields/index.ts b/client/js/nonprofits/donate/parseFields/index.ts index 43a9458c1..fa6ea40dd 100644 --- a/client/js/nonprofits/donate/parseFields/index.ts +++ b/client/js/nonprofits/donate/parseFields/index.ts @@ -1,7 +1,7 @@ // License: LGPL-3.0-or-later export {default as parseCustomField, CustomFieldDescription} from "./customField"; export {default as parseCustomFields} from "./customFields"; -export {default as parseCustomAmounts, CustomAmount, Amount} from './customAmounts'; +export {default as parseCustomAmounts, CustomAmount} from './customAmounts'; export function splitParam(param:string) : string[] { return param.split(/[_;,]/); From 684214f341caa358302254ce737a35de12b88c9f Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Tue, 29 Oct 2024 13:51:26 -0500 Subject: [PATCH 149/208] update import, fix custom amounts parser specs --- .../customAmounts/JsonStringParser.spec.ts | 65 +++++++++++++------ .../donate/parseFields/customAmounts/index.ts | 2 +- 2 files changed, 47 insertions(+), 20 deletions(-) diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts index 014536f3f..573275cc6 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.spec.ts @@ -2,13 +2,14 @@ import JsonStringParser from './JsonStringParser'; describe('JsonStringParser', () => { - describe.each([ - ["with bracket", "["], - ["with brace", "[{]"], - ["with no amount given", "[{name:'name', label: 'LABEL'}]"], - ])("when invalid %s", (_n, input)=> { - const parser = new JsonStringParser(input) + ['with bracket', '['], + ['with brace', '[{]'], + ['without brackets', '2,3,4'], + ['with letters', '[letters]'], + ['with no amount given', "[{highlight: 'car'}]"], + ])('when invalid %s', (_n, input) => { + const parser = new JsonStringParser(input); it('has correct result', () => { expect(parser.results).toStrictEqual([]); }); @@ -18,25 +19,51 @@ describe('JsonStringParser', () => { }); it('is marked not valid', () => { - expect(parser.isValid).toBe(false) + expect(parser.isValid).toBe(false); }); }); describe.each([ ['when an empty array', '[]', []], - ])("when valid %s", (_name, input, result) => { - const parser = new JsonStringParser(input); + [ + 'with all numbers', + '[1,2.5,3]', + [ + { amount: 1, highlight: false }, + { amount: 2.5, highlight: false }, + { amount: 3, highlight: false }, + ], + ], + [ + 'with some numbers and some objects', + "[1,{amount:2.5,highlight:'icon'},3]", + [ + { amount: 1, highlight: false }, + { amount: 2.5, highlight: 'icon' }, + { amount: 3, highlight: false }, + ], + ], + [ + 'with objects', + "[{amount:2.5,highlight:'icon'},{amount:5}]", + [ + { amount: 2.5, highlight: 'icon' }, + { amount: 5, highlight: false }, + ], + ], + ])('when valid %s', (_name, input, result) => { + const parser = new JsonStringParser(input); - it('has no errors', () => { - expect(parser.errors).toBeEmpty(); - }); + it('has no errors', () => { + expect(parser.errors).toBeEmpty(); + }); - it('has is marked valid', () => { - expect(parser.isValid).toStrictEqual(true); - }); + it('has is marked valid', () => { + expect(parser.isValid).toStrictEqual(true); + }); - it('matches expected result', () => { - expect(parser.results).toStrictEqual(result); - }); + it('matches expected result', () => { + expect(parser.results).toStrictEqual(result); }); -}); \ No newline at end of file + }); +}); diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts index f89968318..0bdb69e1d 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts @@ -1,6 +1,6 @@ // License: LGPL-3.0-or-later import JsonStringParser from './JsonStringParser'; -const { getDefaultAmounts } = require('../../custom_amounts'); +import { getDefaultAmounts } from '../../custom_amounts'; import { splitParam } from '..'; export interface CustomAmount { From 158634e56ee6b7b7b3a45108c951dec7d44de057 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Tue, 29 Oct 2024 14:02:10 -0500 Subject: [PATCH 150/208] split up customAmount and customAmounts --- .../donate/parseFields/customAmount/index.ts | 11 +++++++++++ .../parseFields/customAmounts/JsonStringParser.ts | 6 +----- .../donate/parseFields/customAmounts/index.ts | 6 +----- 3 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 client/js/nonprofits/donate/parseFields/customAmount/index.ts diff --git a/client/js/nonprofits/donate/parseFields/customAmount/index.ts b/client/js/nonprofits/donate/parseFields/customAmount/index.ts new file mode 100644 index 000000000..412a9c11c --- /dev/null +++ b/client/js/nonprofits/donate/parseFields/customAmount/index.ts @@ -0,0 +1,11 @@ +// License: LGPL-3.0-or-later +import has from 'lodash/has'; + +export interface CustomAmount { + amount: NonNullable; + highlight: NonNullable; +} + +export function isCustomAmountObject(item: unknown): item is CustomAmount { + return typeof item == 'object' && has(item, 'amount'); +} diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts index de85a04d5..ca2034bf3 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/JsonStringParser.ts @@ -1,11 +1,7 @@ // License: LGPL-3.0-or-later -import has from 'lodash/has'; import { parse } from 'json5'; -import { CustomAmount } from '../customAmounts'; +import { CustomAmount, isCustomAmountObject } from '../customAmount'; -function isCustomAmountObject(item: unknown): item is CustomAmount { - return typeof item == 'object' && has(item, 'amount'); -} export default class JsonStringParser { public errors: SyntaxError[] = []; public readonly results: CustomAmount[] = []; diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts index 0bdb69e1d..f7c4791b8 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts @@ -1,13 +1,9 @@ // License: LGPL-3.0-or-later import JsonStringParser from './JsonStringParser'; import { getDefaultAmounts } from '../../custom_amounts'; +import { CustomAmount } from '../customAmount'; import { splitParam } from '..'; -export interface CustomAmount { - amount: NonNullable; - highlight: NonNullable; -} - export default function parseCustomAmounts(amountsString: string): CustomAmount[] { const defaultAmts = getDefaultAmounts().join(); From 33c25f63aa862394b34e54409c00c3d76215bb14 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Tue, 29 Oct 2024 14:15:06 -0500 Subject: [PATCH 151/208] fix import --- client/js/nonprofits/donate/parseFields/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/js/nonprofits/donate/parseFields/index.ts b/client/js/nonprofits/donate/parseFields/index.ts index fa6ea40dd..d3c4d1704 100644 --- a/client/js/nonprofits/donate/parseFields/index.ts +++ b/client/js/nonprofits/donate/parseFields/index.ts @@ -1,7 +1,8 @@ // License: LGPL-3.0-or-later export {default as parseCustomField, CustomFieldDescription} from "./customField"; export {default as parseCustomFields} from "./customFields"; -export {default as parseCustomAmounts, CustomAmount} from './customAmounts'; +export {default as parseCustomAmounts} from "./customAmounts"; +export { CustomAmount } from "./customAmount"; export function splitParam(param:string) : string[] { return param.split(/[_;,]/); From d93ce162d5431b4624ee73f46052fe5619c9e2af Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Wed, 30 Oct 2024 09:39:17 -0500 Subject: [PATCH 152/208] refactor to separate parseNumberAmounts --- .../donate/parseFields/customAmounts/index.ts | 11 +++---- .../customAmounts/parseNumberAmounts.spec.ts | 31 +++++++++++++++++++ .../customAmounts/parseNumberAmounts.ts | 8 +++++ 3 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.spec.ts create mode 100644 client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.ts diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts index f7c4791b8..6034a683a 100644 --- a/client/js/nonprofits/donate/parseFields/customAmounts/index.ts +++ b/client/js/nonprofits/donate/parseFields/customAmounts/index.ts @@ -2,17 +2,16 @@ import JsonStringParser from './JsonStringParser'; import { getDefaultAmounts } from '../../custom_amounts'; import { CustomAmount } from '../customAmount'; -import { splitParam } from '..'; +import parseNumberAmounts from './parseNumberAmounts'; export default function parseCustomAmounts(amountsString: string): CustomAmount[] { const defaultAmts = getDefaultAmounts().join(); if (amountsString.includes('{')) { - return new JsonStringParser(`[${amountsString}]`).results; + if (!amountsString.startsWith('[')) amountsString = `[${amountsString}`; + if (!amountsString.endsWith(']')) amountsString = `${amountsString}]`; + return new JsonStringParser(amountsString).results; } else { - const commaParams = splitParam(amountsString || defaultAmts) - .map(Number) - .join(','); - return new JsonStringParser(`[${commaParams}]`).results; + return parseNumberAmounts(amountsString || defaultAmts); } } diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.spec.ts b/client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.spec.ts new file mode 100644 index 000000000..a586f7e5e --- /dev/null +++ b/client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.spec.ts @@ -0,0 +1,31 @@ +// License: LGPL-3.0-or-later +import parseNumberAmounts from './parseNumberAmounts'; + +describe.each([ + ['when empty', '', []], + [ + 'when integers', + '1,2,3', + [ + { amount: 1, highlight: false }, + { amount: 2, highlight: false }, + { amount: 3, highlight: false }, + ], + ], + [ + 'when integers, floats, and spaces', + '1,2.5,3 ,456', + [ + { amount: 1, highlight: false }, + { amount: 2.5, highlight: false }, + { amount: 3, highlight: false }, + { amount: 456, highlight: false }, + ], + ], +])('parseCustomField', (name, input, result) => { + describe(name, () => { + it('maps the numbers as expected', () => { + expect(parseNumberAmounts(input)).toStrictEqual(result); + }); + }); +}); diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.ts b/client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.ts new file mode 100644 index 000000000..3bf3206af --- /dev/null +++ b/client/js/nonprofits/donate/parseFields/customAmounts/parseNumberAmounts.ts @@ -0,0 +1,8 @@ +// License: LGPL-3.0-or-later +import { CustomAmount } from '../customAmount'; +import { splitParam } from '..'; + +export default function parseNumberAmounts(amountsString: string): CustomAmount[] { + if (amountsString.length === 0) return []; + return splitParam(amountsString).map((n) => ({ amount: Number(n), highlight: false })); +} From 26f6a9ce9fed2c0e1592bbb25ca1d10aa9a1ec6e Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Wed, 30 Oct 2024 10:21:49 -0500 Subject: [PATCH 153/208] add tests for parseCustomAmounts --- .../parseFields/customAmounts/index.spec.ts | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 client/js/nonprofits/donate/parseFields/customAmounts/index.spec.ts diff --git a/client/js/nonprofits/donate/parseFields/customAmounts/index.spec.ts b/client/js/nonprofits/donate/parseFields/customAmounts/index.spec.ts new file mode 100644 index 000000000..df4e05ad7 --- /dev/null +++ b/client/js/nonprofits/donate/parseFields/customAmounts/index.spec.ts @@ -0,0 +1,67 @@ +// License: LGPL-3.0-or-later +import parseCustomAmounts from '.'; +import { getDefaultAmounts } from '../../custom_amounts'; + +describe.each([ + ['maps default amounts', '', getDefaultAmounts().map((a: number) => ({ amount: a, highlight: false }))], + [ + 'maps integers correctly', + '1,2,3', + [ + { amount: 1, highlight: false }, + { amount: 2, highlight: false }, + { amount: 3, highlight: false }, + ], + ], + [ + 'accepts integers, floats, and extraneous spaces', + '1, 2.5,3 ,456', + [ + { amount: 1, highlight: false }, + { amount: 2.5, highlight: false }, + { amount: 3, highlight: false }, + { amount: 456, highlight: false }, + ], + ], + [ + 'accepts a mix of numbers and objects with amounts and highlights', + '1, {amount: 2.5, highlight:"icon"},3', + [ + { amount: 1, highlight: false }, + { amount: 2.5, highlight: 'icon' }, + { amount: 3, highlight: false }, + ], + ], + [ + 'omits invalid objects', + '1, {highlight: "icon"},3', + [ + { amount: 1, highlight: false }, + { amount: 3, highlight: false }, + ], + ], + [ + 'accepts objects without highlights and maps them to false', + '1, {amount:2 },3', + [ + { amount: 1, highlight: false }, + { amount: 2, highlight: false }, + { amount: 3, highlight: false }, + ], + ], + [ + 'accepts mixed inputs with array brackets', + '[1,{amount:52},3]', + [ + { amount: 1, highlight: false }, + { amount: 52, highlight: false }, + { amount: 3, highlight: false }, + ], + ], +])('parseCustomField', (name, input, result) => { + describe('parseCustomAmounts', () => { + it(name, () => { + expect(parseCustomAmounts(input)).toStrictEqual(result); + }); + }); +}); From 810d895e3da2fcd52f332358721d625be8e51266 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Wed, 30 Oct 2024 10:46:15 -0500 Subject: [PATCH 154/208] stop titlerow from overlapping border of widget --- app/assets/stylesheets/nonprofits/donation_form/title_row.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss index a882b2716..a17227488 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss @@ -8,6 +8,7 @@ background: $fog; display: flex; gap: 15px; + @include border-radius(3px 3px 0 0); } .titleRow-info { max-width: 85%; From 601a8ff4b5ba172c58947293be1988ec13006d18 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 30 Oct 2024 12:07:55 -0500 Subject: [PATCH 155/208] Add Eric as CODEOWNER --- .github/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..1a4068f23 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,2 @@ +# put Eric as codeowner so he is autoadded to review every PR +* @wwahammy \ No newline at end of file From e035be8f576c4de467616e0f54ab262e11d53805 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Wed, 6 Nov 2024 16:52:03 -0600 Subject: [PATCH 156/208] use flexbox for form field widths in widget --- .../stylesheets/components/cards.scss.erb | 14 ++++++++++++ .../nonprofits/donation_form/form.scss | 22 +++++++++++++++++++ .../components/address-autocomplete-fields.js | 16 +++++++------- client/js/components/card-form.es6 | 6 ++--- client/js/components/supporter-fields.js | 6 ++--- 5 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/components/cards.scss.erb b/app/assets/stylesheets/components/cards.scss.erb index a3828397e..b96d0457d 100755 --- a/app/assets/stylesheets/components/cards.scss.erb +++ b/app/assets/stylesheets/components/cards.scss.erb @@ -33,3 +33,17 @@ &.mastercard { background-position: 64px; } &.discovercard { background-position: 96px; } } + +<%# currently only the /components/card-form.es6 form for the widget uses this %> +.cardForm .name-zip { + display: flex; + + fieldset.name { + flex: 7; + } + + fieldset.zip { + flex: 5; + } + +} \ No newline at end of file diff --git a/app/assets/stylesheets/nonprofits/donation_form/form.scss b/app/assets/stylesheets/nonprofits/donation_form/form.scss index 1269cbf5b..f80acf2c0 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/form.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/form.scss @@ -101,6 +101,28 @@ $mint-milk : #E7F3ED; padding: 0; } +.info-step section.group { + display: flex; +} + +.info-step div.address { + display: flex; + flex-wrap: wrap; + + fieldset { + flex: 33.33%; + } + fieldset:first-child { + flex: 66.66%; + } + p.search-link { + flex: 100%; + text-align: center; + padding-top: 5px; + } +} + + @media screen and (max-width: 350px) { .wizard-steps { diff --git a/client/js/components/address-autocomplete-fields.js b/client/js/components/address-autocomplete-fields.js index 113b4fc88..e532e2d93 100644 --- a/client/js/components/address-autocomplete-fields.js +++ b/client/js/components/address-autocomplete-fields.js @@ -36,7 +36,7 @@ function calculateToShip(state) } function view(state) { - return h('section.u-padding--5.pastelBox--grey clearfix', [ + return h('section.u-padding--5.pastelBox--grey', [ calculateToShip(state) ? h('label.u-centered.u-marginBottom--5', [ 'Shipping address (required)' @@ -61,8 +61,8 @@ const autoField = state => { } const manualFields = state => { - return h('div', [ - h('fieldset.col-8.u-fontSize--14', [ + return h('div.address', [ + h('fieldset.u-fontSize--14', [ h('input.u-marginBottom--0', {props: { type: 'text' , title: 'Street Addresss' @@ -72,7 +72,7 @@ const manualFields = state => { , required: calculateToShip(state) ? state.params$().gift_option.to_ship : undefined }}) ]) - , h('fieldset.col-right-4.u-fontSize--15', [ + , h('fieldset.u-fontSize--15', [ h('input.u-marginBottom--0', {props: { type: 'text' , name: 'city' @@ -82,7 +82,7 @@ const manualFields = state => { , required: calculateToShip(state) ? state.params$().gift_option.to_ship : undefined }}) ]) - , h('fieldset.u-marginBottom--0.u-floatL.col-4', [ + , h('fieldset.u-marginBottom--0', [ h('input.u-marginBottom--0', {props: { type: 'text' , name: 'state_code' @@ -92,7 +92,7 @@ const manualFields = state => { , required: calculateToShip(state) ? state.params$().gift_option.to_ship : undefined }}) ]) - , h('fieldset.u-marginBottom--0.u-floatL.col-right-4.u-fontSize--14', [ + , h('fieldset.u-marginBottom--0.u-fontSize--14', [ h('input.u-marginBottom--0', {props: { type: 'text' , title: 'Zip/Postal' @@ -102,7 +102,7 @@ const manualFields = state => { , required: calculateToShip(state) ? state.params$().gift_option.to_ship : undefined }}) ]) - , h('fieldset.u-marginBottom--0.u-floatL.col-right-4', [ + , h('fieldset.u-marginBottom--0', [ h('input.u-marginBottom--0', {props: { type: 'text' , title: 'Country' @@ -111,7 +111,7 @@ const manualFields = state => { , value: state.data$().country , required: calculateToShip(state) ? state.params$().gift_option.to_ship : undefined }}) - ]), h('p.u-margin--0.u-centered', { style: { display: !!app.autocomplete ? 'block' : 'none' } }, [ + ]), h('p.u-margin--0.search-link', { style: { display: !!app.autocomplete ? 'block' : 'none' } }, [ h('a', {on: {click: [state.isManual$, false]}}, [h('small', 'Search for your address')]) ]) ]) diff --git a/client/js/components/card-form.es6 b/client/js/components/card-form.es6 index ba6f989a7..6628e72b1 100644 --- a/client/js/components/card-form.es6 +++ b/client/js/components/card-form.es6 @@ -138,7 +138,7 @@ const view = state => { var field = validatedForm.field(state.form) return validatedForm.form(state.form, h('form.cardForm', [ h('div', [ - h('section.group', [ + h('section.group.name-zip', [ nameInput(field, state.card$().name) , zipInput(field, state.card$().address_zip) ]) @@ -199,11 +199,11 @@ function feeCoverageField(state) { } const nameInput = (field, name) => - h('fieldset.col-7', [field(h('input', { props: { name: 'name', value: name || '', placeholder: I18n.t('nonprofits.donate.payment.card.name') } }))]) + h('fieldset.name', [field(h('input', { props: { name: 'name', value: name || '', placeholder: I18n.t('nonprofits.donate.payment.card.name') } }))]) const zipInput = (field, zip) => - h('fieldset.col-right-5', [ + h('fieldset.zip', [ field(h('input' , { props: { type: 'text' diff --git a/client/js/components/supporter-fields.js b/client/js/components/supporter-fields.js index 5662c634e..0b15318e8 100644 --- a/client/js/components/supporter-fields.js +++ b/client/js/components/supporter-fields.js @@ -63,7 +63,7 @@ function view(state) { }) ]) , h('section.group', [ - h('fieldset.u-marginBottom--0.u-floatL.col-right-4', [ + h('fieldset', [ h('input', { props: { type: 'text' @@ -75,7 +75,7 @@ function view(state) { } }) ]) - , h('fieldset.u-marginBottom--0.u-floatL.col-right-4', [ + , h('fieldset', [ h('input', { props: { type: 'text' @@ -87,7 +87,7 @@ function view(state) { } }) ]) - , h('fieldset.u-marginBottom--0.u-floatL.col-right-4', [ + , h('fieldset', [ h('input', { props: { type: 'text' From afabf06a1455a46400a7fe8a8a41d6b2016fdb92 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 7 Nov 2024 08:56:24 -0600 Subject: [PATCH 157/208] remove unused functions from supporter-fields --- client/js/components/supporter-fields.js | 65 ------------------------ 1 file changed, 65 deletions(-) diff --git a/client/js/components/supporter-fields.js b/client/js/components/supporter-fields.js index 5662c634e..58eadb2fd 100644 --- a/client/js/components/supporter-fields.js +++ b/client/js/components/supporter-fields.js @@ -104,69 +104,4 @@ function view(state) { ]) } -function manualAddressFields(state) { - state.selectCountry$ = state.selectCountry$ || flyd.stream() - var stateOptions = R.prepend( - h('option', {props: {value: '', disabled: true, selected: true}}, I18n.t('nonprofits.donate.info.supporter.state')) - , R.map( - s => h('option', {props: {selected: state.supporter.state_code === s, value: s}}, s) - , geography.stateCodes ) - ) - var countryOptions = R.prepend( - h('option', {props: {value: '', disabled: true, selected: true}}, I18n.t('nonprofits.donate.info.supporter.country')) - , R.map( - c => h('option', {props: {value: c[0]}}, c[1]) - , app.countriesList ) -) - return h('section.group.pastelBox--grey.u-padding--5', [ - state.to_ship ? h('label.u-centered.u-marginBottom--5', I18n.t('nonprofits.donate.info.supporter.shipping_address')) : '' - , h('fieldset.col-8.u-fontSize--14', [ - h('input.u-marginBottom--0', { - props: { - title: 'Address' - , placeholder: I18n.t('nonprofits.donate.info.supporter.address') - , type: 'text' - , name: 'address' - , value: state.supporter.address - } - }) - ]) - , h('fieldset.col-right-4.u-fontSize--14', [ - h('input.u-marginBottom--0', { - props: { - name: 'city' - , type: 'text' - , placeholder: I18n.t('nonprofits.donate.info.supporter.city') - , title: 'City' - , value: state.supporter.city - } - }) - ]) - , state.notUSA$() - ? showRegionField() - : h('fieldset.u-marginBottom--0.u-floatL.col-4', [ - h('select.select.u-fontSize--14.u-marginBottom--0', {props: {name: 'state_code'}}, stateOptions) - ]) - , h('fieldset.u-marginBottom--0.u-floatL.col-right-4.u-fontSize--14', [ - h('input.u-marginBottom--0', { - props: {type: 'text', title: 'Postal code', name: 'zip_code', placeholder: I18n.t('nonprofits.donate.info.supporter.postal_code'), value: state.supporter.zip_code} - }) - ]) - , h('fieldset.u-marginBottom--0.u-floatL.col-right-8', [ - h('select.select.u-fontSize--14.u-marginBottom--0', { - props: { name: 'country' } - , on: {change: ev => state.selectCountry$(ev.currentTarget)} - }, countryOptions ) - ]) - ]) -} - -function showRegionField() { - if(app.show_state_field) { - h('input.u-marginBottom--0.u-floatL.col-4', {props: {type: 'text', title: 'Region', name: 'region', placeholder: I18n.t('nonprofits.donate.info.supporter.region'), value: state.supporter.state_code}}) - } else { - return "" - } -} - module.exports = {view, init} From 228454c9bcbe2f0118515ff694f58a6c9ad3295f Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 7 Nov 2024 09:35:08 -0600 Subject: [PATCH 158/208] use css grid for amount buttons in widget only --- app/assets/stylesheets/common/layouts.scss | 12 ++++++++++++ client/js/nonprofits/donate/amount-step.js | 6 ++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/common/layouts.scss b/app/assets/stylesheets/common/layouts.scss index dd8035996..dc24b6a27 100644 --- a/app/assets/stylesheets/common/layouts.scss +++ b/app/assets/stylesheets/common/layouts.scss @@ -164,6 +164,18 @@ & > *:nth-of-type(3n) { padding:0 0 0 5px; } } +.fieldset-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 10px 0px; + padding-bottom: 10px; + + > fieldset { + margin: 0; + padding: 0 5px; + } +} + .fieldsetLayout--three--evenPadding { @include fieldsetLayout; fieldset { diff --git a/client/js/nonprofits/donate/amount-step.js b/client/js/nonprofits/donate/amount-step.js index 0ed3f69ad..85ebeaf0e 100644 --- a/client/js/nonprofits/donate/amount-step.js +++ b/client/js/nonprofits/donate/amount-step.js @@ -152,9 +152,8 @@ function amountFields(state) { if(state.params$().single_amount) return [''] const postfix = getPostfixElement(); return [ - h('div.fieldsetLayout--three--evenPadding', [ - h('span', - R.map( + h('div.fieldset-grid', [ + ...R.map( amt => h('fieldset', [ h('button.button.u-width--full.white.amount', { class: {'is-selected': state.buttonAmountSelected$() && state.donation$().amount === amt.amount*100} @@ -166,7 +165,6 @@ function amountFields(state) { }, amount_button_contents(app.currency_symbol, amt)) ]) , (state.params$().custom_amounts || []).map((a) => getAmt(a)) ) - ) , h('fieldset.' + prependCurrencyClassname(), [ h('input.amount.other', { props: {name: 'amount', step: 'any', type: 'number', min: 1, placeholder: I18n.t('nonprofits.donate.amount.custom')} From bf1f4a36f0f3f93f088b47e5f98fa984ceb8c6eb Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 7 Nov 2024 11:37:39 -0600 Subject: [PATCH 159/208] use css grid for amount buttons in update recurring --- client/js/recurring_donations/edit/amount-step.es6 | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/client/js/recurring_donations/edit/amount-step.es6 b/client/js/recurring_donations/edit/amount-step.es6 index c6596d96d..d837962d2 100644 --- a/client/js/recurring_donations/edit/amount-step.es6 +++ b/client/js/recurring_donations/edit/amount-step.es6 @@ -69,9 +69,8 @@ function chooseNewDonationAmount() { // All the buttons and the custom input for the amounts to select function amountFields(state) { if(state.params$().single_amount) return '' - return h('div.u-inline.fieldsetLayout--three--evenPadding', [ - h('span', - R.map( + return h('div.fieldset-grid', [ + ...R.map( amt => h('fieldset', [ h('button.button.u-width--full.white.amount', { class: {'is-selected': state.buttonAmountSelected$() && state.donation$().amount === amt*100} @@ -86,7 +85,6 @@ function amountFields(state) { ]) ]) , state.params$().custom_amounts || [] ) - ) , h('fieldset.prepend--dollar', [ h('input.amount', { props: {name: 'amount', step: 'any', type: 'number', min: 1, placeholder: 'Custom'} From 8151cf18182dea389d3dc22f2e5e1bbaddf6e867 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 7 Nov 2024 12:22:37 -0600 Subject: [PATCH 160/208] use flexbox for wizard steps --- app/assets/stylesheets/components/wizard_index.scss | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/components/wizard_index.scss b/app/assets/stylesheets/components/wizard_index.scss index 06dada72a..f78b26797 100644 --- a/app/assets/stylesheets/components/wizard_index.scss +++ b/app/assets/stylesheets/components/wizard_index.scss @@ -11,6 +11,8 @@ $chevron_height: 34px; width: 100%; overflow: hidden; margin: 0; + display: flex; + flex-direction: row; } [data-ff-wizard-content-wrapper], @@ -26,7 +28,7 @@ $chevron_height: 34px; .ff-wizard-index-label, .wizard-index-label { margin: 0; - float: left; + display: inline-block; text-align: center; position: relative; padding-right: $chevron_height/2; From d3d5529e2f8f88305d1fd4ee232ce15031167eba Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Thu, 7 Nov 2024 13:28:20 -0800 Subject: [PATCH 161/208] Initial work from public repo --- Dockerfile | 68 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 23 ++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..9ed665dbb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,68 @@ +# syntax=docker/dockerfile:1 +ARG BASE_IMAGE=ruby +ARG BASE_TAG=2.7.6-slim +ARG BASE=${BASE_IMAGE}:${BASE_TAG} + +FROM ${BASE} AS builder + +ENV LANG en_US.UTF-8 + +RUN apt-get update -qq \ + && apt-get install -y \ + build-essential \ + ca-certificates \ + curl \ + tzdata \ + git \ + libpq-dev \ + nodejs \ + yarn \ + && curl -sL https://deb.nodesource.com/setup_16.x | bash \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" \ + > /etc/apt/sources.list.d/yarn.list \ + && apt-get update -qq \ + && apt-get install -y nodejs yarn \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +ARG RAILS_ROOT=/app/ + +RUN mkdir ${RAILS_ROOT} +WORKDIR ${RAILS_ROOT} + +COPY Gemfile* ${RAILS_ROOT} +ADD gems ${RAILS_ROOT}gems + +RUN bundle install -j4 --retry 3 + +COPY package.json yarn.lock ${RAILS_ROOT} +RUN yarn install + +COPY . ${RAILS_ROOT} + +RUN bundle exec rake assets:precompile + +FROM ${BASE} + +ENV LANG en_US.UTF-8 + +RUN apt-get update -qq \ + && apt-get install -y libjemalloc2 postgresql-client tzdata libv8-dev nodejs + +RUN groupadd --gid 1000 app && \ + useradd --uid 1000 --no-log-init --create-home --gid app app +USER app + +COPY --from=builder --chown=app:app /usr/local/bundle/ /usr/local/bundle/ +COPY --from=builder --chown=app:app /app /app + +ENV RAILS_ENV=development +ENV RAILS_LOG_TO_STDOUT true +ENV RAILS_SERVE_STATIC_FILES true +ENV PORT 3000 +ARG RAILS_ROOT=/app/ + +WORKDIR $RAILS_ROOT +RUN mkdir -p tmp/pids +CMD bundle exec puma -p $PORT -C ./config/puma.rb \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 000000000..ec1ab27cc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,23 @@ +version: "3.9" +services: + db: + image: postgres:latest + restart: always + ports: + - "5432:5432" + volumes: + - ~/tmp/houdini/db:/var/lib/postgresql/data + environment: + - POSTGRES_USER=houdini_user + - POSTGRES_PASSWORD=password + - POSTGRES_DB=houdini_development + web: + build: . + volumes: + - .:/app + ports: + - "3000:3000" + environment: + - DATABASE_HOST=db + depends_on: + - db \ No newline at end of file From 5165cd4696fedc356070fbadf555690a7140529e Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Fri, 8 Nov 2024 10:26:58 -0600 Subject: [PATCH 162/208] remove logged-in widget footer --- .../stylesheets/common/donate_button.scss | 1 - .../nonprofits/donation_form/footer.scss | 12 ---------- client/js/nonprofits/donate/wizard.js | 7 ------ spec/js/nonprofits/donate/wizard-spec.js | 22 ------------------- 4 files changed, 42 deletions(-) delete mode 100644 app/assets/stylesheets/nonprofits/donation_form/footer.scss diff --git a/app/assets/stylesheets/common/donate_button.scss b/app/assets/stylesheets/common/donate_button.scss index e4b6bf533..c9c84a8c9 100644 --- a/app/assets/stylesheets/common/donate_button.scss +++ b/app/assets/stylesheets/common/donate_button.scss @@ -1,6 +1,5 @@ /* License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later */ @import 'mixins'; @import 'nonprofits/donation_form/title_row'; -@import 'nonprofits/donation_form/footer'; @import 'nonprofits/donation_form/form'; // for styling the actual form @import 'nonprofits/donation_form/show/index'; // for styling the layout on /donate diff --git a/app/assets/stylesheets/nonprofits/donation_form/footer.scss b/app/assets/stylesheets/nonprofits/donation_form/footer.scss deleted file mode 100644 index 417879ba7..000000000 --- a/app/assets/stylesheets/nonprofits/donation_form/footer.scss +++ /dev/null @@ -1,12 +0,0 @@ -/* License: AGPL-3.0-or-later WITH Web-Template-Output-Additional-Permission-3.0-or-later */ -@import 'mixins'; - -.donateForm-footer { - background: $fog; - font-size: 12px; - text-align: center; - padding: 7px; - @include ellipsis; - width: 100%; - color: $grey; -} diff --git a/client/js/nonprofits/donate/wizard.js b/client/js/nonprofits/donate/wizard.js index 546915d47..71750d663 100644 --- a/client/js/nonprofits/donate/wizard.js +++ b/client/js/nonprofits/donate/wizard.js @@ -190,13 +190,6 @@ const view = state => { , h('div.titleRow-info', titleInfo(state)) ]) , wizardWrapper(state) - , h('footer.donateForm-footer', { - class: {'u-hide': !app.user} - }, [ - h('span', `${I18n.t('nonprofits.donate.signed_in')} `) - , h('strong', String(app.user && app.user.email)) - , h('a.logout-button', {on: {click: state.clickLogout$}}, ` ${I18n.t('nonprofits.donate.log_out')}`) - ]) ]) } diff --git a/spec/js/nonprofits/donate/wizard-spec.js b/spec/js/nonprofits/donate/wizard-spec.js index 1cbd9e77e..e97e489a1 100644 --- a/spec/js/nonprofits/donate/wizard-spec.js +++ b/spec/js/nonprofits/donate/wizard-spec.js @@ -92,25 +92,3 @@ test('shows the tagline if designation param set and single amount set', ()=> { let streams = init(flyd.stream({designation, single_amount: 1000})) assert.equal(streams.dom$().querySelector('.titleRow-info p').textContent, app.nonprofit.tagline) }) - -test('hides the footer if no user is in the env', () => { - let streams = init() - const idx = streams.dom$().querySelector('.donateForm-footer').className.indexOf('hide') - assert.notEqual(idx, -1) -}) - -test('shows the footer if a user is in the env', () => { - app.user = {email: 'user@example.com', id: 1} - let streams = init() - const idx = streams.dom$().querySelector('.donateForm-footer').className.indexOf('hide') - assert.equal(idx, -1) - app.user = {} -}) - -test('shows user info text if a user is in the env', () => { - app.user = {email: 'user@example.com', id: 1} - let streams = init() - const text = streams.dom$().querySelector('.donateForm-footer').textContent - assert.equal(text, 'Signed in as user@example.com Logout') - app.user = {} -}) From 82900c03cef21880c3e771062102ab2a0e871552 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 11 Nov 2024 10:25:54 -0500 Subject: [PATCH 163/208] fix widget dedication form layout --- app/assets/stylesheets/nonprofits/donation_form/form.scss | 6 ------ client/js/nonprofits/donate/info-step.js | 7 ++++--- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/nonprofits/donation_form/form.scss b/app/assets/stylesheets/nonprofits/donation_form/form.scss index f80acf2c0..af24613a0 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/form.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/form.scss @@ -87,12 +87,6 @@ $mint-milk : #E7F3ED; margin: 15px 0 20px 0; } -.dedication-form { - background: #fcfcfc; - padding: 10px; - border-radius: 4px; -} - .donationModal .ff-modal { width: 480px; margin-left: -240px; diff --git a/client/js/nonprofits/donate/info-step.js b/client/js/nonprofits/donate/info-step.js index 3403d373b..d80e5b3f1 100644 --- a/client/js/nonprofits/donate/info-step.js +++ b/client/js/nonprofits/donate/info-step.js @@ -91,22 +91,23 @@ function recurringMessage(state){ function view(state) { var form = h('form', { + class: {'u-hide': state.showDedicationForm$()}, on: { submit: ev => {ev.preventDefault(); state.currentStep$(2); state.submitSupporter$(ev.currentTarget)} } }, [ - recurringMessage(state) + recurringMessage(state) , supporterFields.view(state.supporterFields) , customFields(state.params$().custom_fields) , dedicationLink(state) , anonField(state) , h('div', paymentMethodButtons(["card"], state)) ]) + return h('div.wizard-step.info-step.u-padding--10', [ form , h('div', { - style: {background: '#f8f8f8', position: 'absolute', 'top': '0', left: '3px', height: '100%', width: '99%'} - , class: {'u-hide': !state.showDedicationForm$(), opacity: 0, transition: 'opacity 1s', delay: {opacity: 1}} + class: { 'u-hide': !state.showDedicationForm$() } }, [dedicationForm.view(state)] ) ]) } From adc75f46e1e389a3822aaeac707c760acf8936c9 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 11 Nov 2024 12:15:30 -0500 Subject: [PATCH 164/208] in widget, make textarea styles match input styles --- .../stylesheets/nonprofits/donation_form/form.scss | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/assets/stylesheets/nonprofits/donation_form/form.scss b/app/assets/stylesheets/nonprofits/donation_form/form.scss index af24613a0..4a512cab7 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/form.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/form.scss @@ -116,6 +116,16 @@ $mint-milk : #E7F3ED; } } +// making textarea consistent with other form inputs here +// intentionally just changing this in the widget for now +.info-step form textarea { + font-size: 16px; + border: 1px solid lighten($grey, 35%);; + border-bottom: 2px solid lighten($grey, 35%); + &:focus { + border-bottom: 2px solid lighten($grey, 15%); + } +} @media screen and (max-width: 350px) { From e02646bd9a1769095733b4c772f2dd2722190804 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Tue, 12 Nov 2024 10:09:31 -0500 Subject: [PATCH 165/208] update widget dedication form to flexbox layout --- .../nonprofits/donation_form/form.scss | 15 +++++++++++++++ client/js/nonprofits/donate/dedication-form.js | 12 ++++++------ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/nonprofits/donation_form/form.scss b/app/assets/stylesheets/nonprofits/donation_form/form.scss index 4a512cab7..5c036c473 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/form.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/form.scss @@ -127,6 +127,18 @@ $mint-milk : #E7F3ED; } } +// dedication form +.info-step .dedication-form { + display: flex; + flex-wrap: wrap; + > * { + flex: 100%; + } + > .half-width { + flex: 50%; + } +} + @media screen and (max-width: 350px) { .wizard-steps { @@ -141,4 +153,7 @@ $mint-milk : #E7F3ED; .wizard-steps .amount-step .custom-amount .button--inset { padding: 0 5px; } + .info-step form textarea { + font-size: 13px; + } } diff --git a/client/js/nonprofits/donate/dedication-form.js b/client/js/nonprofits/donate/dedication-form.js index 31d80f960..6b8a673c9 100644 --- a/client/js/nonprofits/donate/dedication-form.js +++ b/client/js/nonprofits/donate/dedication-form.js @@ -13,7 +13,7 @@ function view(state) { on: {submit: ev => {ev.preventDefault(); state.submitDedication$(ev.currentTarget)}} }, [ h('p.u-centered.u-strong.u-marginBottom--10', I18n.t('nonprofits.donate.dedication.info')) - , h('fieldset.u-marginBottom--0.col-6', [ + , h('fieldset.u-marginBottom--0.half-width', [ h('input', {props: { name: 'dedication_type' , type: 'radio' @@ -23,7 +23,7 @@ function view(state) { }}) , h('label', {props: {htmlFor: radioId1}}, I18n.t('nonprofits.donate.dedication.in_honor_label')) ]) - , h('fieldset.u-marginBottom--0', [ + , h('fieldset.u-marginBottom--0.half-width', [ h('input', {props: { name: 'dedication_type' , type: 'radio' @@ -33,7 +33,7 @@ function view(state) { }}) , h('label', {props: {htmlFor: radioId2}}, I18n.t('nonprofits.donate.dedication.in_memory_label')) ]) - , h('fieldset.u-marginBottom--0.col-6', [ + , h('fieldset.u-marginBottom--0.half-width', [ h('input', {props: { name: 'first_name' , placeholder: I18n.t('nonprofits.donate.dedication.first_name') @@ -42,7 +42,7 @@ function view(state) { , value: data.first_name }}) ]) - , h('fieldset.u-marginBottom--0', [ + , h('fieldset.u-marginBottom--0.half-width', [ h('input', {props: { name: 'last_name' , placeholder: I18n.t('nonprofits.donate.dedication.last_name') @@ -51,7 +51,7 @@ function view(state) { , value: data.last_name }}) ]) - , h('fieldset.u-marginBottom--0.col-6', [ + , h('fieldset.u-marginBottom--0.half-width', [ h('input', {props: { name: 'email' , placeholder: I18n.t('nonprofits.donate.dedication.email') @@ -60,7 +60,7 @@ function view(state) { , value: data.email }}) ]) - , h('fieldset.u-marginBottom--0', [ + , h('fieldset.u-marginBottom--0.half-width', [ h('input', {props: { name: 'phone' , placeholder: I18n.t('nonprofits.donate.dedication.phone') From 969fd4451cee4220bddf619b66bf44a7e7568a3a Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Tue, 12 Nov 2024 11:59:43 -0500 Subject: [PATCH 166/208] dedication link: more styling in css, fix wrapping --- .../stylesheets/nonprofits/donation_form/form.scss | 9 +++++++++ client/js/nonprofits/donate/info-step.js | 6 ++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/nonprofits/donation_form/form.scss b/app/assets/stylesheets/nonprofits/donation_form/form.scss index 5c036c473..fa02f4e89 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/form.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/form.scss @@ -127,6 +127,15 @@ $mint-milk : #E7F3ED; } } +.info-step .dedication-link { + border-width: thin; + border-style: solid; + padding: 2px; + border-radius: 2px; + display: block; + text-wrap: balance; +} + // dedication form .info-step .dedication-form { display: flex; diff --git a/client/js/nonprofits/donate/info-step.js b/client/js/nonprofits/donate/info-step.js index d80e5b3f1..4eed73d40 100644 --- a/client/js/nonprofits/donate/info-step.js +++ b/client/js/nonprofits/donate/info-step.js @@ -168,9 +168,11 @@ const dedicationLink = state => { if(state.params$().hide_dedication) return '' return h('label.u-centered.u-marginTop--10', [ h('small', [ - h('a', { + h('a.dedication-link', { on: { click: [state.showDedicationForm$, true] }, - style: {'border-width': 'thin', 'border-style': 'solid', padding: '2px', 'border-color': `${app.nonprofit.brand_color || '#4DAE7F'}`, 'border-radius': '2px', 'box-shadow': `3px 3px ${app.nonprofit.brand_color || '#4DAE7F'}7F`} + style: { + 'border-color': `${app.nonprofit.brand_color || '#4DAE7F'}` + , 'box-shadow': `3px 3px ${app.nonprofit.brand_color || '#4DAE7F'}7F`} }, state.dedicationData$() && state.dedicationData$().first_name ? [h('i.fa.fa-check'), I18n.t('nonprofits.donate.info.dedication_saved') + `${state.dedicationData$().first_name || ''} ${state.dedicationData$().last_name || ''}`] : [I18n.t('nonprofits.donate.info.dedication_link')] From 1e3bca0dab4ac25f57104531d853266dab080045 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 11 Nov 2024 11:49:16 -0500 Subject: [PATCH 167/208] clean up widget padding, remove box around address --- .../stylesheets/nonprofits/donation_form/form.scss | 10 +++++++--- client/js/components/address-autocomplete-fields.js | 2 +- client/js/components/supporter-fields.js | 8 ++++---- client/js/nonprofits/donate/info-step.js | 2 +- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/nonprofits/donation_form/form.scss b/app/assets/stylesheets/nonprofits/donation_form/form.scss index fa02f4e89..fece32cc5 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/form.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/form.scss @@ -42,9 +42,6 @@ $mint-milk : #E7F3ED; } // amount step -.wizard-steps .amount-step { - padding: 0 5px; -} .wizard-steps .amount-step button.is-selected { background: darken($mint-milk, 10); } @@ -95,6 +92,13 @@ $mint-milk : #E7F3ED; padding: 0; } +// info step +.info-step > form { + display: flex; + flex-direction: column; + align-items: center; +} + .info-step section.group { display: flex; } diff --git a/client/js/components/address-autocomplete-fields.js b/client/js/components/address-autocomplete-fields.js index e532e2d93..44d3ccf27 100644 --- a/client/js/components/address-autocomplete-fields.js +++ b/client/js/components/address-autocomplete-fields.js @@ -36,7 +36,7 @@ function calculateToShip(state) } function view(state) { - return h('section.u-padding--5.pastelBox--grey', [ + return h('section', [ calculateToShip(state) ? h('label.u-centered.u-marginBottom--5', [ 'Shipping address (required)' diff --git a/client/js/components/supporter-fields.js b/client/js/components/supporter-fields.js index 899000bf3..e5ff6a0b2 100644 --- a/client/js/components/supporter-fields.js +++ b/client/js/components/supporter-fields.js @@ -46,7 +46,7 @@ function init(state, params$) { // } function view(state) { const emailTitle = I18n.t('nonprofits.donate.info.supporter.email') + `${state.required.email ? `${I18n.t('nonprofits.donate.info.supporter.email_required')}` : ''}` - return h('div.u-marginY--10', [ + return h('div', [ h('input', { props: { type: 'hidden' , name: 'profile_id' , value: state.supporter.profile_id } }) , h('input', { props: { type: 'hidden' , name: 'nonprofit_id' , value: state.supporter.nonprofit_id || app.nonprofit_id } }) , h('fieldset', [ @@ -64,7 +64,7 @@ function view(state) { ]) , h('section.group', [ h('fieldset', [ - h('input', { + h('input.u-marginBottom--0', { props: { type: 'text' , name: 'first_name' @@ -76,7 +76,7 @@ function view(state) { }) ]) , h('fieldset', [ - h('input', { + h('input.u-marginBottom--0', { props: { type: 'text' , name: 'last_name' @@ -88,7 +88,7 @@ function view(state) { }) ]) , h('fieldset', [ - h('input', { + h('input.u-marginBottom--0', { props: { type: 'text' , name: 'phone' diff --git a/client/js/nonprofits/donate/info-step.js b/client/js/nonprofits/donate/info-step.js index 4eed73d40..045ae8136 100644 --- a/client/js/nonprofits/donate/info-step.js +++ b/client/js/nonprofits/donate/info-step.js @@ -104,7 +104,7 @@ function view(state) { , h('div', paymentMethodButtons(["card"], state)) ]) - return h('div.wizard-step.info-step.u-padding--10', [ + return h('div.wizard-step.info-step', [ form , h('div', { class: { 'u-hide': !state.showDedicationForm$() } From 10dbc7f350d7de8ca0809ae3fb71890c2b764d39 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Fri, 15 Nov 2024 13:21:22 -0500 Subject: [PATCH 168/208] remove titlerow background --- app/assets/stylesheets/nonprofits/donation_form/title_row.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss index a17227488..c5b026309 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/title_row.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/title_row.scss @@ -5,7 +5,6 @@ padding: 15px 15px 10px 15px; position: relative; overflow: hidden; - background: $fog; display: flex; gap: 15px; @include border-radius(3px 3px 0 0); From 76b7f297deafcb80b45e1bf9c503ecf7ec274964 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 18 Nov 2024 14:19:09 -0600 Subject: [PATCH 169/208] add gap before address fields --- app/assets/stylesheets/nonprofits/donation_form/form.scss | 7 ++++++- client/js/components/address-autocomplete-fields.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/nonprofits/donation_form/form.scss b/app/assets/stylesheets/nonprofits/donation_form/form.scss index fece32cc5..b6aeb76ef 100644 --- a/app/assets/stylesheets/nonprofits/donation_form/form.scss +++ b/app/assets/stylesheets/nonprofits/donation_form/form.scss @@ -103,7 +103,12 @@ $mint-milk : #E7F3ED; display: flex; } -.info-step div.address { +.info-step section.address { + padding-top: 10px; +} + + +.info-step .manual-address-fields { display: flex; flex-wrap: wrap; diff --git a/client/js/components/address-autocomplete-fields.js b/client/js/components/address-autocomplete-fields.js index 44d3ccf27..f78d3eba3 100644 --- a/client/js/components/address-autocomplete-fields.js +++ b/client/js/components/address-autocomplete-fields.js @@ -36,7 +36,7 @@ function calculateToShip(state) } function view(state) { - return h('section', [ + return h('section.address', [ calculateToShip(state) ? h('label.u-centered.u-marginBottom--5', [ 'Shipping address (required)' @@ -61,7 +61,7 @@ const autoField = state => { } const manualFields = state => { - return h('div.address', [ + return h('div.manual-address-fields', [ h('fieldset.u-fontSize--14', [ h('input.u-marginBottom--0', {props: { type: 'text' From 72fbbb0412e1d499bb2dcfa6489500cf8891ca2d Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Mon, 18 Nov 2024 16:07:51 -0800 Subject: [PATCH 170/208] Initial pass at SfG-izing it --- .dockerignore | 5 +++++ Dockerfile | 8 ++++---- README.md | 7 +++++++ docker-compose.yml | 7 ++++--- 4 files changed, 20 insertions(+), 7 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..0fec179f3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +.env +.env.test +Dockerfile +docker-compose.yml +README.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 9ed665dbb..453a6012a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 ARG BASE_IMAGE=ruby -ARG BASE_TAG=2.7.6-slim +ARG BASE_TAG=2.6.10-slim ARG BASE=${BASE_IMAGE}:${BASE_TAG} FROM ${BASE} AS builder @@ -34,14 +34,14 @@ WORKDIR ${RAILS_ROOT} COPY Gemfile* ${RAILS_ROOT} ADD gems ${RAILS_ROOT}gems -RUN bundle install -j4 --retry 3 +RUN bundle update --bundler && bundle install -j4 --retry 3 COPY package.json yarn.lock ${RAILS_ROOT} RUN yarn install COPY . ${RAILS_ROOT} -RUN bundle exec rake assets:precompile +# RUN bundle exec rake assets:precompile FROM ${BASE} @@ -65,4 +65,4 @@ ARG RAILS_ROOT=/app/ WORKDIR $RAILS_ROOT RUN mkdir -p tmp/pids -CMD bundle exec puma -p $PORT -C ./config/puma.rb \ No newline at end of file +CMD echo $PORT && foreman start \ No newline at end of file diff --git a/README.md b/README.md index bc62d74b2..499056117 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,13 @@ echo '[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion" # This # Reference Stack Overflow post: https://stackoverflow.com/questions/53118850/brew-install-nvm-nvm-command-not-found ``` +Make sure you've installed Yarn. + +```bash +yarn --version +brew install yarn +``` + Set your Postgres version with homebrew. ```bash diff --git a/docker-compose.yml b/docker-compose.yml index ec1ab27cc..956852527 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,9 +8,9 @@ services: volumes: - ~/tmp/houdini/db:/var/lib/postgresql/data environment: - - POSTGRES_USER=houdini_user + - POSTGRES_USER=admin - POSTGRES_PASSWORD=password - - POSTGRES_DB=houdini_development + - POSTGRES_DB=commitchange_development_legacy web: build: . volumes: @@ -20,4 +20,5 @@ services: environment: - DATABASE_HOST=db depends_on: - - db \ No newline at end of file + - db + env_file: ".env" \ No newline at end of file From e1a1229ad93304bc112fc0661f2feb2c6132b8a6 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Wed, 20 Nov 2024 14:49:37 -0800 Subject: [PATCH 171/208] working on bare metal mac --- .dockerignore | 6 +++++- Dockerfile | 12 ++++++++++-- README.md | 9 +++++++++ config/database.yml | 6 +++--- docker-compose.yml | 7 ++++--- 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/.dockerignore b/.dockerignore index 0fec179f3..765261a76 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,8 @@ .env.test Dockerfile docker-compose.yml -README.md \ No newline at end of file +README.md +.dockerignore +tmp +log +node_modules \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 453a6012a..39c1ebbb6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -48,7 +48,15 @@ FROM ${BASE} ENV LANG en_US.UTF-8 RUN apt-get update -qq \ - && apt-get install -y libjemalloc2 postgresql-client tzdata libv8-dev nodejs + && apt-get install -y libjemalloc2 postgresql-client tzdata libv8-dev curl default-jre \ + && curl -sL https://deb.nodesource.com/setup_16.x | bash \ + && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ + && echo "deb https://dl.yarnpkg.com/debian/ stable main" \ + > /etc/apt/sources.list.d/yarn.list \ + && apt-get update -qq \ + && apt-get install -y nodejs yarn \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* RUN groupadd --gid 1000 app && \ useradd --uid 1000 --no-log-init --create-home --gid app app @@ -65,4 +73,4 @@ ARG RAILS_ROOT=/app/ WORKDIR $RAILS_ROOT RUN mkdir -p tmp/pids -CMD echo $PORT && foreman start \ No newline at end of file +CMD foreman start \ No newline at end of file diff --git a/README.md b/README.md index 499056117..4b1dee48e 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,13 @@ brew services start postgresql@12 ``` +You might get segmentation faults if you don't configure `pg` with the correct macports. One of these should work. +```bash +gem install pg -- --with-pg-config="${HOMEBREW_PREFIX}/opt/libpq/bin/pg_config" +# Or? +bundle config build.pg --with-pg-config="${HOMEBREW_PREFIX}/opt/libpq/bin/pg_config" +``` + Create necessary postgres users in the `psql` console. ```bash @@ -143,6 +150,8 @@ CREATE ROLE admin WITH SUPERUSER CREATEDB LOGIN PASSWORD 'password'; CREATE ROLE postgres WITH SUPERUSER CREATEDB LOGIN PASSWORD 'password'; ``` +You may need to disable AirPlay Receiver in your System Settings if it is hogging port 5000. + #### System configuration (all) There are a number of steps for configuring your Houdini instance for startup ##### Run bin/setup diff --git a/config/database.yml b/config/database.yml index 13dafa6ff..dbf83493e 100755 --- a/config/database.yml +++ b/config/database.yml @@ -3,8 +3,8 @@ # # Install the pg driver: # gem install pg -# On Mac OS X with macports: -# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config +# On Mac OS X with macports, after installing postgresql with homebrew: +# gem install pg -- --with-pg-config="${HOMEBREW_PREFIX}/opt/libpq/bin/pg_config" # On Windows: # gem install pg # Choose the win32 build. @@ -20,7 +20,7 @@ development: pool: 5 username: admin password: password - host: <%= ENV['DATABASE_HOST'] || 'localhost' %> + # host: <%= ENV['DATABASE_HOST'] || 'localhost' %> # This breaks mac? Is it needed? min_messages: warning test: diff --git a/docker-compose.yml b/docker-compose.yml index 956852527..d66a43330 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: image: postgres:latest restart: always ports: - - "5432:5432" + - "5433:5432" volumes: - ~/tmp/houdini/db:/var/lib/postgresql/data environment: @@ -14,11 +14,12 @@ services: web: build: . volumes: - - .:/app + - ./log:/app/log ports: - - "3000:3000" + - "5000:5000" environment: - DATABASE_HOST=db + - BUILD_DATABASE_URL=postgres://admin:password@db/commitchange_development_legacy depends_on: - db env_file: ".env" \ No newline at end of file From a730551632e25ee9b4b350b3bcd4f48edf6ac568 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Fri, 22 Nov 2024 14:07:15 -0500 Subject: [PATCH 172/208] Working dockerization --- Dockerfile | 3 ++- Procfile.dev | 2 +- README.md | 34 +++++++++++++++++++++++++--------- config/database.yml | 2 +- docker-compose.yml | 11 +++++++++-- 5 files changed, 38 insertions(+), 14 deletions(-) diff --git a/Dockerfile b/Dockerfile index 39c1ebbb6..1414439aa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,6 +66,7 @@ COPY --from=builder --chown=app:app /usr/local/bundle/ /usr/local/bundle/ COPY --from=builder --chown=app:app /app /app ENV RAILS_ENV=development +ENV IS_DOCKER=true ENV RAILS_LOG_TO_STDOUT true ENV RAILS_SERVE_STATIC_FILES true ENV PORT 3000 @@ -73,4 +74,4 @@ ARG RAILS_ROOT=/app/ WORKDIR $RAILS_ROOT RUN mkdir -p tmp/pids -CMD foreman start \ No newline at end of file +CMD echo $IS_DOCKER && foreman start \ No newline at end of file diff --git a/Procfile.dev b/Procfile.dev index 3c19bca79..da4d294c2 100644 --- a/Procfile.dev +++ b/Procfile.dev @@ -1,3 +1,3 @@ -web: bin/rails server -p $PORT +web: /bin/bash -c '[[ $IS_DOCKER = "true" ]] && $(bundle exec puma -p $PORT -C ./config/puma.rb) || $(bin/rails server -p $PORT)' worker: bin/rake jobs:work webpack: yarn watch diff --git a/README.md b/README.md index 4b1dee48e..cbe59cbf2 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,25 @@ git clone https://github.com/Commitchange/houdini git checkout supporter_level_goal ``` +##### Get your .env file +If you don't already have access to the CommitChange 1Password vault, ask to be added. Then +download the .env file in 1Password and place it in the root directory. + +> *Note:* Double check that your .env file has the '.' in front of the file name. + +#### Dockerized +This is a work-in-progress method of running a development environment. The standard is still the bare metal instructions below. + +One time setup: +```bash +docker-compose run web bin/rake db:setup +``` + +Running: +```bash +docker-compose up +``` + #### One-time setup (Ubuntu) You'll want to run the next commands as root or via sudo (for Ubuntu 18.04 users or anyone running ProgresSQL 10, change "postgresql-12" below to "postgresql-10"). You could do this by typing `sudo /bin/sh` running the commands from there. @@ -127,11 +146,11 @@ brew install yarn Set your Postgres version with homebrew. ```bash -brew install postgresql@12 -brew switch postgres@12 +brew install postgresql@16 +brew switch postgres@16 # To start postgres locally run: -brew services start postgresql@12 +brew services start postgresql@16 ``` @@ -142,6 +161,8 @@ gem install pg -- --with-pg-config="${HOMEBREW_PREFIX}/opt/libpq/bin/pg_config" bundle config build.pg --with-pg-config="${HOMEBREW_PREFIX}/opt/libpq/bin/pg_config" ``` +You may also need to remove the `development: host` config line in `development.yml` + Create necessary postgres users in the `psql` console. ```bash @@ -152,6 +173,7 @@ CREATE ROLE postgres WITH SUPERUSER CREATEDB LOGIN PASSWORD 'password'; You may need to disable AirPlay Receiver in your System Settings if it is hogging port 5000. + #### System configuration (all) There are a number of steps for configuring your Houdini instance for startup ##### Run bin/setup @@ -159,12 +181,6 @@ There are a number of steps for configuring your Houdini instance for startup bin/setup ``` -##### Get your .env file -If you don't already have access to the CommitChange 1Password vault, ask to be added. Then -download the .env file in 1Password and place it in the root directory. - -> *Note:* Double check that your .env file has the '.' in front of the file name. - #### Startup ##### run foreman for development diff --git a/config/database.yml b/config/database.yml index dbf83493e..457dce37b 100755 --- a/config/database.yml +++ b/config/database.yml @@ -20,7 +20,7 @@ development: pool: 5 username: admin password: password - # host: <%= ENV['DATABASE_HOST'] || 'localhost' %> # This breaks mac? Is it needed? + host: <%= ENV['DATABASE_HOST'] || 'localhost' %> # This breaks mac? Is it needed? Yes, by docker min_messages: warning test: diff --git a/docker-compose.yml b/docker-compose.yml index d66a43330..5ac32c34b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: image: postgres:latest restart: always ports: - - "5433:5432" + - "5432:5432" volumes: - ~/tmp/houdini/db:/var/lib/postgresql/data environment: @@ -14,7 +14,14 @@ services: web: build: . volumes: - - ./log:/app/log + - ./app:/app/app + - ./bin:/app/bin + - ./config:/app/config + - ./db:/app/db + - ./javascripts:/app/javascripts + - ./lib:/app/lib + - ./script:/app/script + - ./types:/app/types ports: - "5000:5000" environment: From 106dabf6f0f593990b325af7672f6de3165cf71c Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Fri, 22 Nov 2024 15:28:02 -0500 Subject: [PATCH 173/208] Changed suggested method for fixing seg faults --- README.md | 2 +- config/database.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbe59cbf2..471375384 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ gem install pg -- --with-pg-config="${HOMEBREW_PREFIX}/opt/libpq/bin/pg_config" bundle config build.pg --with-pg-config="${HOMEBREW_PREFIX}/opt/libpq/bin/pg_config" ``` -You may also need to remove the `development: host` config line in `development.yml` +You may also need to set the env variable `PGGSSENCMODE=disable` to resolve segmentation faults. Create necessary postgres users in the `psql` console. diff --git a/config/database.yml b/config/database.yml index 457dce37b..27990b517 100755 --- a/config/database.yml +++ b/config/database.yml @@ -20,7 +20,7 @@ development: pool: 5 username: admin password: password - host: <%= ENV['DATABASE_HOST'] || 'localhost' %> # This breaks mac? Is it needed? Yes, by docker + host: localhost min_messages: warning test: From 84e31869e6c33f3fb821606e8499a645f7d682e8 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Fri, 22 Nov 2024 15:32:14 -0500 Subject: [PATCH 174/208] put db volume in repo tmp folder --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 5ac32c34b..2279d5f2b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: ports: - "5432:5432" volumes: - - ~/tmp/houdini/db:/var/lib/postgresql/data + - ./tmp/db:/var/lib/postgresql/data environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=password From 167bdd5c93b27dff37c83d52fc6d1917b5b4e35c Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Tue, 26 Nov 2024 11:59:31 -0500 Subject: [PATCH 175/208] Make script/restore_from_heroku work in Docker --- .dockerignore | 3 ++- Dockerfile | 19 ++++++++++++++++--- README.md | 6 ++++++ config/database.yml | 2 +- docker-compose.yml | 1 + script/pg_restore_local_from_production.sh | 2 +- 6 files changed, 27 insertions(+), 6 deletions(-) diff --git a/.dockerignore b/.dockerignore index 765261a76..ee05a5823 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,4 +6,5 @@ README.md .dockerignore tmp log -node_modules \ No newline at end of file +node_modules +.bin \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 1414439aa..ca2cdb71f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -46,9 +46,8 @@ COPY . ${RAILS_ROOT} FROM ${BASE} ENV LANG en_US.UTF-8 - RUN apt-get update -qq \ - && apt-get install -y libjemalloc2 postgresql-client tzdata libv8-dev curl default-jre \ + && apt-get install -y libjemalloc2 tzdata libv8-dev curl default-jre git \ && curl -sL https://deb.nodesource.com/setup_16.x | bash \ && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" \ @@ -58,6 +57,19 @@ RUN apt-get update -qq \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* +ARG BASE_RELEASE=bullseye +RUN apt-get update -qq \ + && apt-get install -y gnupg2 wget vim \ + && echo "deb https://apt.postgresql.org/pub/repos/apt ${BASE_RELEASE}-pgdg main" \ + > /etc/apt/sources.list.d/pgdg.list \ + && curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc|gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg \ + && apt-get update -qq \ + && apt-get install -y postgresql-client-16 \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +RUN curl https://cli-assets.heroku.com/install.sh | sh + RUN groupadd --gid 1000 app && \ useradd --uid 1000 --no-log-init --create-home --gid app app USER app @@ -74,4 +86,5 @@ ARG RAILS_ROOT=/app/ WORKDIR $RAILS_ROOT RUN mkdir -p tmp/pids -CMD echo $IS_DOCKER && foreman start \ No newline at end of file +RUN touch /home/app/.netrc +CMD foreman start \ No newline at end of file diff --git a/README.md b/README.md index 471375384..15e17a43a 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,13 @@ This is a work-in-progress method of running a development environment. The stan One time setup: ```bash +touch ~/.netrc #prevents docker compose from creating it as a directory if you don't have it yet + docker-compose run web bin/rake db:setup + +# To get a copy of the Prod database as test data. +docker-compose run web script/restore_from_heroku.sh +# enter `password` when prompted for password ``` Running: diff --git a/config/database.yml b/config/database.yml index 27990b517..381f3128e 100755 --- a/config/database.yml +++ b/config/database.yml @@ -20,7 +20,7 @@ development: pool: 5 username: admin password: password - host: localhost + host: <%= ENV['DATABASE_HOST'] || 'localhost' %> min_messages: warning test: diff --git a/docker-compose.yml b/docker-compose.yml index 2279d5f2b..485e9acf2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,6 +14,7 @@ services: web: build: . volumes: + - ${HOME}${USERPROFILE}/.netrc:/home/app/.netrc # for heroku credentials - ./app:/app/app - ./bin:/app/bin - ./config:/app/config diff --git a/script/pg_restore_local_from_production.sh b/script/pg_restore_local_from_production.sh index 0fd34edfa..c95ad4c6f 100755 --- a/script/pg_restore_local_from_production.sh +++ b/script/pg_restore_local_from_production.sh @@ -1,4 +1,4 @@ #!/bin/bash set -e -pg_restore --verbose --clean --no-acl --no-owner -h localhost -U admin -d commitchange_development_legacy latest.dump +pg_restore --verbose --clean --no-acl --no-owner -h ${DATABASE_HOST:-localhost} -U admin -d commitchange_development_legacy latest.dump From 1d4ba8b063ff5ae83be5c11f52ec7bb591874570 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Tue, 26 Nov 2024 17:16:44 -0500 Subject: [PATCH 176/208] Add details to readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 15e17a43a..2646c46b3 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,10 @@ touch ~/.netrc #prevents docker compose from creating it as a directory if you d docker-compose run web bin/rake db:setup -# To get a copy of the Prod database as test data. +# Run this to get a copy of the Prod database as test data. +# Enter `password` when prompted for a password after the download step. +# The restore step after that will take a very long time. Hours maybe. docker-compose run web script/restore_from_heroku.sh -# enter `password` when prompted for password ``` Running: From 3cd7e8ec7bd4bd0b6f47af71905fb022189b55ec Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Mon, 2 Dec 2024 17:38:35 -0500 Subject: [PATCH 177/208] run restore on db container --- README.md | 8 +++++--- docker-compose.yml | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2646c46b3..3907068d6 100644 --- a/README.md +++ b/README.md @@ -42,9 +42,11 @@ touch ~/.netrc #prevents docker compose from creating it as a directory if you d docker-compose run web bin/rake db:setup # Run this to get a copy of the Prod database as test data. -# Enter `password` when prompted for a password after the download step. -# The restore step after that will take a very long time. Hours maybe. -docker-compose run web script/restore_from_heroku.sh +curl -o ./tmp/shared/latest.dump `heroku pg:backups:url -a commitchange` + +# Enter `password` if prompted for a password. +# This may take up to an hour. +docker-compose exec db pg_restore --verbose --clean --no-acl --no-owner -h localhost -U admin -d commitchange_development_legacy /tmp/shared/latest.dump ``` Running: diff --git a/docker-compose.yml b/docker-compose.yml index 485e9acf2..2223ea21b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,13 @@ version: "3.9" services: db: - image: postgres:latest + image: postgres:16 restart: always ports: - "5432:5432" volumes: - ./tmp/db:/var/lib/postgresql/data + - ./tmp/shared:/tmp/shared environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=password From 43c708daa32cf5c5b812dbe73cd66c450b71ef2f Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 22 Nov 2024 13:44:44 -0600 Subject: [PATCH 178/208] Add QuerySupporters.dupes_on_last_name_and_address_and_email --- app/legacy_lib/query_supporters.rb | 23 +++- app/models/nonprofit.rb | 4 + spec/lib/query/query_supporters_spec.rb | 149 ++++++++++++++++++++++++ 3 files changed, 175 insertions(+), 1 deletion(-) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index 838c68e17..f1d15fb00 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -641,6 +641,19 @@ def self.dupes_on_last_name_and_address(np_id) .map { |arr_group| arr_group.flatten.sort } end + def self.dupes_on_last_name_and_address_and_email(np_id) + dupes_expr(np_id) + .and_where( + "name IS NOT NULL\ + AND name != ''\ + AND address IS NOT NULL \ + AND address != ''" + ) + .group_by(self.calculated_last_name + " || '_____' || address || '_____' || COALESCE(email, '')") + .execute(format: 'csv')[1..-1] + .map { |arr_group| arr_group.flatten.sort } + end + def self.dupes_on_phone_and_email(np_id, strict_mode = true) group_by_clause = [(strict_mode ? strict_email_match : loose_email_match), "phone_index"].join(', ') dupes_expr(np_id) @@ -697,7 +710,15 @@ def self.loose_name_match end def self.calculated_last_name - "substring(trim(both from supporters.name) from '^.+ ([^\s]+)$')" + " + CASE + WHEN TRIM(supporters.name) LIKE '% %' THEN substring( + TRIM(supporters.name) + FROM + '^.+ ([^ ]+)$' + ) + ELSE TRIM(supporters.name) + END" end # Create an export that lists donors with their total contributed amounts diff --git a/app/models/nonprofit.rb b/app/models/nonprofit.rb index 353d35828..3a3411fdf 100755 --- a/app/models/nonprofit.rb +++ b/app/models/nonprofit.rb @@ -121,6 +121,10 @@ def dupes_on_last_name_and_address QuerySupporters.dupes_on_last_name_and_address(proxy_association.owner.id) end + def dupes_on_last_name_and_address_and_email + QuerySupporters.dupes_on_last_name_and_address_and_email(proxy_association.owner.id) + end + def for_export_enumerable(query, chunk_limit=15000) QuerySupporters.for_export_enumerable(proxy_association.owner.id, query, chunk_limit) end diff --git a/spec/lib/query/query_supporters_spec.rb b/spec/lib/query/query_supporters_spec.rb index 932476516..0f0037aee 100644 --- a/spec/lib/query/query_supporters_spec.rb +++ b/spec/lib/query/query_supporters_spec.rb @@ -458,6 +458,155 @@ end end + describe '.dupes_on_last_name_and_address_and_email' do + subject { QuerySupporters.dupes_on_last_name_and_address_and_email(np.id) } + + it 'finds supporters with the same last name and address and email' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Penelope Borges', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + + context 'when different names' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Penelope Schultz', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to match_array([]) + end + end + + context 'when different addresses' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau', address: 'Clear.Waters.Avenue') + + expect(subject).to match_array([]) + end + end + + context 'when the name is empty' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: '', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: '', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([]) + end + end + + context 'when the name is nil' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: nil, address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: nil, address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([]) + end + end + + context 'when the email is nil' do + it 'finds' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau', address: 'Clear Waters Avenue', email: nil) + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau', address: 'Clear Waters Avenue', email: nil) + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + end + + context 'when not on strict mode' do + subject { QuerySupporters.dupes_on_name_and_address(np.id, false) } + context 'when names are the same but with different casing' do + it 'finds' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'cacau', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'CACAU', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + end + + context 'when names are the same but with different spacing' do + it 'finds' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'cacauborges', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + end + + context 'when names are the same but with special characters' do + it 'finds' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'cacau-borges', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau.Borges', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + end + + context 'when the names are not the same' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'cacau', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'cacau borges', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to match_array([]) + end + end + + context 'when the name is empty' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: '', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: '', address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([]) + end + end + + context 'when the name is nil' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: nil, address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: nil, address: 'Clear Waters Avenue', email: "email@example.com") + + expect(subject).to eq([]) + end + end + + context 'when addresses are the same but with different casing' do + it 'finds' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'clear waters avenue', email: "email@example.com") + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + end + + context 'when addresses are the same but with different spacing' do + it 'finds' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'ClearWatersAvenue', email: "email@example.com") + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + end + + context 'when addresses are the same but with special characters' do + it 'finds' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters . Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters - Avenue', email: "email@example.com") + + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) + end + end + + context 'when the addresses are not the same' do + it 'does not find' do + supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters Avenue', email: "email@example.com") + supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Avenue, Clear Waters', email: "email@example.com") + + expect(subject).to match_array([]) + end + end + end + end + describe '.dupes_on_phone_and_email' do subject { QuerySupporters.dupes_on_phone_and_email(np.id) } From 9bc06a2205af568b9a447c850eae5eb2a5fcbe98 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Wed, 4 Dec 2024 17:39:49 -0600 Subject: [PATCH 179/208] Test on newer ubuntu versions --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ba1d843e2..6a436e8af 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-20.04] + os: [ubuntu-20.04, ubuntu-22.04, ubuntu-24.04] node: [16] ruby: ['2.6.10'] postgres: ['16'] From 4c8409b282c5b37ee2f35d3038cb5fc013ee4ebc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Dec 2024 23:42:45 +0000 Subject: [PATCH 180/208] Bump elliptic from 6.5.7 to 6.6.1 Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.1. - [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.1) --- updated-dependencies: - dependency-name: elliptic dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index ebf56a5ab..32597e9d4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3523,9 +3523,9 @@ electron-to-chromium@^1.2.7, electron-to-chromium@^1.3.45, electron-to-chromium@ integrity sha512-r20dUOtZ4vUPTqAajDGonIM1uas5tf85Up+wPdtNBNvBSqGCfkpvMVvQ1T8YJzPV9/Y9g3FbUDcXb94Rafycow== elliptic@^6.0.0, elliptic@^6.5.4: - version "6.5.7" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.7.tgz#8ec4da2cb2939926a1b9a73619d768207e647c8b" - integrity sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q== + version "6.6.1" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.6.1.tgz#3b8ffb02670bf69e382c7f65bf524c97c5405c06" + integrity sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g== dependencies: bn.js "^4.11.9" brorand "^1.1.0" From 32c0416e9f0428308053477167cd3b59acaaff60 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Thu, 5 Dec 2024 08:33:27 -0800 Subject: [PATCH 181/208] Revert "run restore on db container" This reverts commit 3cd7e8ec7bd4bd0b6f47af71905fb022189b55ec. --- README.md | 8 +++----- docker-compose.yml | 3 +-- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 3907068d6..2646c46b3 100644 --- a/README.md +++ b/README.md @@ -42,11 +42,9 @@ touch ~/.netrc #prevents docker compose from creating it as a directory if you d docker-compose run web bin/rake db:setup # Run this to get a copy of the Prod database as test data. -curl -o ./tmp/shared/latest.dump `heroku pg:backups:url -a commitchange` - -# Enter `password` if prompted for a password. -# This may take up to an hour. -docker-compose exec db pg_restore --verbose --clean --no-acl --no-owner -h localhost -U admin -d commitchange_development_legacy /tmp/shared/latest.dump +# Enter `password` when prompted for a password after the download step. +# The restore step after that will take a very long time. Hours maybe. +docker-compose run web script/restore_from_heroku.sh ``` Running: diff --git a/docker-compose.yml b/docker-compose.yml index 2223ea21b..485e9acf2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,13 +1,12 @@ version: "3.9" services: db: - image: postgres:16 + image: postgres:latest restart: always ports: - "5432:5432" volumes: - ./tmp/db:/var/lib/postgresql/data - - ./tmp/shared:/tmp/shared environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=password From ac8861fd0a648830dec7ef3487c152bde9ab6f4b Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Thu, 5 Dec 2024 08:33:52 -0800 Subject: [PATCH 182/208] Use Postgres 16 --- docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 485e9acf2..02f99ade1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ version: "3.9" services: db: - image: postgres:latest + image: postgres:16 restart: always ports: - "5432:5432" From 9af689b5a67f089f133de7b70113243a8ed77428 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Thu, 5 Dec 2024 08:37:27 -0800 Subject: [PATCH 183/208] remove hours-long text from readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 2646c46b3..93cd4ec30 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ docker-compose run web bin/rake db:setup # Run this to get a copy of the Prod database as test data. # Enter `password` when prompted for a password after the download step. -# The restore step after that will take a very long time. Hours maybe. docker-compose run web script/restore_from_heroku.sh ``` From 7744226bb5653a9cb30f9eb6bbdd8cbc771f8eac Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Thu, 5 Dec 2024 12:35:28 -0800 Subject: [PATCH 184/208] Volumize bundle output --- .dockerignore | 3 ++- .gitignore | 4 +++- Dockerfile | 17 ++++++----------- docker-compose.yml | 1 + 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/.dockerignore b/.dockerignore index ee05a5823..a308977d3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -7,4 +7,5 @@ README.md tmp log node_modules -.bin \ No newline at end of file +.bin +vendor \ No newline at end of file diff --git a/.gitignore b/.gitignore index 276f33bf5..691371e6e 100755 --- a/.gitignore +++ b/.gitignore @@ -78,4 +78,6 @@ javascripts/api *.csv app/javascript/routes.js -app/javascript/routes.d.ts \ No newline at end of file +app/javascript/routes.d.ts + +vendor \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index ca2cdb71f..3a439e03b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,7 @@ # syntax=docker/dockerfile:1 ARG BASE_IMAGE=ruby -ARG BASE_TAG=2.6.10-slim +ARG RUBY_VERSION=2.6.10 +ARG BASE_TAG=${RUBY_VERSION}-slim ARG BASE=${BASE_IMAGE}:${BASE_TAG} FROM ${BASE} AS builder @@ -14,7 +15,6 @@ RUN apt-get update -qq \ curl \ tzdata \ git \ - libpq-dev \ nodejs \ yarn \ && curl -sL https://deb.nodesource.com/setup_16.x | bash \ @@ -31,11 +31,6 @@ ARG RAILS_ROOT=/app/ RUN mkdir ${RAILS_ROOT} WORKDIR ${RAILS_ROOT} -COPY Gemfile* ${RAILS_ROOT} -ADD gems ${RAILS_ROOT}gems - -RUN bundle update --bundler && bundle install -j4 --retry 3 - COPY package.json yarn.lock ${RAILS_ROOT} RUN yarn install @@ -47,7 +42,7 @@ FROM ${BASE} ENV LANG en_US.UTF-8 RUN apt-get update -qq \ - && apt-get install -y libjemalloc2 tzdata libv8-dev curl default-jre git \ + && apt-get install -y libjemalloc2 tzdata libv8-dev curl default-jre git build-essential libpq-dev \ && curl -sL https://deb.nodesource.com/setup_16.x | bash \ && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \ && echo "deb https://dl.yarnpkg.com/debian/ stable main" \ @@ -72,9 +67,9 @@ RUN curl https://cli-assets.heroku.com/install.sh | sh RUN groupadd --gid 1000 app && \ useradd --uid 1000 --no-log-init --create-home --gid app app + USER app -COPY --from=builder --chown=app:app /usr/local/bundle/ /usr/local/bundle/ COPY --from=builder --chown=app:app /app /app ENV RAILS_ENV=development @@ -85,6 +80,6 @@ ENV PORT 3000 ARG RAILS_ROOT=/app/ WORKDIR $RAILS_ROOT -RUN mkdir -p tmp/pids RUN touch /home/app/.netrc -CMD foreman start \ No newline at end of file +RUN mkdir -p tmp/pids +CMD bundle check || (bundle update --bundler && bundle install -j4 --retry 3) && foreman start \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 02f99ade1..0dd78e51c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,6 +15,7 @@ services: build: . volumes: - ${HOME}${USERPROFILE}/.netrc:/home/app/.netrc # for heroku credentials + - ./vendor/bundle:/usr/local/bundle - ./app:/app/app - ./bin:/app/bin - ./config:/app/config From b800802c90b10e21cd7d0cbc51c7ff5632069a04 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Fri, 6 Dec 2024 10:38:37 -0800 Subject: [PATCH 185/208] Use faster solution, change volume consistency types --- Dockerfile | 1 - README.md | 9 +++++++-- docker-compose.yml | 3 ++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index ca2cdb71f..8e014e647 100644 --- a/Dockerfile +++ b/Dockerfile @@ -59,7 +59,6 @@ RUN apt-get update -qq \ ARG BASE_RELEASE=bullseye RUN apt-get update -qq \ - && apt-get install -y gnupg2 wget vim \ && echo "deb https://apt.postgresql.org/pub/repos/apt ${BASE_RELEASE}-pgdg main" \ > /etc/apt/sources.list.d/pgdg.list \ && curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc|gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg \ diff --git a/README.md b/README.md index 93cd4ec30..acd45f80a 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,13 @@ touch ~/.netrc #prevents docker compose from creating it as a directory if you d docker-compose run web bin/rake db:setup # Run this to get a copy of the Prod database as test data. -# Enter `password` when prompted for a password after the download step. -docker-compose run web script/restore_from_heroku.sh +curl -o ./tmp/shared/latest.dump `heroku pg:backups:url -a commitchange` + +docker-compose up + +# Enter `password` if prompted for a password. +# This may take up to an hour on Mac. +docker-compose exec db pg_restore --verbose --clean --no-acl --no-owner -h localhost -U admin -d commitchange_development_legacy /tmp/shared/latest.dump ``` Running: diff --git a/docker-compose.yml b/docker-compose.yml index 02f99ade1..e79fa6b63 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,8 @@ services: ports: - "5432:5432" volumes: - - ./tmp/db:/var/lib/postgresql/data + - ./tmp/db:/var/lib/postgresql/data:delegated + - ./tmp/shared:/tmp/shared:cached environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=password From 611a329e4ff630fa82f8adf12c9b8ad717c1c25d Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 8 Dec 2024 17:10:51 -0600 Subject: [PATCH 186/208] Provide information on the calculated_last_name query --- app/legacy_lib/query_supporters.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/legacy_lib/query_supporters.rb b/app/legacy_lib/query_supporters.rb index f1d15fb00..916836aa8 100644 --- a/app/legacy_lib/query_supporters.rb +++ b/app/legacy_lib/query_supporters.rb @@ -709,6 +709,8 @@ def self.loose_name_match "regexp_replace (lower(name),'[^0-9a-z]','','g')" end + # gets the last words in the name field. If there's a single word, it gets that. If there's multiple words, then it just gets + # everything but the first one def self.calculated_last_name " CASE From 40cae3279eaa7f0ed8227b684b18a562628d058d Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 8 Dec 2024 17:14:56 -0600 Subject: [PATCH 187/208] Added additional supporter to cover all cases in QuerySupporters spec --- spec/lib/query/query_supporters_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/lib/query/query_supporters_spec.rb b/spec/lib/query/query_supporters_spec.rb index 0f0037aee..91e21850c 100644 --- a/spec/lib/query/query_supporters_spec.rb +++ b/spec/lib/query/query_supporters_spec.rb @@ -464,7 +464,8 @@ it 'finds supporters with the same last name and address and email' do supporter_1 = force_create(:supporter, nonprofit_id: np.id, name: 'Cacau Borges', address: 'Clear Waters Avenue', email: "email@example.com") supporter_2 = force_create(:supporter, nonprofit_id: np.id, name: 'Penelope Borges', address: 'Clear Waters Avenue', email: "email@example.com") - + supporter_3 = force_create(:supporter, nonprofit_id: np.id, name: 'Penelope Schultz', address: 'Clear Waters Avenue', email: "email@example.com") + expect(subject).to eq([[supporter_1.id, supporter_2.id]]) end From a9e7c97c2fbbdf63617601f1004e24f7dbf59be8 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Sun, 8 Dec 2024 17:55:59 -0600 Subject: [PATCH 188/208] Improve the isBlank return type --- javascripts/src/lib/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascripts/src/lib/utils.ts b/javascripts/src/lib/utils.ts index b816618ff..2caf4b7a6 100644 --- a/javascripts/src/lib/utils.ts +++ b/javascripts/src/lib/utils.ts @@ -3,7 +3,7 @@ export function castToNullIfUndef(i:T): T | null{ return i === undefined ? null : i } -export function isBlank(i:null|undefined|string) : boolean { +export function isBlank(i:null|undefined|string) : i is null | undefined | '' { return i === null || i === undefined || i === ''; } From 786e583969ddc36c4922585a94c6ef8dcd4202b6 Mon Sep 17 00:00:00 2001 From: Thena Seer Date: Mon, 9 Dec 2024 11:07:23 -0800 Subject: [PATCH 189/208] add dump_path var to script, add linux user override --- README.md | 25 ++++++++++++++-------- docker-compose.override.yml.example | 4 ++++ script/pg_restore_local_from_production.sh | 3 +-- script/restore_from_heroku.sh | 2 +- 4 files changed, 22 insertions(+), 12 deletions(-) create mode 100644 docker-compose.override.yml.example diff --git a/README.md b/README.md index acd45f80a..201c1b7b5 100644 --- a/README.md +++ b/README.md @@ -35,25 +35,32 @@ download the .env file in 1Password and place it in the root directory. #### Dockerized This is a work-in-progress method of running a development environment. The standard is still the bare metal instructions below. -One time setup: +Mac users can ignore this, but if your host machine is Linux, you might run into permission issues with the tmp files created by the postgres image. To proactively avoid this, run `cp docker-compose.override.yml.example docker-compose.override.yml` and change the values inside the newly copied file to match the output of `echo $(id -u):$(id -g)` + +One-time setup: ```bash touch ~/.netrc #prevents docker compose from creating it as a directory if you don't have it yet docker-compose run web bin/rake db:setup +``` -# Run this to get a copy of the Prod database as test data. -curl -o ./tmp/shared/latest.dump `heroku pg:backups:url -a commitchange` - +Running: +```bash docker-compose up +``` -# Enter `password` if prompted for a password. -# This may take up to an hour on Mac. -docker-compose exec db pg_restore --verbose --clean --no-acl --no-owner -h localhost -U admin -d commitchange_development_legacy /tmp/shared/latest.dump +Restoring the DB from Prod (Linux): +```bash +# Enter `password` when prompted for a password after the download step. +docker-compose exec web script/restore_from_heroku.sh ``` -Running: +Restoring the DB from Prod (Mac). The above command will work on Mac, but will take an hour or more due to differences in how docker handles storage. Use the below to reduce how long it takes (will still take a long time). ```bash -docker-compose up +curl -o ./tmp/shared/latest.dump `heroku pg:backups:url -a commitchange` + +# Enter `password` when prompted for a password. +docker-compose exec db -e HOUDINI_DUMP_PATH="/tmp/shared/latest.dump" script/pg_restore_local_from_production.sh ``` #### One-time setup (Ubuntu) diff --git a/docker-compose.override.yml.example b/docker-compose.override.yml.example new file mode 100644 index 000000000..cc59b37ad --- /dev/null +++ b/docker-compose.override.yml.example @@ -0,0 +1,4 @@ +version: "3.9" +services: + db: + user: 1001:1001 \ No newline at end of file diff --git a/script/pg_restore_local_from_production.sh b/script/pg_restore_local_from_production.sh index c95ad4c6f..d5c6a3857 100755 --- a/script/pg_restore_local_from_production.sh +++ b/script/pg_restore_local_from_production.sh @@ -1,4 +1,3 @@ #!/bin/bash set -e - -pg_restore --verbose --clean --no-acl --no-owner -h ${DATABASE_HOST:-localhost} -U admin -d commitchange_development_legacy latest.dump +pg_restore --verbose --clean --no-acl --no-owner -h ${DATABASE_HOST:-localhost} -U admin -d commitchange_development_legacy ${HOUDINI_DUMP_PATH:-"latest.dump"} diff --git a/script/restore_from_heroku.sh b/script/restore_from_heroku.sh index dd02c4241..9095a8b41 100755 --- a/script/restore_from_heroku.sh +++ b/script/restore_from_heroku.sh @@ -2,5 +2,5 @@ set -e -curl -o latest.dump `heroku pg:backups:url -a commitchange` +curl -o ${HOUDINI_DUMP_PATH:-"latest.dump"} `heroku pg:backups:url -a commitchange` script/pg_restore_local_from_production.sh From 1ea3d69138e81ce7783fb44c20377c5ff2aa55cb Mon Sep 17 00:00:00 2001 From: Thena Seer <138702673+thena-seer-sfg@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:53:36 -0800 Subject: [PATCH 190/208] Update README.md Co-authored-by: Eric Schultz --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 201c1b7b5..258834212 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ Restoring the DB from Prod (Mac). The above command will work on Mac, but will t curl -o ./tmp/shared/latest.dump `heroku pg:backups:url -a commitchange` # Enter `password` when prompted for a password. -docker-compose exec db -e HOUDINI_DUMP_PATH="/tmp/shared/latest.dump" script/pg_restore_local_from_production.sh +docker-compose exec db -e CC_PROD_DUMP_PATH="/tmp/shared/latest.dump" script/pg_restore_local_from_production.sh ``` #### One-time setup (Ubuntu) From d27bcc94d5b367de70c59beb61dfb9240360cc1e Mon Sep 17 00:00:00 2001 From: Thena Seer <138702673+thena-seer-sfg@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:53:44 -0800 Subject: [PATCH 191/208] Update script/pg_restore_local_from_production.sh Co-authored-by: Eric Schultz --- script/pg_restore_local_from_production.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/pg_restore_local_from_production.sh b/script/pg_restore_local_from_production.sh index d5c6a3857..5eb6c88cf 100755 --- a/script/pg_restore_local_from_production.sh +++ b/script/pg_restore_local_from_production.sh @@ -1,3 +1,3 @@ #!/bin/bash set -e -pg_restore --verbose --clean --no-acl --no-owner -h ${DATABASE_HOST:-localhost} -U admin -d commitchange_development_legacy ${HOUDINI_DUMP_PATH:-"latest.dump"} +pg_restore --verbose --clean --no-acl --no-owner -h ${DATABASE_HOST:-localhost} -U admin -d commitchange_development_legacy ${CC_PROD_DUMP_PATH:-"latest.dump"} From 4ad8230a8767f6cdb3b00b4fa319c04d72534e9c Mon Sep 17 00:00:00 2001 From: Thena Seer <138702673+thena-seer-sfg@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:53:50 -0800 Subject: [PATCH 192/208] Update script/restore_from_heroku.sh Co-authored-by: Eric Schultz --- script/restore_from_heroku.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/restore_from_heroku.sh b/script/restore_from_heroku.sh index 9095a8b41..a579abc99 100755 --- a/script/restore_from_heroku.sh +++ b/script/restore_from_heroku.sh @@ -2,5 +2,5 @@ set -e -curl -o ${HOUDINI_DUMP_PATH:-"latest.dump"} `heroku pg:backups:url -a commitchange` +curl -o ${CC_PROD_DUMP_PATH:-"latest.dump"} `heroku pg:backups:url -a commitchange` script/pg_restore_local_from_production.sh From 62b6194783cbb3af6ffa62342923390fd44bc08a Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 12 Dec 2024 10:32:14 -0600 Subject: [PATCH 193/208] add letter_opener in dev --- Gemfile | 1 + Gemfile.lock | 9 +++++++++ config/environments/development.rb | 4 +++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index ac36a27ac..adb55e102 100644 --- a/Gemfile +++ b/Gemfile @@ -99,6 +99,7 @@ group :development, :ci do end group :development, :ci, :test do + gem 'letter_opener' gem 'timecop' gem 'pry' gem 'pry-byebug' diff --git a/Gemfile.lock b/Gemfile.lock index 5a29e372f..356a4cf05 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -145,6 +145,8 @@ GEM carrierwave-aws (1.4.0) aws-sdk-s3 (~> 1.0) carrierwave (>= 0.7, < 2.1) + childprocess (5.1.0) + logger (~> 1.5) chronic (0.10.2) coderay (1.1.3) coercible (1.0.0) @@ -296,6 +298,12 @@ GEM kaminari-core (= 1.2.1) kaminari-core (1.2.1) kdtree (0.3) + launchy (3.0.1) + addressable (~> 2.8) + childprocess (~> 5.0) + letter_opener (1.10.0) + launchy (>= 2.2, < 4) + logger (1.6.2) lograge (0.3.6) actionpack (>= 3) activesupport (>= 3) @@ -559,6 +567,7 @@ DEPENDENCIES js-routes json (>= 2.3.0) kaminari + letter_opener lograge mini_magick nearest_time_zone diff --git a/config/environments/development.rb b/config/environments/development.rb index 1b982534f..d708a06e0 100755 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -34,8 +34,10 @@ # ) # config.action_mailer.delivery_method = :ses + config.action_mailer.delivery_method = :letter_opener + config.action_mailer.perform_deliveries = true + config.action_mailer.default_url_options = { host: 'localhost', port: 5000} - config.action_mailer.delivery_method = Settings.mailer.delivery_method.to_sym config.action_mailer.smtp_settings = { address: Settings.mailer.address, port: Settings.mailer.port } config.action_mailer.smtp_settings['user_name']= Settings.mailer.username if Settings.mailer.username config.action_mailer.smtp_settings['password']= Settings.mailer.password if Settings.mailer.password From f69cec3500a91603a42845a6f63e9d03e38b7ccc Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 12 Dec 2024 13:30:41 -0600 Subject: [PATCH 194/208] Add toggle_soft_delete types --- client/js/components/ajax/toggle_soft_delete.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/js/components/ajax/toggle_soft_delete.js b/client/js/components/ajax/toggle_soft_delete.js index ab644013e..fa9c6a0c4 100644 --- a/client/js/components/ajax/toggle_soft_delete.js +++ b/client/js/components/ajax/toggle_soft_delete.js @@ -1,6 +1,12 @@ // License: LGPL-3.0-or-later var request = require('../../common/client') +/** + * + * @param {string} url + * @param {string} type + * @returns {void} + */ module.exports = function(url, type) { appl.def('toggle_soft_delete', function(bool) { appl.def('loading', true) From 21ed97301708e47f625ed4d287cd3ee9160ec8c3 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 12 Dec 2024 13:31:28 -0600 Subject: [PATCH 195/208] Add sanitize slug types --- client/js/common/sanitize-slug.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/js/common/sanitize-slug.js b/client/js/common/sanitize-slug.js index e2cfcf4cd..d4289461c 100644 --- a/client/js/common/sanitize-slug.js +++ b/client/js/common/sanitize-slug.js @@ -1,4 +1,9 @@ // License: LGPL-3.0-or-later +/** + * + * @param {string} str + * @returns {string} a sanitized string + */ module.exports = str => str.trim().toLowerCase() .replace(/\s*[^A-Za-z0-9\-]\s*/g, '-') // Replace any oddballs with a hyphen From 05d70fe5f400bb52deda77551b9311bcd22eea4a Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 12 Dec 2024 15:59:34 -0600 Subject: [PATCH 196/208] add dedication contact details to receipt emails --- .../_donation_payment_table.html.erb | 22 +++++++++- spec/factories/donations.rb | 17 ++++++-- spec/mailers/donation_mailer_spec.rb | 42 ++++++++++++++++++- 3 files changed, 74 insertions(+), 7 deletions(-) diff --git a/app/views/donation_mailer/_donation_payment_table.html.erb b/app/views/donation_mailer/_donation_payment_table.html.erb index 4b76f2daf..62f112804 100644 --- a/app/views/donation_mailer/_donation_payment_table.html.erb +++ b/app/views/donation_mailer/_donation_payment_table.html.erb @@ -80,7 +80,27 @@ Dedication <%= donation.dedication ? Format::Dedication.from_json(donation.dedication) : 'None' %> - + + <% @contact = JSON.parse(donation.dedication).dig('contact') %> + + Acknowledgement Email + <% if @contact['email'] %> + <%= @contact['email'] %> + <% else %> + None + <% end %> + + + Acknowledgement Phone + <% if @contact['phone'] %> + <%= @contact['phone'] %> + <% else %> + None + <% end %> + + + Acknowledgement Address + <%= @contact['address'] ? @contact['address'] : 'None' %> <% end %> diff --git a/spec/factories/donations.rb b/spec/factories/donations.rb index 06f8da42f..b80679882 100644 --- a/spec/factories/donations.rb +++ b/spec/factories/donations.rb @@ -22,11 +22,20 @@ )] } end - end - - - + factory :donation_with_dedication do + dedication { { + contact: { + email: 'email@ema.com', + phone: '234-343-3234', + address: '123 Four' + }, + name: 'our loved one', + note: 'we miss them dearly', + type: 'memory' + }.to_json } + end + end factory :fv_poverty_donation, class: 'Donation' do nonprofit {association :fv_poverty} diff --git a/spec/mailers/donation_mailer_spec.rb b/spec/mailers/donation_mailer_spec.rb index 63c6be6cb..fbf526203 100644 --- a/spec/mailers/donation_mailer_spec.rb +++ b/spec/mailers/donation_mailer_spec.rb @@ -255,6 +255,44 @@ expect(mail.subject).to include supporter.email end end - + + context 'when dedication is not set' do + before(:each) { + expect(QueryUsers).to receive(:nonprofit_user_emails).and_return(['anything@example.com']) + } + + let(:supporter) { create(:supporter_base)} + let(:donation) { create(:donation_base, supporter: supporter, nonprofit: supporter.nonprofit)} + let(:payment) { create(:payment_base, donation: donation)} + + let!(:mail) { DonationMailer.nonprofit_payment_notification( + donation.id, + payment.id + ) } + + it 'does not include the dedication section' do + expect(mail.body.encoded).to_not include "Acknowledgement Phone" + end + end + + context 'when dedication is set' do + before(:each) { + expect(QueryUsers).to receive(:nonprofit_user_emails).and_return(['anything@example.com']) + } + + let(:supporter) { create(:supporter_base)} + let(:donation) { create(:donation_with_dedication, supporter: supporter, nonprofit: supporter.nonprofit)} + let(:payment) { create(:payment_base, donation: donation)} + + let!(:mail) { DonationMailer.nonprofit_payment_notification( + donation.id, + payment.id + ) } + + it 'does not include the dedication section' do + expect(mail.body.encoded).to include "Acknowledgement Phone" + expect(mail.body.encoded).to include "234-343-3234" + end + end end -end \ No newline at end of file +end From 1584b67ab119e13d2f98207cd822821a217461c2 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Thu, 12 Dec 2024 16:34:24 -0600 Subject: [PATCH 197/208] Fix a type error --- javascripts/src/lib/createNumberMask.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascripts/src/lib/createNumberMask.spec.ts b/javascripts/src/lib/createNumberMask.spec.ts index a52d782b4..002b3a1a2 100644 --- a/javascripts/src/lib/createNumberMask.spec.ts +++ b/javascripts/src/lib/createNumberMask.spec.ts @@ -176,7 +176,7 @@ describe('createNumberMask', () => { }) describe('numberMask default behavior', () => { - let numberMask:ReturnType = null + let numberMask:ReturnType; beforeEach(() => { numberMask = createNumberMask() From 4a4af76dd7cf08bf49b647d1a9f32d83e327a45b Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 12 Dec 2024 16:56:59 -0600 Subject: [PATCH 198/208] more null checking --- app/views/donation_mailer/_donation_payment_table.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/donation_mailer/_donation_payment_table.html.erb b/app/views/donation_mailer/_donation_payment_table.html.erb index 62f112804..fdde81eab 100644 --- a/app/views/donation_mailer/_donation_payment_table.html.erb +++ b/app/views/donation_mailer/_donation_payment_table.html.erb @@ -84,7 +84,7 @@ <% @contact = JSON.parse(donation.dedication).dig('contact') %> Acknowledgement Email - <% if @contact['email'] %> + <% if @contact && @contact['email'] %> <%= @contact['email'] %> <% else %> None @@ -92,7 +92,7 @@ Acknowledgement Phone - <% if @contact['phone'] %> + <% if @contact && @contact['phone'] %> <%= @contact['phone'] %> <% else %> None @@ -100,7 +100,7 @@ Acknowledgement Address - <%= @contact['address'] ? @contact['address'] : 'None' %> + <%= @contact && @contact['address'] ? @contact['address'] : 'None' %> <% end %> From 68e742193d1915d35c9fdddafe1d49cab5c2e2ba Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Fri, 13 Dec 2024 08:56:11 -0600 Subject: [PATCH 199/208] Remove unused import --- client/js/events/new/wizard.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/js/events/new/wizard.js b/client/js/events/new/wizard.js index d63fdedc7..9c69de8e3 100644 --- a/client/js/events/new/wizard.js +++ b/client/js/events/new/wizard.js @@ -3,8 +3,6 @@ require('../../common/pikaday-timepicker') require('../../components/wizard') require('../../common/image_uploader') var checkName = require('../../common/ajax/check_campaign_or_event_name') -var format_err = require('../../common/format_response_error') - appl.def('advance_event_name_step', function(form_obj) { var name = form_obj['event[name]'] From a7239ff649d1b68db22cef9dd74b768719d09b5d Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Fri, 13 Dec 2024 11:28:12 -0600 Subject: [PATCH 200/208] more concise null-checking --- app/views/donation_mailer/_donation_payment_table.html.erb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/donation_mailer/_donation_payment_table.html.erb b/app/views/donation_mailer/_donation_payment_table.html.erb index fdde81eab..45d961ce9 100644 --- a/app/views/donation_mailer/_donation_payment_table.html.erb +++ b/app/views/donation_mailer/_donation_payment_table.html.erb @@ -84,7 +84,7 @@ <% @contact = JSON.parse(donation.dedication).dig('contact') %> Acknowledgement Email - <% if @contact && @contact['email'] %> + <% if @contact&.dig('email') %> <%= @contact['email'] %> <% else %> None @@ -92,7 +92,7 @@ Acknowledgement Phone - <% if @contact && @contact['phone'] %> + <% if @contact&.dig('phone') %> <%= @contact['phone'] %> <% else %> None @@ -100,7 +100,7 @@ Acknowledgement Address - <%= @contact && @contact['address'] ? @contact['address'] : 'None' %> + <%= @contact&.dig('address') ? @contact['address'] : 'None' %> <% end %> From 279479caf54123b0f7d2c0e290e956399acca61c Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 16 Dec 2024 10:47:14 -0600 Subject: [PATCH 201/208] Remove unused QueryCampaigns.featured --- app/legacy_lib/query_campaigns.rb | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/app/legacy_lib/query_campaigns.rb b/app/legacy_lib/query_campaigns.rb index ec4eec890..64695dade 100644 --- a/app/legacy_lib/query_campaigns.rb +++ b/app/legacy_lib/query_campaigns.rb @@ -3,27 +3,6 @@ module QueryCampaigns - def self.featured(limit, gross_more_than) - expr = Qexpr.new.select( - 'campaigns.name', - 'campaigns.summary', - 'campaigns.gross', - Image._url('campaigns', 'main_image', 'normal') + 'AS main_image_url', - "concat('/nonprofits/', campaigns.nonprofit_id, '/campaigns/', campaigns.id) AS url" - ).from( - Qexpr.new.select("campaigns.*", "SUM(donations.amount) AS gross") - .from("campaigns") - .join("donations", "donations.campaign_id=campaigns.id") - .group_by("campaigns.id"), - "campaigns" - ).where("campaigns.gross > $amount AND campaigns.published='t' AND campaigns.nonprofit_id!=$id", amount: gross_more_than, id: 4182) - .order_by("campaigns.updated_at ASC") - .limit(limit) - - Psql.execute(expr.parse) - end - - def self.timeline(campaign_id) ex = QueryCampaigns.payments_expression(campaign_id, true) ex.group_by("DATE(payments.date)") From e060948172ea36fbf3b2d731514a35bd58133256 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Mon, 16 Dec 2024 11:31:19 -0600 Subject: [PATCH 202/208] remove unused class-object and the unused things it was imported in --- client/js/common/class-object.js | 8 -------- client/js/components/number-input.js | 17 ----------------- client/js/components/text-input.js | 2 -- client/js/components/textarea.js | 15 --------------- 4 files changed, 42 deletions(-) delete mode 100644 client/js/common/class-object.js delete mode 100644 client/js/components/number-input.js delete mode 100644 client/js/components/textarea.js diff --git a/client/js/common/class-object.js b/client/js/common/class-object.js deleted file mode 100644 index 7bdd32cd1..000000000 --- a/client/js/common/class-object.js +++ /dev/null @@ -1,8 +0,0 @@ -// License: LGPL-3.0-or-later -const R = require('ramda') - -module.exports = (classes='') => R.reduce( - (a, b) => {a[b] = true; return a} - , {} - , R.drop(1, classes.split('.'))) - diff --git a/client/js/components/number-input.js b/client/js/components/number-input.js deleted file mode 100644 index da5ff94c9..000000000 --- a/client/js/components/number-input.js +++ /dev/null @@ -1,17 +0,0 @@ -// License: LGPL-3.0-or-later -const h = require('flimflam/h') -const classObject = require('../common/class-object') - -module.exports = (name, placeholder, value, classes) => { -return h('input.max-width-2', { - props: { - type: 'number' - , - , name - , placeholder - , value - } - , class: classObject(classes) - }) -} - diff --git a/client/js/components/text-input.js b/client/js/components/text-input.js index 75cf386da..97852f415 100644 --- a/client/js/components/text-input.js +++ b/client/js/components/text-input.js @@ -1,6 +1,5 @@ // License: LGPL-3.0-or-later const h = require('flimflam/h') -const classObject = require('../common/class-object') module.exports = (name, placeholder, value, classes) => { return h('input.max-width-2', { @@ -10,6 +9,5 @@ return h('input.max-width-2', { , placeholder , value } - , class: classObject(classes) }) } diff --git a/client/js/components/textarea.js b/client/js/components/textarea.js deleted file mode 100644 index cd00057ce..000000000 --- a/client/js/components/textarea.js +++ /dev/null @@ -1,15 +0,0 @@ -// License: LGPL-3.0-or-later -const h = require('flimflam/h') -const classObject = require('../common/class-object') - -module.exports = (name, placeholder, value, classes) => { -return h('textarea.max-width-2', { - props: { - name - , placeholder - , value - } - , class: classObject(classes) - }) -} - From c350067a85a9bb13c1e4f223ec45dea0d4aa4ad5 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 16 Dec 2024 13:14:11 -0600 Subject: [PATCH 203/208] Add some basic typing to chart-options --- client/js/components/chart-options.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/client/js/components/chart-options.js b/client/js/components/chart-options.js index 77c0b1c39..ab816c8f0 100644 --- a/client/js/components/chart-options.js +++ b/client/js/components/chart-options.js @@ -18,6 +18,12 @@ chartOptions.dollars = { } , tooltips: { callbacks: { + /** + * + * @param {{datasetIndex:any, yLabel: any}} item + * @param {{datasets:any}} data + * @returns {string} + */ label: (item, data) => data.datasets[item.datasetIndex].label + ': $' + utils.cents_to_dollars(item.yLabel) From cbfd0d8a8746260a3137c7160c1aacb7c6524d97 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 16 Dec 2024 13:16:18 -0600 Subject: [PATCH 204/208] Some cleanup and typing in timeline.js --- client/js/campaigns/timeline.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/client/js/campaigns/timeline.js b/client/js/campaigns/timeline.js index 5ff8c455e..f2c9447c6 100644 --- a/client/js/campaigns/timeline.js +++ b/client/js/campaigns/timeline.js @@ -22,6 +22,11 @@ function query() { }) } +/** + * + * @param {Array} data + * @returns + */ function cumulative(data) { var moments = dateRange(R.head(data).date, R.last(data).date, 'days') var dateStrings = R.map((m) => m.format('YYYY-MM-DD'), moments) @@ -53,9 +58,14 @@ function cumulative(data) { }, [proto], R.values(dateDictionary))) } +/** + * + * @param {Array} data + * @returns + */ function formatData(data) { return { - labels: R.map((st) => moment(st).format('M/D/YYYY'), R.pluck('date', data)) + labels: data.map(i => i.date).map((st) => moment(st).format('M/D/YYYY')) , datasets: [ dataset('Total' , 'total_cents' @@ -77,6 +87,14 @@ function formatData(data) { } } +/** + * + * @param {string} label + * @param {string} key + * @param {string} rgb + * @param {Array} data + * @returns + */ function dataset(label, key, rgb, data) { return { label: label From 16741fe60f94c16c1bc6a5afe30b886c15d72970 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 16 Dec 2024 13:20:04 -0600 Subject: [PATCH 205/208] Add types to is-sold-out --- client/js/campaigns/show/is-sold-out.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/js/campaigns/show/is-sold-out.js b/client/js/campaigns/show/is-sold-out.js index e013f44b3..0ab754319 100644 --- a/client/js/campaigns/show/is-sold-out.js +++ b/client/js/campaigns/show/is-sold-out.js @@ -1,3 +1,8 @@ // License: LGPL-3.0-or-later +/** + * + * @param {{quantity?:undefined} | {quantity:number, total_gifts:number}} g + * @returns {boolean} + */ module.exports = g => g.quantity && (g.quantity - g.total_gifts <= 0) From 4e039c102aedffdcf34c6c12f1d4a199e9f786e6 Mon Sep 17 00:00:00 2001 From: Eric Schultz Date: Mon, 16 Dec 2024 16:01:18 -0600 Subject: [PATCH 206/208] Minor cleanup in tickets/wizard.js --- client/js/tickets/wizard.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/js/tickets/wizard.js b/client/js/tickets/wizard.js index 1e5929d5a..8788352ae 100644 --- a/client/js/tickets/wizard.js +++ b/client/js/tickets/wizard.js @@ -1,8 +1,8 @@ // License: LGPL-3.0-or-later require('../cards/create') -var request = require('../common/super-agent-promise') -var create_card = require('../cards/create') -var format_err = require('../common/format_response_error') +const request = require('../common/super-agent-promise') +const create_card = require('../cards/create') +const format_err = require('../common/format_response_error') var path = '/nonprofits/' + app.nonprofit_id + '/events/' + appl.event_id + '/tickets' const R = require('ramda') @@ -126,7 +126,6 @@ appl.def('ticket_wiz', { set_tickets: function (form_obj) { hide_err() var tickets = [] - var total_amount = 0 var total_quantity = 0 for (var key in form_obj.tickets) { var ticket = form_obj.tickets[key] From 9d61473b34187f067b4e853d7d8c273a706a6891 Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 19 Dec 2024 11:38:48 -0600 Subject: [PATCH 207/208] remove ramda from spec --- spec/js/nonprofits/donate/amount-step-spec.js | 5 ++--- spec/js/nonprofits/donate/wizard-spec.js | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/js/nonprofits/donate/amount-step-spec.js b/spec/js/nonprofits/donate/amount-step-spec.js index 20518ed7d..d971bb30d 100644 --- a/spec/js/nonprofits/donate/amount-step-spec.js +++ b/spec/js/nonprofits/donate/amount-step-spec.js @@ -3,7 +3,6 @@ const snabbdom = require('snabbdom') const flyd = require('flyd') const render = require('ff-core/render') const amount = require("../../../../client/js/nonprofits/donate/amount-step") -const R = require('ramda') const assert = require('assert') window.log = x => y => console.log(x,y) @@ -36,14 +35,14 @@ const init = (donationDefaults, params$) => { return streams } -const allText = R.map(R.prop('textContent')) +const allText = (input => input.map(item => item.textContent)) const defaultDesigOptions = ['Choose a designation (optional)', 'Use my donation where most needed'] suite("donate wiz / amount step") test("shows a designation dropdown if the multiple_designations param is set", ()=> { let streams = init({}, flyd.stream({multiple_designations: ['a','b']})) let options = allText(streams.dom$().querySelectorAll('.donate-designationDropdown option')) - assert.deepEqual(options, R.concat(defaultDesigOptions, ['a', 'b'])) + assert.deepEqual(options, [...defaultDesigOptions, 'a', 'b']) }) test('sets no designation with a dropdown on the default value', () => { diff --git a/spec/js/nonprofits/donate/wizard-spec.js b/spec/js/nonprofits/donate/wizard-spec.js index e97e489a1..81b962e43 100644 --- a/spec/js/nonprofits/donate/wizard-spec.js +++ b/spec/js/nonprofits/donate/wizard-spec.js @@ -3,7 +3,6 @@ const snabbdom = require('snabbdom') const flyd = require('flyd') const render = require('ff-core/render') const wiz = require("../../../../client/js/nonprofits/donate/wizard") -const R = require('ramda') const assert = require('assert') window.log = x => y => console.log(x,y) @@ -41,7 +40,7 @@ suite("donate wizzzzz") test("initializes amount, info, and payment steps", ()=> { let streams = init() let labels = streams.dom$().querySelectorAll('.ff-wizard-index-label') - assert.deepEqual(R.map(R.prop('textContent'), labels), ['Amount', 'Info', 'Payment']) + assert.deepEqual(labels.map(l => l.text), ['Amount', 'Info', 'Payment']) }) test("shows the nonprofit name without a campaign", () => { From cb092d085b231babc8346acb6401594b9c9513fe Mon Sep 17 00:00:00 2001 From: Ben Hejkal Date: Thu, 19 Dec 2024 11:54:46 -0600 Subject: [PATCH 208/208] remove ramda imports from super-admin --- client/js/super-admin/nonprofits-table.js | 1 - client/js/super-admin/page.js | 1 - client/js/super-admin/profiles-table.js | 1 - 3 files changed, 3 deletions(-) diff --git a/client/js/super-admin/nonprofits-table.js b/client/js/super-admin/nonprofits-table.js index 902dfec05..46bc22e65 100644 --- a/client/js/super-admin/nonprofits-table.js +++ b/client/js/super-admin/nonprofits-table.js @@ -1,5 +1,4 @@ // License: LGPL-3.0-or-later -const R = require('ramda') const h = require('flimflam/h') const searchTable = require('../components/search-table') diff --git a/client/js/super-admin/page.js b/client/js/super-admin/page.js index a3491c7c1..6c3552a6d 100644 --- a/client/js/super-admin/page.js +++ b/client/js/super-admin/page.js @@ -1,5 +1,4 @@ // License: LGPL-3.0-or-later -const R = require('ramda') const h = require('flimflam/h') const flyd = require('flimflam/flyd') const render = require('flimflam/render') diff --git a/client/js/super-admin/profiles-table.js b/client/js/super-admin/profiles-table.js index 040d60c4b..4442a1df5 100644 --- a/client/js/super-admin/profiles-table.js +++ b/client/js/super-admin/profiles-table.js @@ -1,5 +1,4 @@ // License: LGPL-3.0-or-later -const R = require('ramda') const h = require('flimflam/h') const searchTable = require('../components/search-table') const request = require('../common/client')