From ed170d8d90bb43fed9c2d4de114f08c41a8cc79c Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Thu, 3 Aug 2017 16:58:39 -0400 Subject: [PATCH 01/40] Initial reordering --- src/js/components/search/SearchSidebar.jsx | 25 +++++++--------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/src/js/components/search/SearchSidebar.jsx b/src/js/components/search/SearchSidebar.jsx index 7a5cd6f99e..381d37726f 100644 --- a/src/js/components/search/SearchSidebar.jsx +++ b/src/js/components/search/SearchSidebar.jsx @@ -10,8 +10,6 @@ import AwardTypeContainer from 'containers/search/filters/AwardTypeContainer'; import TimePeriodContainer from 'containers/search/filters/TimePeriodContainer'; import AgencyContainer from 'containers/search/filters/AgencyContainer'; import LocationSearchContainer from 'containers/search/filters/location/LocationSearchContainer'; -import BudgetCategorySearchContainer - from 'containers/search/filters/budgetCategory/BudgetCategorySearchContainer'; import RecipientSearchContainer from 'containers/search/filters/recipient/RecipientSearchContainer'; import KeywordContainer from 'containers/search/filters/KeywordContainer'; import AwardIDSearchContainer from 'containers/search/filters/awardID/AwardIDSearchContainer'; @@ -27,25 +25,23 @@ const filters = { options: [ 'Search', 'Time Period', - 'Place of Performance', - 'Budget Categories', + 'Award Type', 'Agencies', 'Recipients', - 'Award Type', - 'Award ID', + 'Place of Performance', 'Award Amount', + 'Award ID', 'Other Award Items' ], components: [ KeywordContainer, TimePeriodContainer, - LocationSearchContainer, - BudgetCategorySearchContainer, + AwardTypeContainer, AgencyContainer, RecipientSearchContainer, - AwardTypeContainer, - AwardIDSearchContainer, + LocationSearchContainer, AwardAmountSearchContainer, + AwardIDSearchContainer, OtherFilters ] }; @@ -62,13 +58,8 @@ export default class SearchSidebar extends React.Component { render() { const expanded = []; filters.options.forEach(() => { - // collapse if mobile, otherwise expand - if (this.props.mobile) { - expanded.push(false); - } - else { - expanded.push(true); - } + // Collapse all by default + expanded.push(false); }); let mobileHeader = null; From 9e0275a0af5dfc34f7500f19014b8116f67721cd Mon Sep 17 00:00:00 2001 From: Elizabeth Salita Date: Fri, 4 Aug 2017 09:42:15 -0400 Subject: [PATCH 02/40] Added back white/gray row shading --- .../agencyLanding/table/TableCell.jsx | 31 ------------------- .../agencyLanding/table/TableRow.jsx | 6 ++++ .../table/cells/AgencyLinkCell.jsx | 9 +----- 3 files changed, 7 insertions(+), 39 deletions(-) delete mode 100644 src/js/components/agencyLanding/table/TableCell.jsx diff --git a/src/js/components/agencyLanding/table/TableCell.jsx b/src/js/components/agencyLanding/table/TableCell.jsx deleted file mode 100644 index a69a1fbea3..0000000000 --- a/src/js/components/agencyLanding/table/TableCell.jsx +++ /dev/null @@ -1,31 +0,0 @@ -/** - * TableCell.jsx - * Created by Lizzie Salita 08/01/17 - **/ - -import React from 'react'; -import PropTypes from 'prop-types'; - -const propTypes = { - rowIndex: PropTypes.number, - column: PropTypes.object -}; - -export default class TableCell extends React.Component { - render() { - let rowClass = 'row-even'; - if (this.props.rowIndex % 2 === 0) { - // row index is zero-based - rowClass = 'row-odd'; - } - - return ( - - {this.props.column.cell(this.props.rowIndex)} - - ); - } -} - -TableCell.propTypes = propTypes; - diff --git a/src/js/components/agencyLanding/table/TableRow.jsx b/src/js/components/agencyLanding/table/TableRow.jsx index e7981772e7..03aa094f9b 100644 --- a/src/js/components/agencyLanding/table/TableRow.jsx +++ b/src/js/components/agencyLanding/table/TableRow.jsx @@ -17,11 +17,16 @@ const propTypes = { export default class TableRow extends React.PureComponent { render() { + let rowClass = 'row-even'; + if (this.props.rowIndex % 2 === 0) { + rowClass = 'row-odd'; + } const cells = this.props.columns.map((column) => { if (column.columnName === 'agency_name') { // show the agency link cell return ( +
{name} From 81ef52fce1e6f59e29c0d259852fa0c3869292da Mon Sep 17 00:00:00 2001 From: Elizabeth Salita Date: Mon, 7 Aug 2017 17:30:58 -0400 Subject: [PATCH 03/40] Helper with business type fields to check --- src/js/helpers/businessTypesHelper.js | 347 ++++++++++++++++++++++++++ 1 file changed, 347 insertions(+) create mode 100644 src/js/helpers/businessTypesHelper.js diff --git a/src/js/helpers/businessTypesHelper.js b/src/js/helpers/businessTypesHelper.js new file mode 100644 index 0000000000..09e3b4fb11 --- /dev/null +++ b/src/js/helpers/businessTypesHelper.js @@ -0,0 +1,347 @@ +/** + * businessTypesHelper.js + * Created by Lizzie Salita 8/7/17 + */ + +export const getBusinessTypes = () => { + const businessTypes = + [ + { + displayName: 'Alaskan Native Owned Corporation or Firm', + fieldName: 'alaskan_native_owned_corporation_or_firm' + }, + { + displayName: 'American Indian Owned Business', + fieldName: 'american_indian_owned_business' + }, + { + displayName: 'Indian Tribe Federally Recognized', + fieldName: 'indian_tribe_federally_recognized' + }, + { + displayName: 'Native Hawaiian Owned Business', + fieldName: 'native_hawaiian_owned_business' + }, + { + displayName: 'Tribally Owned Business', + fieldName: 'tribally_owned_business' + }, + { + displayName: 'Veteran Owned Business', + fieldName: 'veteran_owned_business' + }, + { + displayName: 'Service Disabled Veteran Owned Business', + fieldName: 'service_disabled_veteran_owned_business' + }, + { + displayName: 'Woman Owned business', + fieldName: 'woman_owned_business' + }, + { + displayName: 'Women Owned Small Business', + fieldName: 'women_owned_small_business' + }, + { + displayName: 'Economically Disadvantaged Women Owned Small Business', + fieldName: 'economically_disadvantaged_women_owned_small_business' + }, + { + displayName: 'Joint Venture Women Owned Small Business', + fieldName: 'joint_venture_women_owned_small_business' + }, + { + displayName: 'Joint Venture Economically Disadvantaged Women Owned Small Business', + fieldName: 'joint_venture_economic_disadvantaged_women_owned_small_bus' + }, + { + displayName: 'Minority Owned Business', + fieldName: 'minority_owned_business' + }, + { + displayName: 'Subcontinent Asian Asian - Indian American Owned Business', + fieldName: 'subcontinent_asian_asian_indian_american_owned_business' + }, + { + displayName: 'Asian Pacific American Owned business', + fieldName: 'asian_pacific_american_owned_business' + }, + { + displayName: 'Black American Owned Business', + fieldName: 'black_american_owned_business' + }, + { + displayName: 'Hispanic American Owned Business', + fieldName: 'hispanic_american_owned_business' + }, + { + displayName: 'Native American Owned Business', + fieldName: 'native_american_owned_business' + }, + { + displayName: 'Other Minority Owned Business', + fieldName: 'other_minority_owned_business' + }, + { + displayName: 'Emerging Small business', + fieldName: 'emerging_small_business' + }, + { + displayName: 'Community Developed Corporation Owned Firm', + fieldName: 'community_development_corporation' + }, + { + displayName: 'Labor Surplus Area Firm', + fieldName: 'labor_surplus_area_firm' + }, + { + displayName: 'U.S. Federal Government', + fieldName: 'us_federal_government' + }, + { + displayName: 'Federally Funded Research and Development Corp', + fieldName: 'federally_funded_research_and_development_corp' + }, + { + displayName: 'Federal Agency', + fieldName: 'federal_agency' + }, + { + displayName: 'U.S. State Government', + fieldName: 'us_state_government' + }, + { + displayName: 'U.S. Local Government', + fieldName: 'us_local_government' + }, + { + displayName: 'City Local Government', + fieldName: 'city_local_government' + }, + { + displayName: 'County Local Government', + fieldName: 'county_local_government' + }, + { + displayName: 'Inter-Municipal Local Government', + fieldName: 'inter_municipal_local_government' + }, + { + displayName: 'Local Government Owned', + fieldName: 'local_government_owned' + }, + { + displayName: 'Municipality Local Government', + fieldName: 'municipality_local_government' + }, + { + displayName: 'School District Local Government', + fieldName: 'school_district_local_government' + }, + { + displayName: 'Township Local Government', + fieldName: 'township_local_government' + }, + { + displayName: 'U.S. Tribal Government', + fieldName: 'us_tribal_government' + }, + { + displayName: 'Foreign Government', + fieldName: 'foreign_government' + }, + { + displayName: 'Corporate Entity Not Tax Exempt', + fieldName: 'corporate_entity_not_tax_exempt' + }, + { + displayName: 'Corporate Entity tax Exempt', + fieldName: 'corporate_entity_tax_exempt' + }, + { + displayName: 'Partnership or Limited Liability Partnership', + fieldName: 'partnership_or_limited_liability_partnership' + }, + { + displayName: 'Sole Proprietorship', + fieldName: 'sole_proprietorship' + }, + { + displayName: 'Small Agricultural Cooperative', + fieldName: 'small_agricultural_cooperative' + }, + { + displayName: 'International Organization', + fieldName: 'international_organization' + }, + { + displayName: 'U.S. Government Entity', + fieldName: 'us_government_entity' + }, + { + displayName: 'Community Development Corporation', + fieldName: 'community_development_corporation' + }, + { + displayName: 'Domestic Shelter', + fieldName: 'domestic_shelter' + }, + { + displayName: 'Educational Institution', + fieldName: 'educational_institution' + }, + { + displayName: 'Foundation', + fieldName: 'foundation' + }, + { + displayName: 'Hospital Flag', + fieldName: 'hospital_flag' + }, + { + displayName: 'Manufacturer of Goods', + fieldName: 'manufacturer_of_goods' + }, + { + displayName: 'Veterinary Hospital', + fieldName: 'veterinary_hospital' + }, + { + displayName: 'Hispanic Servicing Institution', + fieldName: 'hispanic_servicing_institution' + }, + { + displayName: 'Receives Contracts and Grants', + fieldName: 'receives_contracts_and_grants' + }, + { + displayName: 'Airport Authority', + fieldName: 'airport_authority' + }, + { + displayName: 'Council of Governments', + fieldName: 'council_of_governments' + }, + { + displayName: 'Housing Authorities Public/Tribal', + fieldName: 'housing_authorities_public_tribal' + }, + { + displayName: 'Interstate Entity', + fieldName: 'interstate_entity' + }, + { + displayName: 'Planning Commission', + fieldName: 'planning_commission' + }, + { + displayName: 'Port Authority', + fieldName: 'port_authority' + }, + { + displayName: 'Transit Authority', + fieldName: 'transit_authority' + }, + { + displayName: 'Subchapter S Corporation', + fieldName: 'subchapter_scorporation' + }, + { + displayName: 'Limited Liability Corporation', + fieldName: 'limited_liability_corporation' + }, + { + displayName: 'Foreign Owned and Located', + fieldName: 'foreign_owned_and_located' + }, + { + displayName: 'For Profit Organization', + fieldName: 'for_profit_organization' + }, + { + displayName: 'Nonprofit Organization', + fieldName: 'nonprofit_organization' + }, + { + displayName: 'Other Not For Profit Organization', + fieldName: 'other_not_for_profit_organization' + }, + { + displayName: 'The AbilityOne Program', + fieldName: 'the_ability_one_program' + }, + { + displayName: 'Private University or College ', + fieldName: 'private_university_or_college' + }, + { + displayName: 'State Controlled Institution of Higher Learning', + fieldName: 'state_controlled_institution_of_higher_learning' + }, + { + displayName: '1862 Land grant College', + fieldName: 'c1862_land_grant_college' + }, + { + displayName: '1890 land grant College', + fieldName: 'c1890_land_grant_college' + }, + { + displayName: '1994 Land Grant College', + fieldName: 'c1994_land_grant_college' + }, + { + displayName: 'Minority Institution', + fieldName: 'minority_institution' + }, + { + displayName: 'Historically Black College or University', + fieldName: 'historically_black_college' + }, + { + displayName: 'Tribal College', + fieldName: 'tribal_college' + }, + { + displayName: 'Alaskan Native Servicing Institution', + fieldName: 'alaskan_native_servicing_institution' + }, + { + displayName: 'Native Hawaiian Servicing Institution', + fieldName: 'native_hawaiian_servicing_institution' + }, + { + displayName: 'School of Forestry', + fieldName: 'school_of_forestry' + }, + { + displayName: 'Veterinary College', + fieldName: 'veterinary_college' + }, + { + displayName: 'DoT Certified Disadvantaged Business Enterprise', + fieldName: 'dot_certified_disadvantage' + }, + { + displayName: 'Self-Certified Small Disadvantaged Business', + fieldName: 'self_certified_small_disadvantaged_business' + }, + { + displayName: 'Small Disadvantaged Business', + fieldName: 'small_disadvantaged_business' + }, + { + displayName: '8a Program Participant', + fieldName: 'c8a_program_participant' + }, + { + displayName: 'Historically Underutilized Business Zone HUBZone Firm', + fieldName: 'historically_underutilized_business_zone' + }, + { + displayName: 'SBA Certified 8 a Joint Venture', + fieldName: 'sba_certified_8a_joint_venture' + } + ]; + return businessTypes; +}; \ No newline at end of file From 458c3cd646cb00496ab1e0f72e45aeaba4c7243d Mon Sep 17 00:00:00 2001 From: Elizabeth Salita Date: Tue, 8 Aug 2017 10:41:36 -0400 Subject: [PATCH 04/40] List applicable boolean fields for contracts --- src/js/components/award/RecipientInfo.jsx | 21 +- src/js/helpers/businessTypesHelper.js | 680 +++++++++++----------- 2 files changed, 359 insertions(+), 342 deletions(-) diff --git a/src/js/components/award/RecipientInfo.jsx b/src/js/components/award/RecipientInfo.jsx index d5a6033e1f..771a19d501 100644 --- a/src/js/components/award/RecipientInfo.jsx +++ b/src/js/components/award/RecipientInfo.jsx @@ -7,6 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { toLower, includes } from 'lodash'; import { awardTypeGroups } from 'dataMapping/search/awardType'; +import * as BusinessTypesHelper from 'helpers/businessTypesHelper'; import InfoSnippet from './InfoSnippet'; import RecipientAddress from './RecipientAddress'; @@ -50,15 +51,31 @@ export default class RecipientInfo extends React.Component { let businessType = "Not Available"; const isContract = includes(awardTypeGroups.contracts, this.props.recipient.award_type); + const allBusinessTypes = BusinessTypesHelper.getBusinessTypes(); + const businessTypesArray = []; + allBusinessTypes.forEach((type) => { + if (recipient.latest_transaction.recipient[type.fieldName] === '1') { + businessTypesArray.push(type.displayName); + } + }); + if (this.props.recipient.recipient_parent_duns) { parentDuns = this.props.recipient.recipient_parent_duns; } if (this.props.recipient.recipient_duns) { duns = this.props.recipient.recipient_duns; } - if (this.props.recipient.recipient_business_type) { + if (this.props.recipient.recipient_business_type !== 'Unknown Types') { + // Grants, Loans, Direct Payments, and Insurance businessType = this.props.recipient.recipient_business_type; } + else if (businessTypesArray.length > 0) { + businessType = ''; + businessTypesArray.forEach((type) => { + businessType += `${type}, `; + }); + } + let parentDunsSnippet = ( {parentDunsSnippet} ); diff --git a/src/js/helpers/businessTypesHelper.js b/src/js/helpers/businessTypesHelper.js index 09e3b4fb11..1fd98a3912 100644 --- a/src/js/helpers/businessTypesHelper.js +++ b/src/js/helpers/businessTypesHelper.js @@ -4,344 +4,344 @@ */ export const getBusinessTypes = () => { - const businessTypes = - [ - { - displayName: 'Alaskan Native Owned Corporation or Firm', - fieldName: 'alaskan_native_owned_corporation_or_firm' - }, - { - displayName: 'American Indian Owned Business', - fieldName: 'american_indian_owned_business' - }, - { - displayName: 'Indian Tribe Federally Recognized', - fieldName: 'indian_tribe_federally_recognized' - }, - { - displayName: 'Native Hawaiian Owned Business', - fieldName: 'native_hawaiian_owned_business' - }, - { - displayName: 'Tribally Owned Business', - fieldName: 'tribally_owned_business' - }, - { - displayName: 'Veteran Owned Business', - fieldName: 'veteran_owned_business' - }, - { - displayName: 'Service Disabled Veteran Owned Business', - fieldName: 'service_disabled_veteran_owned_business' - }, - { - displayName: 'Woman Owned business', - fieldName: 'woman_owned_business' - }, - { - displayName: 'Women Owned Small Business', - fieldName: 'women_owned_small_business' - }, - { - displayName: 'Economically Disadvantaged Women Owned Small Business', - fieldName: 'economically_disadvantaged_women_owned_small_business' - }, - { - displayName: 'Joint Venture Women Owned Small Business', - fieldName: 'joint_venture_women_owned_small_business' - }, - { - displayName: 'Joint Venture Economically Disadvantaged Women Owned Small Business', - fieldName: 'joint_venture_economic_disadvantaged_women_owned_small_bus' - }, - { - displayName: 'Minority Owned Business', - fieldName: 'minority_owned_business' - }, - { - displayName: 'Subcontinent Asian Asian - Indian American Owned Business', - fieldName: 'subcontinent_asian_asian_indian_american_owned_business' - }, - { - displayName: 'Asian Pacific American Owned business', - fieldName: 'asian_pacific_american_owned_business' - }, - { - displayName: 'Black American Owned Business', - fieldName: 'black_american_owned_business' - }, - { - displayName: 'Hispanic American Owned Business', - fieldName: 'hispanic_american_owned_business' - }, - { - displayName: 'Native American Owned Business', - fieldName: 'native_american_owned_business' - }, - { - displayName: 'Other Minority Owned Business', - fieldName: 'other_minority_owned_business' - }, - { - displayName: 'Emerging Small business', - fieldName: 'emerging_small_business' - }, - { - displayName: 'Community Developed Corporation Owned Firm', - fieldName: 'community_development_corporation' - }, - { - displayName: 'Labor Surplus Area Firm', - fieldName: 'labor_surplus_area_firm' - }, - { - displayName: 'U.S. Federal Government', - fieldName: 'us_federal_government' - }, - { - displayName: 'Federally Funded Research and Development Corp', - fieldName: 'federally_funded_research_and_development_corp' - }, - { - displayName: 'Federal Agency', - fieldName: 'federal_agency' - }, - { - displayName: 'U.S. State Government', - fieldName: 'us_state_government' - }, - { - displayName: 'U.S. Local Government', - fieldName: 'us_local_government' - }, - { - displayName: 'City Local Government', - fieldName: 'city_local_government' - }, - { - displayName: 'County Local Government', - fieldName: 'county_local_government' - }, - { - displayName: 'Inter-Municipal Local Government', - fieldName: 'inter_municipal_local_government' - }, - { - displayName: 'Local Government Owned', - fieldName: 'local_government_owned' - }, - { - displayName: 'Municipality Local Government', - fieldName: 'municipality_local_government' - }, - { - displayName: 'School District Local Government', - fieldName: 'school_district_local_government' - }, - { - displayName: 'Township Local Government', - fieldName: 'township_local_government' - }, - { - displayName: 'U.S. Tribal Government', - fieldName: 'us_tribal_government' - }, - { - displayName: 'Foreign Government', - fieldName: 'foreign_government' - }, - { - displayName: 'Corporate Entity Not Tax Exempt', - fieldName: 'corporate_entity_not_tax_exempt' - }, - { - displayName: 'Corporate Entity tax Exempt', - fieldName: 'corporate_entity_tax_exempt' - }, - { - displayName: 'Partnership or Limited Liability Partnership', - fieldName: 'partnership_or_limited_liability_partnership' - }, - { - displayName: 'Sole Proprietorship', - fieldName: 'sole_proprietorship' - }, - { - displayName: 'Small Agricultural Cooperative', - fieldName: 'small_agricultural_cooperative' - }, - { - displayName: 'International Organization', - fieldName: 'international_organization' - }, - { - displayName: 'U.S. Government Entity', - fieldName: 'us_government_entity' - }, - { - displayName: 'Community Development Corporation', - fieldName: 'community_development_corporation' - }, - { - displayName: 'Domestic Shelter', - fieldName: 'domestic_shelter' - }, - { - displayName: 'Educational Institution', - fieldName: 'educational_institution' - }, - { - displayName: 'Foundation', - fieldName: 'foundation' - }, - { - displayName: 'Hospital Flag', - fieldName: 'hospital_flag' - }, - { - displayName: 'Manufacturer of Goods', - fieldName: 'manufacturer_of_goods' - }, - { - displayName: 'Veterinary Hospital', - fieldName: 'veterinary_hospital' - }, - { - displayName: 'Hispanic Servicing Institution', - fieldName: 'hispanic_servicing_institution' - }, - { - displayName: 'Receives Contracts and Grants', - fieldName: 'receives_contracts_and_grants' - }, - { - displayName: 'Airport Authority', - fieldName: 'airport_authority' - }, - { - displayName: 'Council of Governments', - fieldName: 'council_of_governments' - }, - { - displayName: 'Housing Authorities Public/Tribal', - fieldName: 'housing_authorities_public_tribal' - }, - { - displayName: 'Interstate Entity', - fieldName: 'interstate_entity' - }, - { - displayName: 'Planning Commission', - fieldName: 'planning_commission' - }, - { - displayName: 'Port Authority', - fieldName: 'port_authority' - }, - { - displayName: 'Transit Authority', - fieldName: 'transit_authority' - }, - { - displayName: 'Subchapter S Corporation', - fieldName: 'subchapter_scorporation' - }, - { - displayName: 'Limited Liability Corporation', - fieldName: 'limited_liability_corporation' - }, - { - displayName: 'Foreign Owned and Located', - fieldName: 'foreign_owned_and_located' - }, - { - displayName: 'For Profit Organization', - fieldName: 'for_profit_organization' - }, - { - displayName: 'Nonprofit Organization', - fieldName: 'nonprofit_organization' - }, - { - displayName: 'Other Not For Profit Organization', - fieldName: 'other_not_for_profit_organization' - }, - { - displayName: 'The AbilityOne Program', - fieldName: 'the_ability_one_program' - }, - { - displayName: 'Private University or College ', - fieldName: 'private_university_or_college' - }, - { - displayName: 'State Controlled Institution of Higher Learning', - fieldName: 'state_controlled_institution_of_higher_learning' - }, - { - displayName: '1862 Land grant College', - fieldName: 'c1862_land_grant_college' - }, - { - displayName: '1890 land grant College', - fieldName: 'c1890_land_grant_college' - }, - { - displayName: '1994 Land Grant College', - fieldName: 'c1994_land_grant_college' - }, - { - displayName: 'Minority Institution', - fieldName: 'minority_institution' - }, - { - displayName: 'Historically Black College or University', - fieldName: 'historically_black_college' - }, - { - displayName: 'Tribal College', - fieldName: 'tribal_college' - }, - { - displayName: 'Alaskan Native Servicing Institution', - fieldName: 'alaskan_native_servicing_institution' - }, - { - displayName: 'Native Hawaiian Servicing Institution', - fieldName: 'native_hawaiian_servicing_institution' - }, - { - displayName: 'School of Forestry', - fieldName: 'school_of_forestry' - }, - { - displayName: 'Veterinary College', - fieldName: 'veterinary_college' - }, - { - displayName: 'DoT Certified Disadvantaged Business Enterprise', - fieldName: 'dot_certified_disadvantage' - }, - { - displayName: 'Self-Certified Small Disadvantaged Business', - fieldName: 'self_certified_small_disadvantaged_business' - }, - { - displayName: 'Small Disadvantaged Business', - fieldName: 'small_disadvantaged_business' - }, - { - displayName: '8a Program Participant', - fieldName: 'c8a_program_participant' - }, - { - displayName: 'Historically Underutilized Business Zone HUBZone Firm', - fieldName: 'historically_underutilized_business_zone' - }, - { - displayName: 'SBA Certified 8 a Joint Venture', - fieldName: 'sba_certified_8a_joint_venture' - } - ]; + const businessTypes = + [ + { + displayName: 'Alaskan Native Owned Corporation or Firm', + fieldName: 'alaskan_native_owned_corporation_or_firm' + }, + { + displayName: 'American Indian Owned Business', + fieldName: 'american_indian_owned_business' + }, + { + displayName: 'Indian Tribe Federally Recognized', + fieldName: 'indian_tribe_federally_recognized' + }, + { + displayName: 'Native Hawaiian Owned Business', + fieldName: 'native_hawaiian_owned_business' + }, + { + displayName: 'Tribally Owned Business', + fieldName: 'tribally_owned_business' + }, + { + displayName: 'Veteran Owned Business', + fieldName: 'veteran_owned_business' + }, + { + displayName: 'Service Disabled Veteran Owned Business', + fieldName: 'service_disabled_veteran_owned_business' + }, + { + displayName: 'Woman Owned business', + fieldName: 'woman_owned_business' + }, + { + displayName: 'Women Owned Small Business', + fieldName: 'women_owned_small_business' + }, + { + displayName: 'Economically Disadvantaged Women Owned Small Business', + fieldName: 'economically_disadvantaged_women_owned_small_business' + }, + { + displayName: 'Joint Venture Women Owned Small Business', + fieldName: 'joint_venture_women_owned_small_business' + }, + { + displayName: 'Joint Venture Economically Disadvantaged Women Owned Small Business', + fieldName: 'joint_venture_economic_disadvantaged_women_owned_small_bus' + }, + { + displayName: 'Minority Owned Business', + fieldName: 'minority_owned_business' + }, + { + displayName: 'Subcontinent Asian Asian - Indian American Owned Business', + fieldName: 'subcontinent_asian_asian_indian_american_owned_business' + }, + { + displayName: 'Asian Pacific American Owned business', + fieldName: 'asian_pacific_american_owned_business' + }, + { + displayName: 'Black American Owned Business', + fieldName: 'black_american_owned_business' + }, + { + displayName: 'Hispanic American Owned Business', + fieldName: 'hispanic_american_owned_business' + }, + { + displayName: 'Native American Owned Business', + fieldName: 'native_american_owned_business' + }, + { + displayName: 'Other Minority Owned Business', + fieldName: 'other_minority_owned_business' + }, + { + displayName: 'Emerging Small business', + fieldName: 'emerging_small_business' + }, + { + displayName: 'Community Developed Corporation Owned Firm', + fieldName: 'community_development_corporation' + }, + { + displayName: 'Labor Surplus Area Firm', + fieldName: 'labor_surplus_area_firm' + }, + { + displayName: 'U.S. Federal Government', + fieldName: 'us_federal_government' + }, + { + displayName: 'Federally Funded Research and Development Corp', + fieldName: 'federally_funded_research_and_development_corp' + }, + { + displayName: 'Federal Agency', + fieldName: 'federal_agency' + }, + { + displayName: 'U.S. State Government', + fieldName: 'us_state_government' + }, + { + displayName: 'U.S. Local Government', + fieldName: 'us_local_government' + }, + { + displayName: 'City Local Government', + fieldName: 'city_local_government' + }, + { + displayName: 'County Local Government', + fieldName: 'county_local_government' + }, + { + displayName: 'Inter-Municipal Local Government', + fieldName: 'inter_municipal_local_government' + }, + { + displayName: 'Local Government Owned', + fieldName: 'local_government_owned' + }, + { + displayName: 'Municipality Local Government', + fieldName: 'municipality_local_government' + }, + { + displayName: 'School District Local Government', + fieldName: 'school_district_local_government' + }, + { + displayName: 'Township Local Government', + fieldName: 'township_local_government' + }, + { + displayName: 'U.S. Tribal Government', + fieldName: 'us_tribal_government' + }, + { + displayName: 'Foreign Government', + fieldName: 'foreign_government' + }, + { + displayName: 'Corporate Entity Not Tax Exempt', + fieldName: 'corporate_entity_not_tax_exempt' + }, + { + displayName: 'Corporate Entity tax Exempt', + fieldName: 'corporate_entity_tax_exempt' + }, + { + displayName: 'Partnership or Limited Liability Partnership', + fieldName: 'partnership_or_limited_liability_partnership' + }, + { + displayName: 'Sole Proprietorship', + fieldName: 'sole_proprietorship' + }, + { + displayName: 'Small Agricultural Cooperative', + fieldName: 'small_agricultural_cooperative' + }, + { + displayName: 'International Organization', + fieldName: 'international_organization' + }, + { + displayName: 'U.S. Government Entity', + fieldName: 'us_government_entity' + }, + { + displayName: 'Community Development Corporation', + fieldName: 'community_development_corporation' + }, + { + displayName: 'Domestic Shelter', + fieldName: 'domestic_shelter' + }, + { + displayName: 'Educational Institution', + fieldName: 'educational_institution' + }, + { + displayName: 'Foundation', + fieldName: 'foundation' + }, + { + displayName: 'Hospital Flag', + fieldName: 'hospital_flag' + }, + { + displayName: 'Manufacturer of Goods', + fieldName: 'manufacturer_of_goods' + }, + { + displayName: 'Veterinary Hospital', + fieldName: 'veterinary_hospital' + }, + { + displayName: 'Hispanic Servicing Institution', + fieldName: 'hispanic_servicing_institution' + }, + { + displayName: 'Receives Contracts and Grants', + fieldName: 'receives_contracts_and_grants' + }, + { + displayName: 'Airport Authority', + fieldName: 'airport_authority' + }, + { + displayName: 'Council of Governments', + fieldName: 'council_of_governments' + }, + { + displayName: 'Housing Authorities Public/Tribal', + fieldName: 'housing_authorities_public_tribal' + }, + { + displayName: 'Interstate Entity', + fieldName: 'interstate_entity' + }, + { + displayName: 'Planning Commission', + fieldName: 'planning_commission' + }, + { + displayName: 'Port Authority', + fieldName: 'port_authority' + }, + { + displayName: 'Transit Authority', + fieldName: 'transit_authority' + }, + { + displayName: 'Subchapter S Corporation', + fieldName: 'subchapter_scorporation' + }, + { + displayName: 'Limited Liability Corporation', + fieldName: 'limited_liability_corporation' + }, + { + displayName: 'Foreign Owned and Located', + fieldName: 'foreign_owned_and_located' + }, + { + displayName: 'For Profit Organization', + fieldName: 'for_profit_organization' + }, + { + displayName: 'Nonprofit Organization', + fieldName: 'nonprofit_organization' + }, + { + displayName: 'Other Not For Profit Organization', + fieldName: 'other_not_for_profit_organization' + }, + { + displayName: 'The AbilityOne Program', + fieldName: 'the_ability_one_program' + }, + { + displayName: 'Private University or College ', + fieldName: 'private_university_or_college' + }, + { + displayName: 'State Controlled Institution of Higher Learning', + fieldName: 'state_controlled_institution_of_higher_learning' + }, + { + displayName: '1862 Land grant College', + fieldName: 'c1862_land_grant_college' + }, + { + displayName: '1890 land grant College', + fieldName: 'c1890_land_grant_college' + }, + { + displayName: '1994 Land Grant College', + fieldName: 'c1994_land_grant_college' + }, + { + displayName: 'Minority Institution', + fieldName: 'minority_institution' + }, + { + displayName: 'Historically Black College or University', + fieldName: 'historically_black_college' + }, + { + displayName: 'Tribal College', + fieldName: 'tribal_college' + }, + { + displayName: 'Alaskan Native Servicing Institution', + fieldName: 'alaskan_native_servicing_institution' + }, + { + displayName: 'Native Hawaiian Servicing Institution', + fieldName: 'native_hawaiian_servicing_institution' + }, + { + displayName: 'School of Forestry', + fieldName: 'school_of_forestry' + }, + { + displayName: 'Veterinary College', + fieldName: 'veterinary_college' + }, + { + displayName: 'DoT Certified Disadvantaged Business Enterprise', + fieldName: 'dot_certified_disadvantage' + }, + { + displayName: 'Self-Certified Small Disadvantaged Business', + fieldName: 'self_certified_small_disadvantaged_business' + }, + { + displayName: 'Small Disadvantaged Business', + fieldName: 'small_disadvantaged_business' + }, + { + displayName: '8a Program Participant', + fieldName: 'c8a_program_participant' + }, + { + displayName: 'Historically Underutilized Business Zone HUBZone Firm', + fieldName: 'historically_underutilized_business_zone' + }, + { + displayName: 'SBA Certified 8 a Joint Venture', + fieldName: 'sba_certified_8a_joint_venture' + } + ]; return businessTypes; -}; \ No newline at end of file +}; From f7ad1f9a683c14722c416c43844d122fa44106f5 Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Tue, 8 Aug 2017 12:39:34 -0400 Subject: [PATCH 05/40] New Search Operation for the Awards page --- .../TimeVisualizationSectionContainer.jsx | 110 +------ src/js/helpers/searchHelper.js | 17 ++ src/js/models/search/SearchAwardsOperation.js | 268 ++++++++++++++++++ 3 files changed, 297 insertions(+), 98 deletions(-) create mode 100644 src/js/models/search/SearchAwardsOperation.js diff --git a/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx b/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx index 0864f96ba1..ff8a1e1b48 100644 --- a/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx +++ b/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx @@ -16,10 +16,8 @@ import * as searchFilterActions from 'redux/actions/search/searchFilterActions'; import * as resultsMetaActions from 'redux/actions/resultsMeta/resultsMetaActions'; import * as SearchHelper from 'helpers/searchHelper'; -import * as BudgetCategoryHelper from 'helpers/budgetCategoryHelper'; -import SearchTASCategoriesOperation from 'models/search/SearchTASCategoriesOperation'; -import SearchTransactionOperation from 'models/search/SearchTransactionOperation'; +import SearchAwardsOperation from 'models/search/SearchAwardsOperation'; const combinedActions = Object.assign({}, searchFilterActions, resultsMetaActions); @@ -36,33 +34,20 @@ export class TimeVisualizationSectionContainer extends React.Component { loading: true, groups: [], xSeries: [], - ySeries: [], - budgetFiltersSelected: false, - awardFiltersSelected: false + ySeries: [] }; this.apiRequest = null; } componentDidMount() { - this.setFilterStates(); + this.fetchData(); } componentDidUpdate(prevProps) { if (!isEqual(prevProps.reduxFilters, this.props.reduxFilters)) { - this.setFilterStates(); - } - } - - setFilterStates() { - this.setState({ - budgetFiltersSelected: - BudgetCategoryHelper.budgetFiltersSelected(this.props.reduxFilters), - awardFiltersSelected: - BudgetCategoryHelper.awardFiltersSelected(this.props.reduxFilters) - }, () => { this.fetchData(); - }); + } } fetchData() { @@ -75,91 +60,20 @@ export class TimeVisualizationSectionContainer extends React.Component { this.apiRequest.cancel(); } - // // Fetch data from the appropriate endpoint - if (this.state.awardFiltersSelected && this.state.budgetFiltersSelected) { - this.fetchComboRequest(); - } - else if (this.state.budgetFiltersSelected) { - this.fetchBudgetRequest(); - } - else if (this.state.awardFiltersSelected) { - this.fetchAwardRequest(); - // this.fetchTransactionData(); - } - else { - this.fetchUnfilteredRequest(); - // this.fetchBalanceData(); - } + // Fetch data from the Awards v2 endpoint + this.fetchAwards('Spending Over Time Visualization'); } - fetchUnfilteredRequest() { - // no filters have been selected - this.fetchTASCategories('Time visualization - unfiltered'); - } - - fetchBudgetRequest() { - // only budget filters have been applied - this.fetchTASCategories('Time visualization - budget filters'); - } - - fetchAwardRequest() { - // only award filters have been selected - this.fetchTransactions('Time visualization - award filters'); - } - - fetchComboRequest() { - // a combination of budget and award filters have been selected - this.fetchTransactions('Time visualization - combination'); - } - - fetchTransactions(auditTrail = null) { - const field = 'federal_action_obligation'; + fetchAwards(auditTrail = null) { const group = 'action_date__fy'; - const operation = new SearchTransactionOperation(); - operation.fromState(this.props.reduxFilters); - const searchParams = operation.toParams(); - - // Generate the API parameters - const apiParams = { - field, - group, - order: [group], - aggregate: 'sum', - filters: searchParams - }; - - if (auditTrail) { - apiParams.auditTrail = auditTrail; - } - - this.apiRequest = SearchHelper.performTransactionsTotalSearch(apiParams); - - this.apiRequest.promise - .then((res) => { - this.parseData(res.data, group); - this.apiRequest = null; - }) - .catch(() => { - this.apiRequest = null; - }); - } - - fetchTASCategories(auditTrail = null) { - // only budget filters have been selected - const field = 'obligations_incurred_by_program_object_class_cpe'; - const group = 'submission__reporting_fiscal_year'; - // generate the API parameters - const operation = new SearchTASCategoriesOperation(); + const operation = new SearchAwardsOperation(); operation.fromState(this.props.reduxFilters); - const searchParams = operation.toParams(); + const searchParams = operation.params(); // Generate the API parameters const apiParams = { - field, group, - order: [group], - aggregate: 'sum', filters: searchParams }; @@ -167,7 +81,7 @@ export class TimeVisualizationSectionContainer extends React.Component { apiParams.auditTrail = auditTrail; } - this.apiRequest = SearchHelper.performCategorySearch(apiParams); + this.apiRequest = SearchHelper.performSpendingOverTimeSearch(apiParams); this.apiRequest.promise .then((res) => { @@ -190,9 +104,9 @@ export class TimeVisualizationSectionContainer extends React.Component { data.results.forEach((item) => { groups.push(item[group]); xSeries.push([item[group]]); - ySeries.push([parseFloat(item.aggregate)]); + ySeries.push([parseFloat(item.aggregated_amount)]); - totalSpending += parseFloat(item.aggregate); + totalSpending += parseFloat(item.aggregated_amount); }); this.setState({ diff --git a/src/js/helpers/searchHelper.js b/src/js/helpers/searchHelper.js index a2ab81ee05..eae12d494b 100644 --- a/src/js/helpers/searchHelper.js +++ b/src/js/helpers/searchHelper.js @@ -229,6 +229,23 @@ export const fetchAwardTransaction = (params) => { }; }; +// Spending Over Time Visualization Endpoint +export const performSpendingOverTimeSearch = (params) => { + const source = CancelToken.source(); + return { + promise: Axios.request({ + url: `v2/search/spending_over_time/`, + baseURL: kGlobalConstants.API, + method: 'post', + data: params, + cancelToken: source.token + }), + cancel() { + source.cancel(); + } + }; +}; + // Fetch Recipients export const fetchRecipients = (req) => { const source = CancelToken.source(); diff --git a/src/js/models/search/SearchAwardsOperation.js b/src/js/models/search/SearchAwardsOperation.js new file mode 100644 index 0000000000..95308493dd --- /dev/null +++ b/src/js/models/search/SearchAwardsOperation.js @@ -0,0 +1,268 @@ +/** + * SearchAwardsOperation.js + * Created by michaelbray on 8/7/17. + */ + +const rootKeys = { + keyword: 'keyword', + timePeriod: 'time_period', + awardType: 'award_type_codes', + agencies: 'agencies', + recipients: 'legal_entities', + recipientLocationScope: 'recipient_location_scope', + recipientLocation: 'recipient_location', + recipientType: 'recipient_type_names', + placeOfPerformanceScope: 'place_of_performance_scope', + placeOfPerformance: 'place_of_performance', + awardAmount: 'award_amount', + awardID: 'award_ids', + cfda: 'program_numbers', + naics: 'naics_codes', + psc: 'psc_codes', + contractPricing: 'contract_pricing_type_codes', + setAsideType: 'set_aside_type_codes', + extentCompeted: 'extent_competed_type_codes' +}; + +const timePeriodKeys = { + type: 'type', + value: 'value', + startDate: 'start_date', + endDate: 'end_date' +}; + +const agencyKeys = { + type: 'type', + tier: 'tier', + name: 'name' +}; + +const awardAmountKeys = { + min: 'lower_bound', + max: 'upper_bound' +}; + +class SearchAwardsOperation { + constructor() { + this.keyword = ''; + + this.timePeriodType = 'fy'; + this.timePeriodFY = []; + this.timePeriodRange = []; + + this.awardType = []; + + this.awardingAgencies = []; + this.fundingAgencies = []; + + this.selectedRecipients = []; + this.recipientDomesticForeign = 'all'; + this.selectedRecipientLocations = []; + this.recipientType = []; + + this.selectedLocations = []; + this.locationDomesticForeign = 'all'; + + this.awardAmounts = []; + + this.selectedAwardIDs = []; + + this.selectedCFDA = []; + this.selectedNAICS = []; + this.selectedPSC = []; + this.pricingType = []; + this.setAside = []; + this.extentCompeted = []; + } + + fromState(state) { + this.keyword = state.keyword; + + this.timePeriodFY = state.timePeriodFY.toArray(); + this.timePeriodRange = []; + this.timePeriodType = state.timePeriodType; + if (state.timePeriodType === 'dr' && state.timePeriodStart && state.timePeriodEnd) { + this.timePeriodRange = [state.timePeriodStart, state.timePeriodEnd]; + this.timePeriodFY = []; + } + + this.awardType = state.awardType.toArray(); + + this.awardingAgencies = state.selectedAwardingAgencies.toArray(); + this.fundingAgencies = state.selectedFundingAgencies.toArray(); + + this.selectedRecipients = state.selectedRecipients.toArray(); + this.recipientDomesticForeign = state.recipientDomesticForeign; + this.selectedRecipientLocations = state.selectedRecipientLocations.toArray(); + this.recipientType = state.recipientType.toArray(); + + this.selectedLocations = state.selectedLocations.toArray(); + this.locationDomesticForeign = state.locationDomesticForeign; + + this.awardAmounts = state.awardAmounts.toArray(); + + this.selectedAwardIDs = state.selectedAwardIDs.toArray(); + + this.selectedCFDA = state.selectedCFDA.toArray(); + this.selectedNAICS = state.selectedNAICS.toArray(); + this.selectedPSC = state.selectedPSC.toArray(); + + // TODO - Mike Bray - Enable these Other Award Filters when they're available + // this.pricingType = state.pricingType.toArray(); + // this.setAside = state.setAside.toArray(); + // this.extentCompeted = state.extentCompeted.toArray(); + } + + params() { + // Convert the search operation into JS objects + const filters = {}; + + // Add keyword + if (this.keyword !== '') { + filters[rootKeys.keyword] = this.keyword; + } + + // Add Time Period + if (this.timePeriodFY.length > 0 || this.timePeriodRange.length === 2) { + if (this.timePeriodType === 'fy' && this.timePeriodFY.length > 0) { + filters[rootKeys.timePeriod] = { + [timePeriodKeys.type]: this.timePeriodType, + [timePeriodKeys.value]: this.timePeriodFY + }; + } + else if (this.timePeriodType === 'dr' && this.timePeriodRange.length === 2) { + filters[rootKeys.timePeriod] = { + [timePeriodKeys.type]: this.timePeriodType, + [timePeriodKeys.value]: { + [timePeriodKeys.startDate]: this.timePeriodRange[0], + [timePeriodKeys.endDate]: this.timePeriodRange[1] + } + }; + } + } + + // Add award types + if (this.awardType.length > 0) { + filters[rootKeys.awardType] = this.awardType; + } + + // Add Agencies + if (this.fundingAgencies.length > 0 || this.awardingAgencies.length > 0) { + const agencies = []; + + // Funding Agencies are toptier-only + this.fundingAgencies.forEach((agencyArray) => { + agencies.push({ + [agencyKeys.type]: 'funding', + [agencyKeys.tier]: 'toptier', + [agencyKeys.name]: agencyArray.toptier_agency.name + }); + }); + + // Awarding Agencies can be both toptier and subtier + this.awardingAgencies.forEach((agencyArray) => { + const agencyName = agencyArray.agencyType === 'toptier' + ? agencyArray.toptier_agency.name + : agencyArray.subtier_agency.name; + + agencies.push({ + [agencyKeys.type]: 'awarding', + [agencyKeys.tier]: agencyArray.agencyType, + [agencyKeys.name]: agencyName + }); + }); + + filters[rootKeys.agencies] = agencies; + } + + // Add Recipients, Recipient Scope, Recipient Locations, and Recipient Types + if (this.selectedRecipients.length > 0) { + filters[rootKeys.recipients] = this.selectedRecipients.map( + (recipient) => recipient.legal_entity_id); + } + + if (this.recipientDomesticForeign !== '' && this.recipientDomesticForeign !== 'all') { + filters[rootKeys.recipientLocationScope] = this.recipientDomesticForeign; + } + + if (this.selectedRecipientLocations.length > 0) { + filters[rootKeys.recipientLocation] = this.selectedRecipientLocations.map( + (recipient) => recipient.matched_ids); + } + + if (this.recipientType.length > 0) { + filters[rootKeys.recipientType] = this.recipientType; + } + + // Add location queries + if (this.selectedLocations.length > 0) { + filters[rootKeys.placeOfPerformance] = this.selectedLocations.map( + (location) => location.matched_ids); + } + + if (this.locationDomesticForeign !== '' && this.locationDomesticForeign !== 'all') { + filters[rootKeys.placeOfPerformanceScope] = this.locationDomesticForeign; + } + + // Add Award Amount queries + if (this.awardAmounts.length > 0) { + const amounts = []; + + this.awardAmounts.forEach((awardAmount) => { + const amount = {}; + + if (awardAmount[0] !== 0) { + amount[awardAmountKeys.min] = awardAmount[0]; + } + + if (awardAmount[1] !== 0) { + amount[awardAmountKeys.max] = awardAmount[1]; + } + + amounts.push(amount); + }); + + filters[rootKeys.awardAmount] = amounts; + } + + // Add Award ID Queries + if (this.selectedAwardIDs.length > 0) { + filters[rootKeys.awardID] = this.selectedAwardIDs.map( + (awardID) => awardID.id); + } + + // Placeholder for CFDA + if (this.selectedCFDA.length > 0) { + filters[rootKeys.cfda] = this.selectedCFDA.map((cfda) => cfda.program_number); + } + + // Placeholder for NAICS + if (this.selectedNAICS.length > 0) { + filters[rootKeys.naics] = this.selectedNAICS.map((naics) => naics.naics); + } + + // Placeholder for PSC + if (this.selectedPSC.length > 0) { + filters[rootKeys.psc] = this.selectedPSC.map((psc) => psc.product_or_service_code); + } + + // Add Contract Pricing + if (this.pricingType.length > 0) { + filters[rootKeys.contractPricing] = this.pricingType; + } + + // Add Set Aside Type + if (this.setAside.length > 0) { + filters[rootKeys.setAsideType] = this.setAside; + } + + // Add Extent Competed + if (this.extentCompeted.length > 0) { + filters[rootKeys.extentCompeted] = this.extentCompeted; + } + + return filters; + } +} + +export default SearchAwardsOperation; From 3acfca15c05e601b5e28e04d03464960e17172e0 Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Tue, 8 Aug 2017 12:40:23 -0400 Subject: [PATCH 06/40] Comment updates --- src/js/models/search/SearchAwardsOperation.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/js/models/search/SearchAwardsOperation.js b/src/js/models/search/SearchAwardsOperation.js index 95308493dd..c6c52d4c1a 100644 --- a/src/js/models/search/SearchAwardsOperation.js +++ b/src/js/models/search/SearchAwardsOperation.js @@ -194,7 +194,7 @@ class SearchAwardsOperation { filters[rootKeys.recipientType] = this.recipientType; } - // Add location queries + // Add Locations if (this.selectedLocations.length > 0) { filters[rootKeys.placeOfPerformance] = this.selectedLocations.map( (location) => location.matched_ids); @@ -204,7 +204,7 @@ class SearchAwardsOperation { filters[rootKeys.placeOfPerformanceScope] = this.locationDomesticForeign; } - // Add Award Amount queries + // Add Award Amounts if (this.awardAmounts.length > 0) { const amounts = []; @@ -225,23 +225,23 @@ class SearchAwardsOperation { filters[rootKeys.awardAmount] = amounts; } - // Add Award ID Queries + // Add Award IDs if (this.selectedAwardIDs.length > 0) { filters[rootKeys.awardID] = this.selectedAwardIDs.map( (awardID) => awardID.id); } - // Placeholder for CFDA + // Add CFDA if (this.selectedCFDA.length > 0) { filters[rootKeys.cfda] = this.selectedCFDA.map((cfda) => cfda.program_number); } - // Placeholder for NAICS + // Add NAICS if (this.selectedNAICS.length > 0) { filters[rootKeys.naics] = this.selectedNAICS.map((naics) => naics.naics); } - // Placeholder for PSC + // Add PSC if (this.selectedPSC.length > 0) { filters[rootKeys.psc] = this.selectedPSC.map((psc) => psc.product_or_service_code); } From 80895883e9a0774bbc874796478f521e5629fb40 Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Tue, 8 Aug 2017 13:23:53 -0400 Subject: [PATCH 07/40] Updating backend fields --- src/js/models/search/SearchAwardsOperation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/js/models/search/SearchAwardsOperation.js b/src/js/models/search/SearchAwardsOperation.js index c6c52d4c1a..61bfe3dd64 100644 --- a/src/js/models/search/SearchAwardsOperation.js +++ b/src/js/models/search/SearchAwardsOperation.js @@ -9,11 +9,11 @@ const rootKeys = { awardType: 'award_type_codes', agencies: 'agencies', recipients: 'legal_entities', - recipientLocationScope: 'recipient_location_scope', + recipientLocationScope: 'recipient_scope', recipientLocation: 'recipient_location', recipientType: 'recipient_type_names', placeOfPerformanceScope: 'place_of_performance_scope', - placeOfPerformance: 'place_of_performance', + placeOfPerformance: 'place_of_performance_location', awardAmount: 'award_amount', awardID: 'award_ids', cfda: 'program_numbers', From 3cadc48d225f41ef0ddece3f9ad4d7982ad9618b Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Tue, 8 Aug 2017 16:53:59 -0400 Subject: [PATCH 08/40] Show filter if search hash is provided and filter has at least one active selection --- src/js/components/search/SearchPage.jsx | 5 +- src/js/components/search/SearchSidebar.jsx | 80 +++++++++++++++++++- src/js/containers/search/SearchContainer.jsx | 3 +- 3 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/js/components/search/SearchPage.jsx b/src/js/components/search/SearchPage.jsx index 756285d6d2..730c08402e 100644 --- a/src/js/components/search/SearchPage.jsx +++ b/src/js/components/search/SearchPage.jsx @@ -17,7 +17,8 @@ import SearchSidebar from './SearchSidebar'; import SearchResults from './SearchResults'; const propTypes = { - clearAllFilters: PropTypes.func + clearAllFilters: PropTypes.func, + filters: PropTypes.object }; export default class SearchPage extends React.Component { @@ -219,7 +220,7 @@ export default class SearchPage extends React.Component { } render() { - let fullSidebar = (); + let fullSidebar = (); if (this.state.isMobile) { fullSidebar = null; } diff --git a/src/js/components/search/SearchSidebar.jsx b/src/js/components/search/SearchSidebar.jsx index 381d37726f..47ca6eec4e 100644 --- a/src/js/components/search/SearchSidebar.jsx +++ b/src/js/components/search/SearchSidebar.jsx @@ -47,7 +47,8 @@ const filters = { }; const propTypes = { - mobile: PropTypes.bool + mobile: PropTypes.bool, + filters: PropTypes.object }; const defaultProps = { @@ -55,11 +56,82 @@ const defaultProps = { }; export default class SearchSidebar extends React.Component { + constructor(props) { + super(props); + + this.filterHasSelections.bind(this); + } + + filterHasSelections(filter) { + switch (filter) { + case 'Search': + if (this.props.filters.keyword !== '') { + return true; + } + return false; + case 'Time Period': + if (this.props.filters.timePeriodFY.toArray().length > 0 + || (this.props.filters.timePeriodRange + && this.props.filters.timePeriodRange.toArray().length === 2)) { + return true; + } + return false; + case 'Award Type': + if (this.props.filters.awardType.toArray().length > 0) { + return true; + } + return false; + case 'Agencies': + if (this.props.filters.selectedFundingAgencies.toArray().length > 0 + || this.props.filters.selectedAwardingAgencies.toArray().length > 0) { + return true; + } + return false; + case 'Recipients': + if (this.props.filters.selectedRecipients.toArray().length > 0 + || (this.props.filters.recipientDomesticForeign !== '' + && this.props.filters.recipientDomesticForeign !== 'all') + || this.props.filters.selectedRecipientLocations.toArray().length > 0 + || this.props.filters.recipientType.toArray().length > 0) { + return true; + } + return false; + case 'Place of Performance': + if (this.props.filters.selectedLocations.toArray().length > 0 + || (this.props.filters.locationDomesticForeign !== '' + && this.props.filters.locationDomesticForeign !== 'all')) { + return true; + } + return false; + case 'Award Amount': + if (this.props.filters.awardAmounts.toArray().length > 0) { + return true; + } + return false; + case 'Award ID': + if (this.props.filters.selectedAwardIDs.toArray().length > 0) { + return true; + } + return false; + // Todo - Mike Bray - Add remaining Other Award Items and convert to individual + // statements when available + case 'Other Award Items': + if (this.props.filters.selectedCFDA.toArray().length > 0 + || this.props.filters.selectedNAICS.toArray().length > 0 + || this.props.filters.selectedPSC.toArray().length > 0) { + return true; + } + return false; + default: + return false; + } + } + render() { const expanded = []; - filters.options.forEach(() => { - // Collapse all by default - expanded.push(false); + filters.options.forEach((filter) => { + // Collapse all by default, unless the filter has a selection made + expanded.push(this.filterHasSelections(filter)); }); let mobileHeader = null; diff --git a/src/js/containers/search/SearchContainer.jsx b/src/js/containers/search/SearchContainer.jsx index 9203f69699..8e10272840 100644 --- a/src/js/containers/search/SearchContainer.jsx +++ b/src/js/containers/search/SearchContainer.jsx @@ -294,7 +294,8 @@ export class SearchContainer extends React.Component { return ( + clearAllFilters={this.props.clearAllFilters} + filters={this.props.filters} /> ); } } From f6e20805de42f191ac5974ea8da25963caea7952 Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Tue, 8 Aug 2017 16:59:59 -0400 Subject: [PATCH 09/40] Moving keys into their own file --- .../dataMapping/search/awardsOperationKeys.js | 43 +++++++++++++++++++ src/js/models/search/SearchAwardsOperation.js | 40 +---------------- 2 files changed, 45 insertions(+), 38 deletions(-) create mode 100644 src/js/dataMapping/search/awardsOperationKeys.js diff --git a/src/js/dataMapping/search/awardsOperationKeys.js b/src/js/dataMapping/search/awardsOperationKeys.js new file mode 100644 index 0000000000..43f453f1f8 --- /dev/null +++ b/src/js/dataMapping/search/awardsOperationKeys.js @@ -0,0 +1,43 @@ +/** + * awardsOperationKeys.js + * Created by michaelbray on 8/8/17. + */ + +export const rootKeys = { + keyword: 'keyword', + timePeriod: 'time_period', + awardType: 'award_type_codes', + agencies: 'agencies', + recipients: 'legal_entities', + recipientLocationScope: 'recipient_scope', + recipientLocation: 'recipient_location', + recipientType: 'recipient_type_names', + placeOfPerformanceScope: 'place_of_performance_scope', + placeOfPerformance: 'place_of_performance_location', + awardAmount: 'award_amount', + awardID: 'award_ids', + cfda: 'program_numbers', + naics: 'naics_codes', + psc: 'psc_codes', + contractPricing: 'contract_pricing_type_codes', + setAsideType: 'set_aside_type_codes', + extentCompeted: 'extent_competed_type_codes' +}; + +export const timePeriodKeys = { + type: 'type', + value: 'value', + startDate: 'start_date', + endDate: 'end_date' +}; + +export const agencyKeys = { + type: 'type', + tier: 'tier', + name: 'name' +}; + +export const awardAmountKeys = { + min: 'lower_bound', + max: 'upper_bound' +}; diff --git a/src/js/models/search/SearchAwardsOperation.js b/src/js/models/search/SearchAwardsOperation.js index 61bfe3dd64..ab726615ee 100644 --- a/src/js/models/search/SearchAwardsOperation.js +++ b/src/js/models/search/SearchAwardsOperation.js @@ -3,44 +3,8 @@ * Created by michaelbray on 8/7/17. */ -const rootKeys = { - keyword: 'keyword', - timePeriod: 'time_period', - awardType: 'award_type_codes', - agencies: 'agencies', - recipients: 'legal_entities', - recipientLocationScope: 'recipient_scope', - recipientLocation: 'recipient_location', - recipientType: 'recipient_type_names', - placeOfPerformanceScope: 'place_of_performance_scope', - placeOfPerformance: 'place_of_performance_location', - awardAmount: 'award_amount', - awardID: 'award_ids', - cfda: 'program_numbers', - naics: 'naics_codes', - psc: 'psc_codes', - contractPricing: 'contract_pricing_type_codes', - setAsideType: 'set_aside_type_codes', - extentCompeted: 'extent_competed_type_codes' -}; - -const timePeriodKeys = { - type: 'type', - value: 'value', - startDate: 'start_date', - endDate: 'end_date' -}; - -const agencyKeys = { - type: 'type', - tier: 'tier', - name: 'name' -}; - -const awardAmountKeys = { - min: 'lower_bound', - max: 'upper_bound' -}; +import { rootKeys, timePeriodKeys, agencyKeys, awardAmountKeys } + from 'dataMapping/search/awardsOperationKeys'; class SearchAwardsOperation { constructor() { From 27edd156e78af339e2c291e0f51d95f915a11b90 Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Tue, 8 Aug 2017 17:21:26 -0400 Subject: [PATCH 10/40] Reverting TimeVisualizationSectionContainer so others can use this PR without the new backend endpoints being available --- .../TimeVisualizationSectionContainer.jsx | 110 ++++++++++++++++-- 1 file changed, 98 insertions(+), 12 deletions(-) diff --git a/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx b/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx index ff8a1e1b48..a48fcf8f92 100644 --- a/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx +++ b/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx @@ -16,8 +16,10 @@ import * as searchFilterActions from 'redux/actions/search/searchFilterActions'; import * as resultsMetaActions from 'redux/actions/resultsMeta/resultsMetaActions'; import * as SearchHelper from 'helpers/searchHelper'; +import * as BudgetCategoryHelper from 'helpers/budgetCategoryHelper'; -import SearchAwardsOperation from 'models/search/SearchAwardsOperation'; +import SearchTASCategoriesOperation from 'models/search/SearchTASCategoriesOperation'; +import SearchTransactionOperation from 'models/search/SearchTransactionOperation'; const combinedActions = Object.assign({}, searchFilterActions, resultsMetaActions); @@ -34,22 +36,35 @@ export class TimeVisualizationSectionContainer extends React.Component { loading: true, groups: [], xSeries: [], - ySeries: [] + ySeries: [], + budgetFiltersSelected: false, + awardFiltersSelected: false }; this.apiRequest = null; } componentDidMount() { - this.fetchData(); + this.setFilterStates(); } componentDidUpdate(prevProps) { if (!isEqual(prevProps.reduxFilters, this.props.reduxFilters)) { - this.fetchData(); + this.setFilterStates(); } } + setFilterStates() { + this.setState({ + budgetFiltersSelected: + BudgetCategoryHelper.budgetFiltersSelected(this.props.reduxFilters), + awardFiltersSelected: + BudgetCategoryHelper.awardFiltersSelected(this.props.reduxFilters) + }, () => { + this.fetchData(); + }); + } + fetchData() { this.setState({ loading: true @@ -60,20 +75,91 @@ export class TimeVisualizationSectionContainer extends React.Component { this.apiRequest.cancel(); } - // Fetch data from the Awards v2 endpoint - this.fetchAwards('Spending Over Time Visualization'); + // // Fetch data from the appropriate endpoint + if (this.state.awardFiltersSelected && this.state.budgetFiltersSelected) { + this.fetchComboRequest(); + } + else if (this.state.budgetFiltersSelected) { + this.fetchBudgetRequest(); + } + else if (this.state.awardFiltersSelected) { + this.fetchAwardRequest(); + // this.fetchTransactionData(); + } + else { + this.fetchUnfilteredRequest(); + // this.fetchBalanceData(); + } } - fetchAwards(auditTrail = null) { + fetchUnfilteredRequest() { + // no filters have been selected + this.fetchTASCategories('Time visualization - unfiltered'); + } + + fetchBudgetRequest() { + // only budget filters have been applied + this.fetchTASCategories('Time visualization - budget filters'); + } + + fetchAwardRequest() { + // only award filters have been selected + this.fetchTransactions('Time visualization - award filters'); + } + + fetchComboRequest() { + // a combination of budget and award filters have been selected + this.fetchTransactions('Time visualization - combination'); + } + + fetchTransactions(auditTrail = null) { + const field = 'federal_action_obligation'; const group = 'action_date__fy'; - const operation = new SearchAwardsOperation(); + const operation = new SearchTransactionOperation(); + operation.fromState(this.props.reduxFilters); + const searchParams = operation.toParams(); + + // Generate the API parameters + const apiParams = { + field, + group, + order: [group], + aggregate: 'sum', + filters: searchParams + }; + + if (auditTrail) { + apiParams.auditTrail = auditTrail; + } + + this.apiRequest = SearchHelper.performTransactionsTotalSearch(apiParams); + + this.apiRequest.promise + .then((res) => { + this.parseData(res.data, group); + this.apiRequest = null; + }) + .catch(() => { + this.apiRequest = null; + }); + } + + fetchTASCategories(auditTrail = null) { + // only budget filters have been selected + const field = 'obligations_incurred_by_program_object_class_cpe'; + const group = 'submission__reporting_fiscal_year'; + // generate the API parameters + const operation = new SearchTASCategoriesOperation(); operation.fromState(this.props.reduxFilters); - const searchParams = operation.params(); + const searchParams = operation.toParams(); // Generate the API parameters const apiParams = { + field, group, + order: [group], + aggregate: 'sum', filters: searchParams }; @@ -81,7 +167,7 @@ export class TimeVisualizationSectionContainer extends React.Component { apiParams.auditTrail = auditTrail; } - this.apiRequest = SearchHelper.performSpendingOverTimeSearch(apiParams); + this.apiRequest = SearchHelper.performCategorySearch(apiParams); this.apiRequest.promise .then((res) => { @@ -104,9 +190,9 @@ export class TimeVisualizationSectionContainer extends React.Component { data.results.forEach((item) => { groups.push(item[group]); xSeries.push([item[group]]); - ySeries.push([parseFloat(item.aggregated_amount)]); + ySeries.push([parseFloat(item.aggregate)]); - totalSpending += parseFloat(item.aggregated_amount); + totalSpending += parseFloat(item.aggregate); }); this.setState({ From 10f191065bb56796edf1fa3d705494c788c064b0 Mon Sep 17 00:00:00 2001 From: Elizabeth Salita Date: Tue, 8 Aug 2017 17:41:10 -0400 Subject: [PATCH 11/40] Allow user to toggle visibility of additional business types when there are more than 2 --- src/_scss/pages/award/_recipientInfo.scss | 1 + src/js/components/award/RecipientInfo.jsx | 96 ++++++++++++++++++----- 2 files changed, 79 insertions(+), 18 deletions(-) diff --git a/src/_scss/pages/award/_recipientInfo.scss b/src/_scss/pages/award/_recipientInfo.scss index 03c0965abe..1c54bafbb2 100644 --- a/src/_scss/pages/award/_recipientInfo.scss +++ b/src/_scss/pages/award/_recipientInfo.scss @@ -49,6 +49,7 @@ @include span-columns(16); } } + @import './_moreButton'; } @include media($tablet-screen) { @include span-columns(9); diff --git a/src/js/components/award/RecipientInfo.jsx b/src/js/components/award/RecipientInfo.jsx index 771a19d501..635feff2b0 100644 --- a/src/js/components/award/RecipientInfo.jsx +++ b/src/js/components/award/RecipientInfo.jsx @@ -20,8 +20,14 @@ export default class RecipientInfo extends React.Component { constructor(props) { super(props); + + this.state = { + moreTypesButton: true + }; + this.buildSnippets = this.buildSnippets.bind(this); this.buildName = this.buildName.bind(this); + this.toggleButton = this.toggleButton.bind(this); } buildName() { @@ -39,6 +45,12 @@ export default class RecipientInfo extends React.Component { return recipientName; } + toggleButton() { + this.setState({ + moreTypesButton: !this.state.moreTypesButton + }); + } + buildSnippets() { const recipient = this.props.recipient; const isMultiple = toLower(this.props.recipient.recipient_name) === 'multiple recipients'; @@ -48,32 +60,54 @@ export default class RecipientInfo extends React.Component { let duns = "Not Available"; let parentDuns = "Not Available"; - let businessType = "Not Available"; const isContract = includes(awardTypeGroups.contracts, this.props.recipient.award_type); - const allBusinessTypes = BusinessTypesHelper.getBusinessTypes(); - const businessTypesArray = []; - allBusinessTypes.forEach((type) => { - if (recipient.latest_transaction.recipient[type.fieldName] === '1') { - businessTypesArray.push(type.displayName); - } - }); - if (this.props.recipient.recipient_parent_duns) { parentDuns = this.props.recipient.recipient_parent_duns; } if (this.props.recipient.recipient_duns) { duns = this.props.recipient.recipient_duns; } - if (this.props.recipient.recipient_business_type !== 'Unknown Types') { - // Grants, Loans, Direct Payments, and Insurance + + let businessType = "Not Available"; + let businessTypeLabel = "Business Type"; + let overflow = false; + const businessTypesArray = []; + + if (!isContract) { businessType = this.props.recipient.recipient_business_type; } - else if (businessTypesArray.length > 0) { - businessType = ''; - businessTypesArray.forEach((type) => { - businessType += `${type}, `; + else { + businessTypeLabel = "Business Types"; + // Build an array of applicable business type fields + const allBusinessTypes = BusinessTypesHelper.getBusinessTypes(); + allBusinessTypes.forEach((type) => { + if (recipient.latest_transaction.recipient[type.fieldName] === '1') { + businessTypesArray.push(type.displayName); + } }); + + if ((businessTypesArray.length > 0) && (businessTypesArray.length <= 2)) { + // Show all the business types + businessType = businessTypesArray[0]; + businessTypesArray.forEach((type, index) => { + if (index !== 0) { + businessType += `, ${type}`; + } + }); + } + else if (businessTypesArray.length > 2) { + // Show just the first two types until a user clicks the 'See More' button + overflow = true; + businessType = `${businessTypesArray[0]}, ${businessTypesArray[1]}`; + if (!this.state.moreTypesButton) { + businessTypesArray.forEach((type, index) => { + if (index > 1) { + businessType += `, ${type}`; + } + }); + } + } } let parentDunsSnippet = ( @@ -85,6 +119,34 @@ export default class RecipientInfo extends React.Component { parentDunsSnippet = ''; } + let businessTypesSnippet = ( + ); + + if (overflow) { + let button = (); + if (!this.state.moreTypesButton) { + button = (); + } + businessTypesSnippet = ( +
  • +
    +
    + Business Types +
    +
    + {businessType} + {button} +
    +
    +
  • ); + } + let infoSnippets = (
      {parentDunsSnippet} - + {businessTypesSnippet}
    ); if (isMultiple) { From 9a60318c25722897e616342e308743ff167701ba Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Wed, 9 Aug 2017 11:19:56 -0400 Subject: [PATCH 12/40] Allow filter options to expand after initial mount --- .../filterSidebar/FilterOption.jsx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/js/components/sharedComponents/filterSidebar/FilterOption.jsx b/src/js/components/sharedComponents/filterSidebar/FilterOption.jsx index 96787c343f..82d3922db4 100644 --- a/src/js/components/sharedComponents/filterSidebar/FilterOption.jsx +++ b/src/js/components/sharedComponents/filterSidebar/FilterOption.jsx @@ -55,6 +55,23 @@ export default class FilterOption extends React.Component { } } + componentWillUpdate(nextProps) { + if (nextProps.defaultExpand !== this.props.defaultExpand) { + if (nextProps.defaultExpand) { + this.setState({ + showFilter: true, + arrowState: 'expanded' + }); + } + else { + this.setState({ + showFilter: false, + arrowState: 'collapsed' + }); + } + } + } + toggleFilter(e) { e.preventDefault(); From fe86ad1525d648f76a36be62f9132b662ac996ee Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Wed, 9 Aug 2017 11:53:09 -0400 Subject: [PATCH 13/40] Moving switch statement to helper file --- src/js/components/search/SearchSidebar.jsx | 74 +--------------------- src/js/helpers/sidebarHelper.js | 70 ++++++++++++++++++++ 2 files changed, 72 insertions(+), 72 deletions(-) create mode 100644 src/js/helpers/sidebarHelper.js diff --git a/src/js/components/search/SearchSidebar.jsx b/src/js/components/search/SearchSidebar.jsx index 47ca6eec4e..56aa53c451 100644 --- a/src/js/components/search/SearchSidebar.jsx +++ b/src/js/components/search/SearchSidebar.jsx @@ -20,6 +20,7 @@ import OtherFilters from 'components/search/filters/otherFilters/OtherFilters'; import { Filter as FilterIcon } from 'components/sharedComponents/icons/Icons'; import FilterSidebar from 'components/sharedComponents/filterSidebar/FilterSidebar'; import MobileFilterHeader from 'components/search/mobile/MobileFilterHeader'; +import * as SidebarHelper from 'helpers/sidebarHelper'; const filters = { options: [ @@ -56,82 +57,11 @@ const defaultProps = { }; export default class SearchSidebar extends React.Component { - constructor(props) { - super(props); - - this.filterHasSelections.bind(this); - } - - filterHasSelections(filter) { - switch (filter) { - case 'Search': - if (this.props.filters.keyword !== '') { - return true; - } - return false; - case 'Time Period': - if (this.props.filters.timePeriodFY.toArray().length > 0 - || (this.props.filters.timePeriodRange - && this.props.filters.timePeriodRange.toArray().length === 2)) { - return true; - } - return false; - case 'Award Type': - if (this.props.filters.awardType.toArray().length > 0) { - return true; - } - return false; - case 'Agencies': - if (this.props.filters.selectedFundingAgencies.toArray().length > 0 - || this.props.filters.selectedAwardingAgencies.toArray().length > 0) { - return true; - } - return false; - case 'Recipients': - if (this.props.filters.selectedRecipients.toArray().length > 0 - || (this.props.filters.recipientDomesticForeign !== '' - && this.props.filters.recipientDomesticForeign !== 'all') - || this.props.filters.selectedRecipientLocations.toArray().length > 0 - || this.props.filters.recipientType.toArray().length > 0) { - return true; - } - return false; - case 'Place of Performance': - if (this.props.filters.selectedLocations.toArray().length > 0 - || (this.props.filters.locationDomesticForeign !== '' - && this.props.filters.locationDomesticForeign !== 'all')) { - return true; - } - return false; - case 'Award Amount': - if (this.props.filters.awardAmounts.toArray().length > 0) { - return true; - } - return false; - case 'Award ID': - if (this.props.filters.selectedAwardIDs.toArray().length > 0) { - return true; - } - return false; - // Todo - Mike Bray - Add remaining Other Award Items and convert to individual - // statements when available - case 'Other Award Items': - if (this.props.filters.selectedCFDA.toArray().length > 0 - || this.props.filters.selectedNAICS.toArray().length > 0 - || this.props.filters.selectedPSC.toArray().length > 0) { - return true; - } - return false; - default: - return false; - } - } - render() { const expanded = []; filters.options.forEach((filter) => { // Collapse all by default, unless the filter has a selection made - expanded.push(this.filterHasSelections(filter)); + expanded.push(SidebarHelper.filterHasSelections(this.props.filters, filter)); }); let mobileHeader = null; diff --git a/src/js/helpers/sidebarHelper.js b/src/js/helpers/sidebarHelper.js new file mode 100644 index 0000000000..1c2af6fab1 --- /dev/null +++ b/src/js/helpers/sidebarHelper.js @@ -0,0 +1,70 @@ +/** + * Created by michaelbray on 8/9/17. + */ + +/* eslint-disable default-export */ +export const filterHasSelections = (reduxFilters, filter) => { + switch (filter) { + case 'Search': + if (reduxFilters.keyword !== '') { + return true; + } + return false; + case 'Time Period': + if (reduxFilters.timePeriodFY.toArray().length > 0 + || (reduxFilters.timePeriodRange + && reduxFilters.timePeriodRange.toArray().length === 2)) { + return true; + } + return false; + case 'Award Type': + if (reduxFilters.awardType.toArray().length > 0) { + return true; + } + return false; + case 'Agencies': + if (reduxFilters.selectedFundingAgencies.toArray().length > 0 + || reduxFilters.selectedAwardingAgencies.toArray().length > 0) { + return true; + } + return false; + case 'Recipients': + if (reduxFilters.selectedRecipients.toArray().length > 0 + || (reduxFilters.recipientDomesticForeign !== '' + && reduxFilters.recipientDomesticForeign !== 'all') + || reduxFilters.selectedRecipientLocations.toArray().length > 0 + || reduxFilters.recipientType.toArray().length > 0) { + return true; + } + return false; + case 'Place of Performance': + if (reduxFilters.selectedLocations.toArray().length > 0 + || (reduxFilters.locationDomesticForeign !== '' + && reduxFilters.locationDomesticForeign !== 'all')) { + return true; + } + return false; + case 'Award Amount': + if (reduxFilters.awardAmounts.toArray().length > 0) { + return true; + } + return false; + case 'Award ID': + if (reduxFilters.selectedAwardIDs.toArray().length > 0) { + return true; + } + return false; + // Todo - Mike Bray - Add remaining Other Award Items and convert to individual + // statements when available + case 'Other Award Items': + if (reduxFilters.selectedCFDA.toArray().length > 0 + || reduxFilters.selectedNAICS.toArray().length > 0 + || reduxFilters.selectedPSC.toArray().length > 0) { + return true; + } + return false; + default: + return false; + } +}; +/* eslint-enable default-export */ From b3f66478c73856c80c994b37ba81ba022b7defe7 Mon Sep 17 00:00:00 2001 From: Elizabeth Salita Date: Wed, 9 Aug 2017 14:14:14 -0400 Subject: [PATCH 14/40] Added a check for 'Unknown Types' --- src/js/components/award/RecipientInfo.jsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/js/components/award/RecipientInfo.jsx b/src/js/components/award/RecipientInfo.jsx index 635feff2b0..a4fc673eec 100644 --- a/src/js/components/award/RecipientInfo.jsx +++ b/src/js/components/award/RecipientInfo.jsx @@ -74,10 +74,7 @@ export default class RecipientInfo extends React.Component { let overflow = false; const businessTypesArray = []; - if (!isContract) { - businessType = this.props.recipient.recipient_business_type; - } - else { + if (isContract && this.props.recipient.recipient_business_type === 'Unknown Types') { businessTypeLabel = "Business Types"; // Build an array of applicable business type fields const allBusinessTypes = BusinessTypesHelper.getBusinessTypes(); @@ -109,6 +106,9 @@ export default class RecipientInfo extends React.Component { } } } + else { + businessType = this.props.recipient.recipient_business_type; + } let parentDunsSnippet = ( Date: Wed, 9 Aug 2017 14:20:16 -0400 Subject: [PATCH 15/40] Updating Search Operation --- .../dataMapping/search/awardsOperationKeys.js | 8 ++--- src/js/models/search/SearchAwardsOperation.js | 35 ++++++++++++------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/js/dataMapping/search/awardsOperationKeys.js b/src/js/dataMapping/search/awardsOperationKeys.js index 43f453f1f8..a4a082bb1a 100644 --- a/src/js/dataMapping/search/awardsOperationKeys.js +++ b/src/js/dataMapping/search/awardsOperationKeys.js @@ -10,11 +10,11 @@ export const rootKeys = { agencies: 'agencies', recipients: 'legal_entities', recipientLocationScope: 'recipient_scope', - recipientLocation: 'recipient_location', + recipientLocation: 'recipient_locations', recipientType: 'recipient_type_names', placeOfPerformanceScope: 'place_of_performance_scope', - placeOfPerformance: 'place_of_performance_location', - awardAmount: 'award_amount', + placeOfPerformance: 'place_of_performance_locations', + awardAmount: 'award_amounts', awardID: 'award_ids', cfda: 'program_numbers', naics: 'naics_codes', @@ -25,8 +25,6 @@ export const rootKeys = { }; export const timePeriodKeys = { - type: 'type', - value: 'value', startDate: 'start_date', endDate: 'end_date' }; diff --git a/src/js/models/search/SearchAwardsOperation.js b/src/js/models/search/SearchAwardsOperation.js index ab726615ee..51e4cadf51 100644 --- a/src/js/models/search/SearchAwardsOperation.js +++ b/src/js/models/search/SearchAwardsOperation.js @@ -3,6 +3,8 @@ * Created by michaelbray on 8/7/17. */ +import { concat } from 'lodash'; + import { rootKeys, timePeriodKeys, agencyKeys, awardAmountKeys } from 'dataMapping/search/awardsOperationKeys'; @@ -89,18 +91,17 @@ class SearchAwardsOperation { // Add Time Period if (this.timePeriodFY.length > 0 || this.timePeriodRange.length === 2) { if (this.timePeriodType === 'fy' && this.timePeriodFY.length > 0) { - filters[rootKeys.timePeriod] = { - [timePeriodKeys.type]: this.timePeriodType, - [timePeriodKeys.value]: this.timePeriodFY - }; + filters[rootKeys.timePeriod] = this.timePeriodFY.map((fy) => + ({ + [timePeriodKeys.startDate]: `${fy - 1}-10-01`, + [timePeriodKeys.endDate]: `${fy}-09-30` + }) + ); } else if (this.timePeriodType === 'dr' && this.timePeriodRange.length === 2) { filters[rootKeys.timePeriod] = { - [timePeriodKeys.type]: this.timePeriodType, - [timePeriodKeys.value]: { - [timePeriodKeys.startDate]: this.timePeriodRange[0], - [timePeriodKeys.endDate]: this.timePeriodRange[1] - } + [timePeriodKeys.startDate]: this.timePeriodRange[0], + [timePeriodKeys.endDate]: this.timePeriodRange[1] }; } } @@ -150,8 +151,12 @@ class SearchAwardsOperation { } if (this.selectedRecipientLocations.length > 0) { - filters[rootKeys.recipientLocation] = this.selectedRecipientLocations.map( - (recipient) => recipient.matched_ids); + let locationSet = []; + this.selectedRecipientLocations.forEach((location) => { + locationSet = concat(locationSet, location.matched_ids); + }); + + filters[rootKeys.recipientLocation] = locationSet; } if (this.recipientType.length > 0) { @@ -160,8 +165,12 @@ class SearchAwardsOperation { // Add Locations if (this.selectedLocations.length > 0) { - filters[rootKeys.placeOfPerformance] = this.selectedLocations.map( - (location) => location.matched_ids); + let locationSet = []; + this.selectedLocations.forEach((location) => { + locationSet = concat(locationSet, location.matched_ids); + }); + + filters[rootKeys.placeOfPerformance] = locationSet; } if (this.locationDomesticForeign !== '' && this.locationDomesticForeign !== 'all') { From c260fb82910bd2c8ca2bac1a386054c9c63d0c3e Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Wed, 9 Aug 2017 22:38:33 -0400 Subject: [PATCH 16/40] Add federal account column to award detail pages --- .../award/table/FinancialSystemTable.jsx | 8 +++- .../award/table/cells/FinSysAccountCell.jsx | 48 +++++++++++++++++++ .../dataMapping/contracts/financialSystem.js | 8 +++- .../results/other/FinancialSystemItem.js | 11 +++++ 4 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 src/js/components/award/table/cells/FinSysAccountCell.jsx diff --git a/src/js/components/award/table/FinancialSystemTable.jsx b/src/js/components/award/table/FinancialSystemTable.jsx index f55d614f97..b7fe2c2071 100644 --- a/src/js/components/award/table/FinancialSystemTable.jsx +++ b/src/js/components/award/table/FinancialSystemTable.jsx @@ -14,6 +14,7 @@ import tableMapping from 'dataMapping/contracts/financialSystem'; import FinSysHeaderCellContainer from 'containers/award/table/cells/FinSysHeaderCellContainer'; import FinSysGenericCell from './cells/FinSysGenericCell'; +import FinSysAccountCell from './cells/FinSysAccountCell'; import SummaryPageTableMessage from './SummaryPageTableMessage'; const rowHeight = 40; @@ -94,6 +95,11 @@ export default class FinancialSystemTable extends React.Component { const apiKey = tableMapping.table._mapping[column]; const defaultSort = tableMapping.defaultSortDirection[column]; + let CellComponent = FinSysGenericCell; + if (column === 'fedAccount') { + CellComponent = FinSysAccountCell; + } + return { width: columnWidth, name: column, @@ -106,7 +112,7 @@ export default class FinancialSystemTable extends React.Component { isLastColumn={isLast} />), cell: (index) => { const item = this.props.award.finSysData[index]; - return ( + {this.props.data.title} +
    ); + if (!content) { + content = "\u00A0"; + } + + // calculate even-odd class names + let rowClass = 'row-even'; + if (this.props.rowIndex % 2 === 0) { + // row index is zero-based + rowClass = 'row-odd'; + } + + if (this.props.isLastColumn) { + rowClass += ' last-column'; + } + + return ( +
    +
    + {content} +
    +
    + ); + } +} + +FinSysAccountCell.propTypes = propTypes; diff --git a/src/js/dataMapping/contracts/financialSystem.js b/src/js/dataMapping/contracts/financialSystem.js index 308e8c1c37..c27fbab036 100644 --- a/src/js/dataMapping/contracts/financialSystem.js +++ b/src/js/dataMapping/contracts/financialSystem.js @@ -6,6 +6,7 @@ const tableFields = { columnWidths: { submissionDate: 0, + fedAccount: 0, tas: 0, programActivity: 300, objectClass: 300, @@ -13,6 +14,7 @@ const tableFields = { }, defaultSortDirection: { submissionDate: 'desc', + fedAccount: 'asc', tas: 'desc', programActivity: 'asc', objectClass: 'asc', @@ -29,6 +31,7 @@ const tableFields = { ], _order: [ 'submissionDate', + 'fedAccount', 'tas', 'programActivity', 'objectClass', @@ -36,6 +39,7 @@ const tableFields = { ], _sortFields: { submissionDate: 'certified_date', + fedAccount: 'treasury_account__federal_account__account_title', tas: 'treasury_account__tas_rendering_label', programActivity: 'program_activity__program_activity_name', objectClass: 'object_class__object_class_name', @@ -43,13 +47,15 @@ const tableFields = { }, _mapping: { submissionDate: 'submissionDate', + fedAccount: 'fedAccount', tas: 'tas', programActivity: 'programActivity', objectClass: 'objectClass', fundingObligated: 'fundingObligated' }, submissionDate: 'Submission Date', - tas: 'TAS', + fedAccount: 'Federal Account Name', + tas: 'Treasury Account Symbol', programActivity: 'Program Activity', objectClass: 'Object Class', fundingObligated: 'Funding Obligated' diff --git a/src/js/models/results/other/FinancialSystemItem.js b/src/js/models/results/other/FinancialSystemItem.js index b248cabc8a..4c9c63f6e4 100644 --- a/src/js/models/results/other/FinancialSystemItem.js +++ b/src/js/models/results/other/FinancialSystemItem.js @@ -13,6 +13,7 @@ const recordType = 'finsys'; const fields = [ 'id', 'submissionDate', + 'fedAccount', 'tas', 'objectClass', 'programActivity', @@ -26,6 +27,10 @@ const remapData = (data) => { const remappedData = data; remappedData.submissionDate = ''; + remappedData.fedAccount = { + title: '', + id: 0 + }; remappedData.tas = ''; remappedData.objectClass = ''; remappedData.programActivity = ''; @@ -43,6 +48,12 @@ const remapData = (data) => { const tAccount = data.treasury_account; if (tAccount.tas_rendering_label) { remappedData.tas = tAccount.tas_rendering_label; + if (tAccount.federal_account) { + remappedData.fedAccount = { + title: tAccount.federal_account.account_title, + id: tAccount.federal_account.id + }; + } } if (tAccount.budget_function_title && tAccount.budget_function_code) { From 02745e3f98f06e45c6dadfccc96f3e240ed5c2a4 Mon Sep 17 00:00:00 2001 From: Michael Bray Date: Thu, 10 Aug 2017 10:26:21 -0400 Subject: [PATCH 17/40] Used FiscalYearHelper for FY query, future-proofed agency name, additional checks in Award Amounts, added comments to Award Amounts, renamed `params` to `toParams` for consistency --- src/js/models/search/SearchAwardsOperation.js | 49 +++++++++++++------ 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/src/js/models/search/SearchAwardsOperation.js b/src/js/models/search/SearchAwardsOperation.js index 51e4cadf51..70c4141f76 100644 --- a/src/js/models/search/SearchAwardsOperation.js +++ b/src/js/models/search/SearchAwardsOperation.js @@ -4,9 +4,9 @@ */ import { concat } from 'lodash'; - import { rootKeys, timePeriodKeys, agencyKeys, awardAmountKeys } from 'dataMapping/search/awardsOperationKeys'; +import * as FiscalYearHelper from 'helpers/fiscalYearHelper'; class SearchAwardsOperation { constructor() { @@ -79,7 +79,7 @@ class SearchAwardsOperation { // this.extentCompeted = state.extentCompeted.toArray(); } - params() { + toParams() { // Convert the search operation into JS objects const filters = {}; @@ -91,12 +91,14 @@ class SearchAwardsOperation { // Add Time Period if (this.timePeriodFY.length > 0 || this.timePeriodRange.length === 2) { if (this.timePeriodType === 'fy' && this.timePeriodFY.length > 0) { - filters[rootKeys.timePeriod] = this.timePeriodFY.map((fy) => - ({ - [timePeriodKeys.startDate]: `${fy - 1}-10-01`, - [timePeriodKeys.endDate]: `${fy}-09-30` - }) - ); + filters[rootKeys.timePeriod] = this.timePeriodFY.map((fy) => { + const dates = FiscalYearHelper.convertFYToDateRange(fy); + + return { + [timePeriodKeys.startDate]: dates[0], + [timePeriodKeys.endDate]: dates[1] + }; + }); } else if (this.timePeriodType === 'dr' && this.timePeriodRange.length === 2) { filters[rootKeys.timePeriod] = { @@ -126,9 +128,7 @@ class SearchAwardsOperation { // Awarding Agencies can be both toptier and subtier this.awardingAgencies.forEach((agencyArray) => { - const agencyName = agencyArray.agencyType === 'toptier' - ? agencyArray.toptier_agency.name - : agencyArray.subtier_agency.name; + const agencyName = agencyArray[`${agencyArray.agencyType}_agency`].name; agencies.push({ [agencyKeys.type]: 'awarding', @@ -181,21 +181,40 @@ class SearchAwardsOperation { if (this.awardAmounts.length > 0) { const amounts = []; + // The backend expects an object with a lower bound, an upper bound, or both. + // In cases of "$x - $y", we include both a lower and upper bound. + // In cases of "$x & Above", we don't include an upper bound. + // In cases of "Under $x", we don't include a lower bound. this.awardAmounts.forEach((awardAmount) => { const amount = {}; - if (awardAmount[0] !== 0) { + // Don't include the min if it's negative + if (awardAmount[0] > 0) { amount[awardAmountKeys.min] = awardAmount[0]; } - if (awardAmount[1] !== 0) { + // Don't include the max if it's negative + if (awardAmount[1] > 0) { amount[awardAmountKeys.max] = awardAmount[1]; } - amounts.push(amount); + // Remove the max element if the min element is larger + if (awardAmount[0] !== 0 && awardAmount[1] !== 0 && + awardAmount[0] > awardAmount[1]) { + delete amount[awardAmountKeys.max]; + } + + // Only include a range if at least one of the bounds is defined + if (amount[awardAmountKeys.min] || amount[awardAmountKeys.max]) { + amounts.push(amount); + } }); - filters[rootKeys.awardAmount] = amounts; + // Only push the array to the filters element if at least + // one award amount object is defined + if (amounts.length > 0) { + filters[rootKeys.awardAmount] = amounts; + } } // Add Award IDs From 6c9fba0195569fc1b868d691cab0d9522062ccd1 Mon Sep 17 00:00:00 2001 From: Elizabeth Salita Date: Thu, 10 Aug 2017 11:59:05 -0400 Subject: [PATCH 18/40] Display business types in a list --- src/_scss/pages/award/_recipientInfo.scss | 19 ++++++++++++-- src/js/components/award/RecipientInfo.jsx | 30 +++++++++++------------ src/js/helpers/businessTypesHelper.js | 6 ++--- 3 files changed, 35 insertions(+), 20 deletions(-) diff --git a/src/_scss/pages/award/_recipientInfo.scss b/src/_scss/pages/award/_recipientInfo.scss index 1c54bafbb2..9ae2d58c34 100644 --- a/src/_scss/pages/award/_recipientInfo.scss +++ b/src/_scss/pages/award/_recipientInfo.scss @@ -26,9 +26,9 @@ @include span-columns(16); } @include media($medium-screen) { - @include span-columns(3.7); + @include span-columns(5.1); &:first-child { - @include span-columns(6); + @include span-columns(4.6); } &:nth-of-type(2), &:nth-of-type(3) { @@ -42,6 +42,17 @@ } .item-value { @include setText($small-font-size); + ul { + display: initial; + list-style-type: none; + padding: 0; + li { + display: list-item; + width: 100%; + margin: 0 0 rem(5) 0; + line-height: rem(18); + } + } } } &.single { @@ -50,6 +61,10 @@ } } @import './_moreButton'; + button.see-more { + display: block; + margin-left: 0; + } } @include media($tablet-screen) { @include span-columns(9); diff --git a/src/js/components/award/RecipientInfo.jsx b/src/js/components/award/RecipientInfo.jsx index a4fc673eec..259a30c553 100644 --- a/src/js/components/award/RecipientInfo.jsx +++ b/src/js/components/award/RecipientInfo.jsx @@ -73,6 +73,7 @@ export default class RecipientInfo extends React.Component { let businessTypeLabel = "Business Type"; let overflow = false; const businessTypesArray = []; + let typesList = ''; if (isContract && this.props.recipient.recipient_business_type === 'Unknown Types') { businessTypeLabel = "Business Types"; @@ -80,29 +81,26 @@ export default class RecipientInfo extends React.Component { const allBusinessTypes = BusinessTypesHelper.getBusinessTypes(); allBusinessTypes.forEach((type) => { if (recipient.latest_transaction.recipient[type.fieldName] === '1') { - businessTypesArray.push(type.displayName); + businessTypesArray.push(type); } }); if ((businessTypesArray.length > 0) && (businessTypesArray.length <= 2)) { // Show all the business types - businessType = businessTypesArray[0]; - businessTypesArray.forEach((type, index) => { - if (index !== 0) { - businessType += `, ${type}`; - } - }); + typesList = businessTypesArray.map((type) =>
  • {type.displayName}
  • ); } else if (businessTypesArray.length > 2) { // Show just the first two types until a user clicks the 'See More' button overflow = true; - businessType = `${businessTypesArray[0]}, ${businessTypesArray[1]}`; - if (!this.state.moreTypesButton) { - businessTypesArray.forEach((type, index) => { - if (index > 1) { - businessType += `, ${type}`; - } - }); + if (this.state.moreTypesButton) { + typesList = [businessTypesArray[0], businessTypesArray[1]].map((type) => +
  • {type.displayName}
  • + ); + } + else { + typesList = businessTypesArray.map((type) => +
  • {type.displayName}
  • + ); } } } @@ -140,7 +138,9 @@ export default class RecipientInfo extends React.Component { Business Types
    - {businessType} +
      + {typesList} +
    {button}
    diff --git a/src/js/helpers/businessTypesHelper.js b/src/js/helpers/businessTypesHelper.js index 1fd98a3912..471bb36720 100644 --- a/src/js/helpers/businessTypesHelper.js +++ b/src/js/helpers/businessTypesHelper.js @@ -35,7 +35,7 @@ export const getBusinessTypes = () => { fieldName: 'service_disabled_veteran_owned_business' }, { - displayName: 'Woman Owned business', + displayName: 'Woman Owned Business', fieldName: 'woman_owned_business' }, { @@ -63,7 +63,7 @@ export const getBusinessTypes = () => { fieldName: 'subcontinent_asian_asian_indian_american_owned_business' }, { - displayName: 'Asian Pacific American Owned business', + displayName: 'Asian Pacific American Owned Business', fieldName: 'asian_pacific_american_owned_business' }, { @@ -83,7 +83,7 @@ export const getBusinessTypes = () => { fieldName: 'other_minority_owned_business' }, { - displayName: 'Emerging Small business', + displayName: 'Emerging Small Business', fieldName: 'emerging_small_business' }, { From 9201241b43a1f9c834f11c2136f4d8945075a168 Mon Sep 17 00:00:00 2001 From: Kevin Li Date: Thu, 10 Aug 2017 14:27:41 -0400 Subject: [PATCH 19/40] Update tests, add last updated date --- src/_scss/pages/search/searchPage.scss | 8 ++ src/js/components/search/SearchPage.jsx | 6 +- src/js/components/search/SearchResults.jsx | 12 ++- src/js/containers/search/SearchContainer.jsx | 35 ++++++- src/js/helpers/searchHelper.js | 15 +++ .../search/SearchContainer-test.jsx | 95 +++---------------- .../containers/search/filters/searchHelper.js | 43 +++++++++ 7 files changed, 127 insertions(+), 87 deletions(-) diff --git a/src/_scss/pages/search/searchPage.scss b/src/_scss/pages/search/searchPage.scss index e895781da1..c98df26f56 100644 --- a/src/_scss/pages/search/searchPage.scss +++ b/src/_scss/pages/search/searchPage.scss @@ -23,5 +23,13 @@ margin-bottom: rem(60); @import "./results/searchResults"; @import "./topFilterBar/topFilterBar"; + + .last-update { + font-size: rem(12); + margin-bottom: rem(5); + strong { + font-weight: $font-bold; + } + } } } \ No newline at end of file diff --git a/src/js/components/search/SearchPage.jsx b/src/js/components/search/SearchPage.jsx index 730c08402e..e0d2cccb44 100644 --- a/src/js/components/search/SearchPage.jsx +++ b/src/js/components/search/SearchPage.jsx @@ -18,7 +18,8 @@ import SearchResults from './SearchResults'; const propTypes = { clearAllFilters: PropTypes.func, - filters: PropTypes.object + filters: PropTypes.object, + lastUpdate: PropTypes.string }; export default class SearchPage extends React.Component { @@ -255,7 +256,8 @@ export default class SearchPage extends React.Component { showMobileFilters={this.state.showMobileFilters} updateFilterCount={this.updateFilterCount} toggleMobileFilters={this.toggleMobileFilters} - clearAllFilters={this.props.clearAllFilters} /> + clearAllFilters={this.props.clearAllFilters} + lastUpdate={this.props.lastUpdate} />