diff --git a/src/js/components/agency/visualizations/recipient/RecipientVisualization.jsx b/src/js/components/agency/visualizations/recipient/RecipientVisualization.jsx
index 75acc0c13e..dffbf8fe43 100644
--- a/src/js/components/agency/visualizations/recipient/RecipientVisualization.jsx
+++ b/src/js/components/agency/visualizations/recipient/RecipientVisualization.jsx
@@ -22,7 +22,8 @@ const propTypes = {
descriptions: PropTypes.array,
page: PropTypes.number,
isLastPage: PropTypes.bool,
- changePage: PropTypes.func
+ changePage: PropTypes.func,
+ lastUpdate: PropTypes.string
};
@@ -99,6 +100,7 @@ or foreign). Here is a look at who these recipients are and how they rank by awa
Recipients
+
Data as of {this.props.lastUpdate}
{
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}
diff --git a/src/js/components/award/RecipientInfo.jsx b/src/js/components/award/RecipientInfo.jsx
index d5a6033e1f..e1d3a845c0 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';
@@ -19,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() {
@@ -38,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';
@@ -47,7 +60,6 @@ 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);
if (this.props.recipient.recipient_parent_duns) {
@@ -56,9 +68,46 @@ export default class RecipientInfo extends React.Component {
if (this.props.recipient.recipient_duns) {
duns = this.props.recipient.recipient_duns;
}
- if (this.props.recipient.recipient_business_type) {
+
+ let businessType = "Not Available";
+ let businessTypeLabel = "Business Type";
+ let overflow = false;
+ const businessTypesArray = [];
+ let typesList = '';
+
+ 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();
+ allBusinessTypes.forEach((type) => {
+ if (recipient.latest_transaction.recipient[type.fieldName] === '1') {
+ businessTypesArray.push(type);
+ }
+ });
+
+ if ((businessTypesArray.length > 0) && (businessTypesArray.length <= 2)) {
+ // Show all the business types
+ 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;
+ if (this.state.moreTypesButton) {
+ typesList = [businessTypesArray[0], businessTypesArray[1]].map((type) =>
+ {type.displayName}
+ );
+ }
+ else {
+ typesList = businessTypesArray.map((type) =>
+ {type.displayName}
+ );
+ }
+ }
+ }
+ else {
businessType = this.props.recipient.recipient_business_type;
}
+
let parentDunsSnippet = (
);
+
+ if (overflow) {
+ let button = ({`See ${businessTypesArray.length - 2} more`} );
+ if (!this.state.moreTypesButton) {
+ button = ({`See fewer`} );
+ }
+ businessTypesSnippet = (
+
+
+ );
+ }
+
let infoSnippets = (
{parentDunsSnippet}
-
+ {businessTypesSnippet}
);
if (isMultiple) {
diff --git a/src/js/components/award/subawards/SubawardsTable.jsx b/src/js/components/award/subawards/SubawardsTable.jsx
index 085de1807f..c50a33039f 100644
--- a/src/js/components/award/subawards/SubawardsTable.jsx
+++ b/src/js/components/award/subawards/SubawardsTable.jsx
@@ -142,7 +142,7 @@ export default class SubawardsTable extends React.Component {
}
let message = null;
- if (this.props.subawards.length === 0) {
+ if (this.props.subawards.length === 0 && !this.props.inFlight) {
message = ( );
}
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 (
+
+ );
+ }
+}
+
+FinSysAccountCell.propTypes = propTypes;
diff --git a/src/js/components/errorPage/ErrorPage.jsx b/src/js/components/errorPage/ErrorPage.jsx
new file mode 100644
index 0000000000..b3de0aad0f
--- /dev/null
+++ b/src/js/components/errorPage/ErrorPage.jsx
@@ -0,0 +1,44 @@
+/**
+ * ErrorPage.jsx
+ * Created by Kevin Li 8/10/17
+ */
+
+import React from 'react';
+
+import { ExclamationCircle } from 'components/sharedComponents/icons/Icons';
+
+import * as MetaTagHelper from 'helpers/metaTagHelper';
+
+import MetaTags from '../sharedComponents/metaTags/MetaTags';
+import Header from '../sharedComponents/header/Header';
+import Footer from '../sharedComponents/Footer';
+
+const ErrorPage = () => (
+
+
+
+
+
+
+
+
+
+
+
+
Page not found
+
+
+
+
+
+
+
+
+);
+
+export default ErrorPage;
diff --git a/src/js/components/search/SearchPage.jsx b/src/js/components/search/SearchPage.jsx
index 756285d6d2..e0d2cccb44 100644
--- a/src/js/components/search/SearchPage.jsx
+++ b/src/js/components/search/SearchPage.jsx
@@ -17,7 +17,9 @@ import SearchSidebar from './SearchSidebar';
import SearchResults from './SearchResults';
const propTypes = {
- clearAllFilters: PropTypes.func
+ clearAllFilters: PropTypes.func,
+ filters: PropTypes.object,
+ lastUpdate: PropTypes.string
};
export default class SearchPage extends React.Component {
@@ -219,7 +221,7 @@ export default class SearchPage extends React.Component {
}
render() {
- let fullSidebar = (
);
+ let fullSidebar = (
);
if (this.state.isMobile) {
fullSidebar = null;
}
@@ -254,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} />
diff --git a/src/js/components/search/SearchResults.jsx b/src/js/components/search/SearchResults.jsx
index 426179da25..3ed0b29943 100644
--- a/src/js/components/search/SearchResults.jsx
+++ b/src/js/components/search/SearchResults.jsx
@@ -25,7 +25,8 @@ const propTypes = {
filterCount: PropTypes.number,
showMobileFilters: PropTypes.bool,
toggleMobileFilters: PropTypes.func,
- clearAllFilters: PropTypes.func
+ clearAllFilters: PropTypes.func,
+ lastUpdate: PropTypes.string
};
export default class SearchResults extends React.Component {
@@ -45,6 +46,14 @@ export default class SearchResults extends React.Component {
showCountBadge = 'hide';
}
+ let lastUpdate = null;
+ if (this.props.lastUpdate !== '') {
+ lastUpdate = (
+ Note: All data shown is as of {this.props.lastUpdate}
+
);
+ }
+
+
return (
@@ -83,6 +92,7 @@ export default class SearchResults extends React.Component {
showMobileFilters={this.props.showMobileFilters}
toggleMobileFilters={this.props.toggleMobileFilters} />
+ {lastUpdate}
diff --git a/src/js/components/search/SearchSidebar.jsx b/src/js/components/search/SearchSidebar.jsx
index 7a5cd6f99e..b84e2d3881 100644
--- a/src/js/components/search/SearchSidebar.jsx
+++ b/src/js/components/search/SearchSidebar.jsx
@@ -22,36 +22,38 @@ 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: [
'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
]
};
const propTypes = {
- mobile: PropTypes.bool
+ mobile: PropTypes.bool,
+ filters: PropTypes.object
};
const defaultProps = {
@@ -61,14 +63,9 @@ const defaultProps = {
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);
- }
+ filters.options.forEach((filter) => {
+ // Collapse all by default, unless the filter has a selection made
+ expanded.push(SidebarHelper.filterHasSelections(this.props.filters, filter));
});
let mobileHeader = null;
diff --git a/src/js/components/search/filters/agency/SelectedAgencies.jsx b/src/js/components/search/filters/agency/SelectedAgencies.jsx
index b8857843b5..c6e479c220 100644
--- a/src/js/components/search/filters/agency/SelectedAgencies.jsx
+++ b/src/js/components/search/filters/agency/SelectedAgencies.jsx
@@ -22,9 +22,19 @@ export default class SelectedAgencies extends React.Component {
const agency = entry[1];
let label = agency.subtier_agency.name;
- if (agency.agencyType === 'subtier' &&
- agency.toptier_agency.name === agency.subtier_agency.name) {
- label += ' | Sub-Agency';
+ if (agency.agencyType !== '' && agency.agencyType !== null) {
+ if (agency.agencyType === 'subtier' && agency.subtier_agency.abbreviation !== '') {
+ label += ` (${agency.subtier_agency.abbreviation})`;
+ }
+ else if (agency.agencyType === 'toptier' &&
+ agency.toptier_agency.abbreviation !== '') {
+ label += ` (${agency.toptier_agency.abbreviation})`;
+ }
+
+ if (agency.agencyType === 'subtier' &&
+ agency.toptier_agency.name === agency.subtier_agency.name) {
+ label += ' | Sub-Agency';
+ }
}
const value = (
{
let agencyTitle = value.subtier_agency.name;
+
+ if (value.agencyType === 'subtier' && value.subtier_agency.abbreviation !== '') {
+ agencyTitle += ` (${value.subtier_agency.abbreviation})`;
+ }
+ else if (value.agencyType === 'toptier' && value.toptier_agency.abbreviation !== '') {
+ agencyTitle += ` (${value.toptier_agency.abbreviation})`;
+ }
if (value.agencyType === 'subtier' &&
value.toptier_agency.name === value.subtier_agency.name) {
agencyTitle += ' | Sub-Agency';
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();
diff --git a/src/js/containers/agency/AgencyContainer.jsx b/src/js/containers/agency/AgencyContainer.jsx
index dfe563be34..e2178c5c56 100644
--- a/src/js/containers/agency/AgencyContainer.jsx
+++ b/src/js/containers/agency/AgencyContainer.jsx
@@ -8,9 +8,11 @@ import PropTypes from 'prop-types';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { isCancel } from 'axios';
+import moment from 'moment';
import AgencyOverviewModel from 'models/agency/AgencyOverviewModel';
import * as AgencyHelper from 'helpers/agencyHelper';
+import * as SearchHelper from 'helpers/searchHelper';
import * as agencyActions from 'redux/actions/agency/agencyActions';
import AgencyPage from 'components/agency/AgencyPage';
@@ -27,13 +29,16 @@ export class AgencyContainer extends React.Component {
this.state = {
loading: true,
- error: false
+ error: false,
+ lastUpdate: ''
};
this.request = null;
+ this.updateRequest = null;
}
componentWillMount() {
this.loadAgencyOverview(this.props.params.agencyId);
+ this.loadUpdateDate();
}
componentWillReceiveProps(nextProps) {
@@ -81,13 +86,39 @@ export class AgencyContainer extends React.Component {
this.props.setAgencyOverview(agency);
}
+ loadUpdateDate() {
+ if (this.updateRequest) {
+ this.updateRequest.cancel();
+ }
+
+ this.updateRequest = SearchHelper.fetchLastUpdate();
+ this.updateRequest.promise
+ .then((res) => {
+ this.parseUpdateDate(res.data.last_updated);
+ })
+ .catch((err) => {
+ if (!isCancel(err)) {
+ console.log(err);
+ this.updateRequest = null;
+ }
+ });
+ }
+
+ parseUpdateDate(value) {
+ const date = moment(value, 'MM/DD/YYYY');
+ this.setState({
+ lastUpdate: date.format('MMMM D, YYYY')
+ });
+ }
+
render() {
return (
+ agency={this.props.agency}
+ lastUpdate={this.state.lastUpdate} />
);
}
}
diff --git a/src/js/containers/agency/visualizations/FederalAccountContainer.jsx b/src/js/containers/agency/visualizations/FederalAccountContainer.jsx
index e32e80deaf..52ae65a41e 100644
--- a/src/js/containers/agency/visualizations/FederalAccountContainer.jsx
+++ b/src/js/containers/agency/visualizations/FederalAccountContainer.jsx
@@ -17,7 +17,8 @@ import FederalAccountVisualization from
const propTypes = {
id: PropTypes.string,
activeFY: PropTypes.string,
- obligatedAmount: PropTypes.number
+ obligatedAmount: PropTypes.number,
+ asOfDate: PropTypes.string
};
export default class FederalAccountContainer extends React.PureComponent {
@@ -132,7 +133,8 @@ ${account}`;
labelSeries={this.state.labelSeries}
descriptions={this.state.descriptions}
loading={this.state.loading}
- error={this.state.error} />
+ error={this.state.error}
+ asOfDate={this.props.asOfDate} />
);
}
}
diff --git a/src/js/containers/agency/visualizations/ObjectClassContainer.jsx b/src/js/containers/agency/visualizations/ObjectClassContainer.jsx
index accadf2f00..fcaa2c2c05 100644
--- a/src/js/containers/agency/visualizations/ObjectClassContainer.jsx
+++ b/src/js/containers/agency/visualizations/ObjectClassContainer.jsx
@@ -15,7 +15,8 @@ import ObjectClassTreeMap from 'components/agency/visualizations/objectClass/Obj
const propTypes = {
id: PropTypes.string,
- activeFY: PropTypes.string
+ activeFY: PropTypes.string,
+ asOfDate: PropTypes.string
};
const defaultProps = {
@@ -202,7 +203,8 @@ export default class ObjectClassContainer extends React.PureComponent {
minorObjectClasses={this.state.minorObjectClasses}
totalObligation={this.state.totalObligation}
totalMinorObligation={this.state.totalMinorObligation}
- showMinorObjectClasses={this.showMinorObjectClasses} />
+ showMinorObjectClasses={this.showMinorObjectClasses}
+ asOfDate={this.props.asOfDate} />
);
}
diff --git a/src/js/containers/agency/visualizations/ObligatedContainer.jsx b/src/js/containers/agency/visualizations/ObligatedContainer.jsx
index 14b833a240..25a28c3e4b 100644
--- a/src/js/containers/agency/visualizations/ObligatedContainer.jsx
+++ b/src/js/containers/agency/visualizations/ObligatedContainer.jsx
@@ -16,7 +16,8 @@ import ObligatedVisualization from
const propTypes = {
id: PropTypes.string,
activeFY: PropTypes.string,
- agencyName: PropTypes.string
+ agencyName: PropTypes.string,
+ asOfDate: PropTypes.string
};
export class ObligatedContainer extends React.PureComponent {
@@ -26,14 +27,10 @@ export class ObligatedContainer extends React.PureComponent {
this.state = {
inFlight: true,
obligatedAmount: 0,
- budgetAuthority: 0,
- fiscalQuarter: 0,
- cgacCode: ""
+ budgetAuthority: 0
};
this.loadData = this.loadData.bind(this);
- this.setCgacCode = this.setCgacCode.bind(this);
- this.setFiscalQuarter = this.setFiscalQuarter.bind(this);
}
componentWillMount() {
@@ -46,30 +43,6 @@ export class ObligatedContainer extends React.PureComponent {
}
}
- setCgacCode(id) {
- if (this.cgacRequest) {
- this.cgacRequest.cancel();
- }
-
- this.cgacRequest = AgencyHelper.fetchAgencyCgacCode({
- id
- });
-
- return this.cgacRequest.promise;
- }
-
- setFiscalQuarter(cgacCode) {
- if (this.quarterRequest) {
- this.quarterRequest.cancel();
- }
-
- this.quarterRequest = AgencyHelper.fetchAgencyFiscalQuarter({
- cgac_code: cgacCode
- });
-
- return this.quarterRequest.promise;
- }
-
loadData(agencyID, activeFY) {
if (this.searchRequest) {
// A request is currently in-flight, cancel it
@@ -93,21 +66,6 @@ export class ObligatedContainer extends React.PureComponent {
obligatedAmount: parseFloat(res.data.results[0].obligated_amount),
budgetAuthority: parseFloat(res.data.results[0].budget_authority_amount)
});
-
- return this.setCgacCode(this.props.id);
- })
- .then((cgacRes) => {
- const cgacCode = cgacRes.data.results[0].toptier_agency.cgac_code;
- this.setState({
- cgacCode
- });
- return this.setFiscalQuarter(cgacCode);
- })
- .then((quarterRes) => {
- this.setState({
- inFlight: false,
- fiscalQuarter: quarterRes.data.results[0].reporting_fiscal_quarter
- });
})
.catch((err) => {
if (!isCancel(err)) {
@@ -117,8 +75,6 @@ export class ObligatedContainer extends React.PureComponent {
inFlight: false
});
this.searchRequest = null;
- this.cgacRequest = null;
- this.quarterRequest = null;
}
});
}
@@ -127,10 +83,10 @@ export class ObligatedContainer extends React.PureComponent {
return (
+ budgetAuthority={this.state.budgetAuthority}
+ asOfDate={this.props.asOfDate} />
);
}
}
diff --git a/src/js/containers/agency/visualizations/RecipientContainer.jsx b/src/js/containers/agency/visualizations/RecipientContainer.jsx
index 11a84fe290..fd5f20d557 100644
--- a/src/js/containers/agency/visualizations/RecipientContainer.jsx
+++ b/src/js/containers/agency/visualizations/RecipientContainer.jsx
@@ -16,7 +16,8 @@ import RecipientVisualization from
const propTypes = {
id: PropTypes.string,
- activeFY: PropTypes.string
+ activeFY: PropTypes.string,
+ lastUpdate: PropTypes.string
};
export default class RecipientContainer extends React.PureComponent {
@@ -174,7 +175,8 @@ ${recipient}`;
error={this.state.error}
scope={this.state.scope}
changeScope={this.changeScope}
- changePage={this.changePage} />
+ changePage={this.changePage}
+ lastUpdate={this.props.lastUpdate} />
);
}
}
diff --git a/src/js/containers/router/Router.js b/src/js/containers/router/Router.js
index 0d77688704..c650a748c5 100644
--- a/src/js/containers/router/Router.js
+++ b/src/js/containers/router/Router.js
@@ -42,7 +42,8 @@ class RouterSingleton {
parseRoute(location) {
// parse the route
- let componentReference = null;
+ // default to the 404 page, it'll be overwritten if we match with a route later
+ let componentReference = Routes.notFound.component;
for (const route of Routes.routes) {
const path = route.path;
const pathData = this.matchRoute(location, path);
@@ -66,36 +67,33 @@ class RouterSingleton {
}
}
- if (componentReference) {
- // we matched a route
- // load the component asynchronously
- // but because of network conditions, it could take a long time to load the module
- if (this.loaderTime) {
- // cancel any previous timers
- window.clearTimeout(this.loaderTime);
- }
-
- // trigger a slow load event after 1.5 seconds
- this.loaderTime = window.setTimeout(() => {
- // have the React router container show a loading spinner
- this.reactContainer.showSpinner();
- }, 1500);
-
- this.loadComponent(componentReference)
- .then((component) => {
- if (this.reactContainer) {
- // the JS module has loaded
- // have the react container mount the target component and pass down any
- // matched params
- this.reactContainer.navigateToComponent(component, this.state);
- }
-
- if (this.loaderTime) {
- // the module loaded, cancel the spinner timer
- window.clearTimeout(this.loaderTime);
- }
- });
+ // load the component asynchronously
+ // but because of network conditions, it could take a long time to load the module
+ if (this.loaderTime) {
+ // cancel any previous timers
+ window.clearTimeout(this.loaderTime);
}
+
+ // trigger a slow load event after 1.5 seconds
+ this.loaderTime = window.setTimeout(() => {
+ // have the React router container show a loading spinner
+ this.reactContainer.showSpinner();
+ }, 1500);
+
+ this.loadComponent(componentReference)
+ .then((component) => {
+ if (this.reactContainer) {
+ // the JS module has loaded
+ // have the react container mount the target component and pass down any
+ // matched params
+ this.reactContainer.navigateToComponent(component, this.state);
+ }
+
+ if (this.loaderTime) {
+ // the module loaded, cancel the spinner timer
+ window.clearTimeout(this.loaderTime);
+ }
+ });
}
matchRoute(location, path) {
diff --git a/src/js/containers/router/RouterRoutes.jsx b/src/js/containers/router/RouterRoutes.jsx
index 1c93879706..ad168ec087 100644
--- a/src/js/containers/router/RouterRoutes.jsx
+++ b/src/js/containers/router/RouterRoutes.jsx
@@ -147,8 +147,13 @@ const routes = {
}
}
],
- notfound: {
+ notFound: {
// TODO: Kevin Li - add 404 page handling
+ component: (cb) => {
+ require.ensure([], (require) => {
+ cb(require('components/errorPage/ErrorPage').default);
+ });
+ }
}
};
diff --git a/src/js/containers/search/SearchContainer.jsx b/src/js/containers/search/SearchContainer.jsx
index 9203f69699..7e46e3df24 100644
--- a/src/js/containers/search/SearchContainer.jsx
+++ b/src/js/containers/search/SearchContainer.jsx
@@ -9,6 +9,7 @@ import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { isCancel } from 'axios';
import { is } from 'immutable';
+import moment from 'moment';
import Router from 'containers/router/Router';
@@ -33,15 +34,17 @@ export class SearchContainer extends React.Component {
this.state = {
hash: '',
- hashState: 'ready'
+ hashState: 'ready',
+ lastUpdate: ''
};
this.request = null;
+ this.updateRequest = null;
}
componentWillMount() {
- // this.receiveHash(this.props.params.hash);
this.handleInitialUrl(this.props.params.hash);
+ // this.loadUpdateDate();
}
componentWillReceiveProps(nextProps) {
@@ -290,11 +293,38 @@ export class SearchContainer extends React.Component {
});
}
+ loadUpdateDate() {
+ if (this.updateRequest) {
+ this.updateRequest.cancel();
+ }
+
+ this.updateRequest = SearchHelper.fetchLastUpdate();
+ this.updateRequest.promise
+ .then((res) => {
+ this.parseUpdateDate(res.data.last_updated);
+ })
+ .catch((err) => {
+ if (!isCancel(err)) {
+ console.log(err);
+ this.updateRequest = null;
+ }
+ });
+ }
+
+ parseUpdateDate(value) {
+ const date = moment(value, 'MM/DD/YYYY');
+ this.setState({
+ lastUpdate: date.format('MMMM D, YYYY')
+ });
+ }
+
render() {
return (
+ clearAllFilters={this.props.clearAllFilters}
+ filters={this.props.filters}
+ lastUpdate={this.state.lastUpdate} />
);
}
}
diff --git a/src/js/containers/search/filters/AgencyListContainer.jsx b/src/js/containers/search/filters/AgencyListContainer.jsx
index 326e83889b..1bd6a317ab 100644
--- a/src/js/containers/search/filters/AgencyListContainer.jsx
+++ b/src/js/containers/search/filters/AgencyListContainer.jsx
@@ -70,13 +70,21 @@ export class AgencyListContainer extends React.Component {
// Format results of search for use in Autocomplete component
if (results && results.length > 0) {
results.forEach((item) => {
+ let subAbbreviation = '';
+ let topAbbreviation = '';
+ if (item.subtier_agency.abbreviation !== '') {
+ subAbbreviation = `(${item.subtier_agency.abbreviation})`;
+ }
+ if (item.toptier_agency.abbreviation !== '') {
+ topAbbreviation = `(${item.toptier_agency.abbreviation})`;
+ }
// Push two items to the autocomplete entries if subtier = toptier
// Only push items if they are not in selectedAgencies
if (item.toptier_agency.name === item.subtier_agency.name) {
if (this.props.selectedAgencies.size === 0
|| !this.props.selectedAgencies.has(`${item.id}_toptier`)) {
agencies.push({
- title: item.subtier_agency.name,
+ title: `${item.subtier_agency.name} ${topAbbreviation}`,
data: Object.assign({}, item, {
agencyType: 'toptier'
})
@@ -87,8 +95,8 @@ export class AgencyListContainer extends React.Component {
if (this.props.selectedAgencies.size === 0
|| !this.props.selectedAgencies.has(`${item.id}_subtier`)) {
agencies.push({
- title: item.subtier_agency.name,
- subtitle: `Sub-Agency of ${item.toptier_agency.name}`,
+ title: `${item.subtier_agency.name} ${subAbbreviation}`,
+ subtitle: `Sub-Agency of ${item.toptier_agency.name} ${topAbbreviation}`,
data: Object.assign({}, item, {
agencyType: 'subtier'
})
diff --git a/src/js/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer.jsx b/src/js/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer.jsx
index d822babe1e..453c92366d 100644
--- a/src/js/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer.jsx
+++ b/src/js/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer.jsx
@@ -152,7 +152,8 @@ export class SpendingByAwardingAgencyVisualizationContainer extends React.Compon
// generate the API parameters
const apiParams = {
field: 'federal_action_obligation',
- group: `awarding_agency__${this.state.agencyScope}_agency__name`,
+ group: [`awarding_agency__${this.state.agencyScope}_agency__name`,
+ `awarding_agency__${this.state.agencyScope}_agency__abbreviation`],
order: ['-aggregate'],
aggregate: 'sum',
filters: searchParams,
@@ -181,10 +182,14 @@ export class SpendingByAwardingAgencyVisualizationContainer extends React.Compon
operation.fromState(this.props.reduxFilters);
const searchParams = operation.toParams();
+ const groupFields = [`award__awarding_agency__${this.state.agencyScope}_agency__name`];
+ if (this.state.agencyScope === 'toptier') {
+ groupFields.push(`treasury_account__awarding_toptier_agency__abbreviation`);
+ }
// generate the API parameters
const apiParams = {
field: 'transaction_obligated_amount',
- group: `award__awarding_agency__${this.state.agencyScope}_agency__name`,
+ group: groupFields,
order: ['-aggregate'],
aggregate: 'sum',
filters: searchParams,
@@ -214,7 +219,20 @@ export class SpendingByAwardingAgencyVisualizationContainer extends React.Compon
// iterate through each response object and break it up into groups, x series, and y series
data.results.forEach((item) => {
- labelSeries.push(item.item);
+ let abr = '';
+ let field = null;
+ const transactionsTier =
+ `awarding_agency__${this.state.agencyScope}_agency__abbreviation`;
+ if (item.treasury_account__awarding_toptier_agency__abbreviation) {
+ field = item.treasury_account__awarding_toptier_agency__abbreviation;
+ }
+ else if (item[transactionsTier]) {
+ field = item[transactionsTier];
+ }
+ if (field !== '' && field !== null) {
+ abr = ` (${field})`;
+ }
+ labelSeries.push(`${item.item}${abr}`);
dataSeries.push(parseFloat(item.aggregate));
const description = `Spending by ${item.item}: \
diff --git a/src/js/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer.jsx b/src/js/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer.jsx
index 6d70278ac6..998233ef0a 100644
--- a/src/js/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer.jsx
+++ b/src/js/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer.jsx
@@ -151,7 +151,8 @@ export class SpendingByFundingAgencyVisualizationContainer extends React.Compone
// generate the API parameters
const apiParams = {
field: 'transaction_obligated_amount',
- group: 'treasury_account__funding_toptier_agency__name',
+ group: ['treasury_account__funding_toptier_agency__name',
+ 'treasury_account__funding_toptier_agency__abbreviation'],
order: ['-aggregate'],
aggregate: 'sum',
filters: searchParams,
@@ -183,7 +184,8 @@ export class SpendingByFundingAgencyVisualizationContainer extends React.Compone
// Generate the API parameters
const apiParams = {
field: 'obligations_incurred_by_program_object_class_cpe',
- group: 'treasury_account__funding_toptier_agency__name',
+ group: ['treasury_account__funding_toptier_agency__name',
+ 'treasury_account__funding_toptier_agency__abbreviation'],
order: ['-aggregate'],
aggregate: 'sum',
filters: searchParams,
@@ -213,7 +215,12 @@ export class SpendingByFundingAgencyVisualizationContainer extends React.Compone
// iterate through each response object and break it up into groups, x series, and y series
data.results.forEach((item) => {
- labelSeries.push(item.item);
+ let abr = '';
+ if (item.treasury_account__funding_toptier_agency__abbreviation !== ''
+ && item.treasury_account__funding_toptier_agency__abbreviation !== null) {
+ abr = ` (${item.treasury_account__funding_toptier_agency__abbreviation})`;
+ }
+ labelSeries.push(`${item.item}${abr}`);
dataSeries.push(parseFloat(item.aggregate));
const description = `Spending by ${item.item}: \
diff --git a/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx b/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx
index 0864f96ba1..a48fcf8f92 100644
--- a/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx
+++ b/src/js/containers/search/visualizations/time/TimeVisualizationSectionContainer.jsx
@@ -146,7 +146,7 @@ export class TimeVisualizationSectionContainer extends React.Component {
}
fetchTASCategories(auditTrail = null) {
- // only budget filters have been selected
+ // 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
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/dataMapping/search/awardsOperationKeys.js b/src/js/dataMapping/search/awardsOperationKeys.js
new file mode 100644
index 0000000000..a4a082bb1a
--- /dev/null
+++ b/src/js/dataMapping/search/awardsOperationKeys.js
@@ -0,0 +1,41 @@
+/**
+ * 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_locations',
+ recipientType: 'recipient_type_names',
+ placeOfPerformanceScope: 'place_of_performance_scope',
+ placeOfPerformance: 'place_of_performance_locations',
+ awardAmount: 'award_amounts',
+ 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 = {
+ 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/helpers/businessTypesHelper.js b/src/js/helpers/businessTypesHelper.js
new file mode 100644
index 0000000000..471bb36720
--- /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;
+};
diff --git a/src/js/helpers/metaTagHelper.js b/src/js/helpers/metaTagHelper.js
index 7c43648af6..4d044acca8 100644
--- a/src/js/helpers/metaTagHelper.js
+++ b/src/js/helpers/metaTagHelper.js
@@ -235,4 +235,20 @@ export const whatsNewPageMetaTags = {
twitter_data2: ""
};
+export const errorPageMetaTags = {
+ og_url: "https://beta.usaspending.gov/",
+ og_title: "Beta.USAspending.gov",
+ og_description: "Beta.USAspending.gov is the new official source of accessible, searchable and reliable spending data for the U.S. Government.",
+ og_site_name: "Beta.USAspending.gov",
+ og_image: `${productionURL}${imgDirectory}${facebookImage}`,
+ twitter_title: "Beta.USAspending.gov",
+ twitter_description: "Beta.USAspending.gov is the new official source of accessible, searchable and reliable spending data for the U.S. Government.",
+ twitter_image: `${productionURL}${imgDirectory}${twitterImage}`,
+ twitter_url: "https://beta.usaspending.gov/",
+ twitter_label1: "",
+ twitter_data1: "",
+ twitter_label2: "",
+ twitter_data2: ""
+};
+
/* eslint-enable max-len */
diff --git a/src/js/helpers/searchHelper.js b/src/js/helpers/searchHelper.js
index a2ab81ee05..13950cbf91 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();
@@ -396,3 +413,18 @@ export const restoreUrlHash = (data) => {
}
};
};
+
+export const fetchLastUpdate = () => {
+ const source = CancelToken.source();
+ return {
+ promise: Axios.request({
+ url: 'v2/awards/last_updated/',
+ baseURL: kGlobalConstants.API,
+ method: 'get',
+ cancelToken: source.token
+ }),
+ cancel() {
+ source.cancel();
+ }
+ };
+};
diff --git a/src/js/helpers/sidebarHelper.js b/src/js/helpers/sidebarHelper.js
new file mode 100644
index 0000000000..aaa10752c6
--- /dev/null
+++ b/src/js/helpers/sidebarHelper.js
@@ -0,0 +1,77 @@
+/**
+ * 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 'Budget Categories':
+ if (reduxFilters.budgetFunctions.length > 0
+ || reduxFilters.federalAccounts.length > 0
+ || Object.keys(reduxFilters.objectClasses.toArray()).length > 0) {
+ 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 */
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) {
diff --git a/src/js/models/search/SearchAwardsOperation.js b/src/js/models/search/SearchAwardsOperation.js
new file mode 100644
index 0000000000..70c4141f76
--- /dev/null
+++ b/src/js/models/search/SearchAwardsOperation.js
@@ -0,0 +1,260 @@
+/**
+ * SearchAwardsOperation.js
+ * Created by michaelbray on 8/7/17.
+ */
+
+import { concat } from 'lodash';
+import { rootKeys, timePeriodKeys, agencyKeys, awardAmountKeys }
+ from 'dataMapping/search/awardsOperationKeys';
+import * as FiscalYearHelper from 'helpers/fiscalYearHelper';
+
+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();
+ }
+
+ toParams() {
+ // 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] = 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] = {
+ [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[`${agencyArray.agencyType}_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) {
+ let locationSet = [];
+ this.selectedRecipientLocations.forEach((location) => {
+ locationSet = concat(locationSet, location.matched_ids);
+ });
+
+ filters[rootKeys.recipientLocation] = locationSet;
+ }
+
+ if (this.recipientType.length > 0) {
+ filters[rootKeys.recipientType] = this.recipientType;
+ }
+
+ // Add Locations
+ if (this.selectedLocations.length > 0) {
+ let locationSet = [];
+ this.selectedLocations.forEach((location) => {
+ locationSet = concat(locationSet, location.matched_ids);
+ });
+
+ filters[rootKeys.placeOfPerformance] = locationSet;
+ }
+
+ if (this.locationDomesticForeign !== '' && this.locationDomesticForeign !== 'all') {
+ filters[rootKeys.placeOfPerformanceScope] = this.locationDomesticForeign;
+ }
+
+ // Add Award Amounts
+ 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 = {};
+
+ // Don't include the min if it's negative
+ if (awardAmount[0] > 0) {
+ amount[awardAmountKeys.min] = awardAmount[0];
+ }
+
+ // Don't include the max if it's negative
+ if (awardAmount[1] > 0) {
+ amount[awardAmountKeys.max] = awardAmount[1];
+ }
+
+ // 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);
+ }
+ });
+
+ // 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
+ if (this.selectedAwardIDs.length > 0) {
+ filters[rootKeys.awardID] = this.selectedAwardIDs.map(
+ (awardID) => awardID.id);
+ }
+
+ // Add CFDA
+ if (this.selectedCFDA.length > 0) {
+ filters[rootKeys.cfda] = this.selectedCFDA.map((cfda) => cfda.program_number);
+ }
+
+ // Add NAICS
+ if (this.selectedNAICS.length > 0) {
+ filters[rootKeys.naics] = this.selectedNAICS.map((naics) => naics.naics);
+ }
+
+ // Add 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;
diff --git a/tests/containers/agency/visualizations/ObligatedContainer-test.jsx b/tests/containers/agency/visualizations/ObligatedContainer-test.jsx
index 9e363038e7..6fe8c814fe 100644
--- a/tests/containers/agency/visualizations/ObligatedContainer-test.jsx
+++ b/tests/containers/agency/visualizations/ObligatedContainer-test.jsx
@@ -11,10 +11,6 @@ import { ObligatedContainer } from 'containers/agency/visualizations/ObligatedCo
// spy on specific functions inside the component
const loadDataSpy = sinon.spy(ObligatedContainer.prototype, 'loadData');
-const setCgacCodeSpy = sinon.spy(
- ObligatedContainer.prototype, 'setCgacCode');
-const setFiscalQuarterSpy = sinon.spy(
- ObligatedContainer.prototype, 'setFiscalQuarter');
const inboundProps = {
id: '246',
@@ -45,34 +41,6 @@ describe('ObligatedContainer', () => {
expect(loadDataSpy.callCount).toEqual(1);
loadDataSpy.reset();
- setCgacCodeSpy.reset();
- setFiscalQuarterSpy.reset();
- });
-
- it('should make an API call for the selected agency CGAC codes after loading data', async () => {
- const container = mount( );
-
- await container.instance().searchRequest.promise;
-
- expect(setCgacCodeSpy.callCount).toEqual(1);
-
- loadDataSpy.reset();
- setCgacCodeSpy.reset();
- setFiscalQuarterSpy.reset();
- });
-
- it('should make an API call for the selected agency fiscal quarters after loading CGAC data', async () => {
- const container = mount( );
-
- await container.instance().searchRequest.promise;
-
- expect(setFiscalQuarterSpy.callCount).toEqual(1);
-
- loadDataSpy.reset();
- setCgacCodeSpy.reset();
- setFiscalQuarterSpy.reset();
});
it('should make a new API call for obligated amounts when the inbound agency ID prop' +
@@ -90,7 +58,6 @@ describe('ObligatedContainer', () => {
expect(loadDataMock).toHaveBeenCalledWith('555', inboundProps.activeFY);
loadDataSpy.reset();
- setCgacCodeSpy.reset();
- setFiscalQuarterSpy.reset();
+ loadDataSpy.reset();
});
});
diff --git a/tests/containers/search/SearchContainer-test.jsx b/tests/containers/search/SearchContainer-test.jsx
index f561342a7a..31effa98ad 100644
--- a/tests/containers/search/SearchContainer-test.jsx
+++ b/tests/containers/search/SearchContainer-test.jsx
@@ -20,51 +20,18 @@ import { mockHash, mockFilters, mockRedux, mockActions } from './mockSearchHashe
global.Promise = require.requireActual('promise');
// spy on specific functions inside the component
-const requestFiltersSpy = sinon.spy(SearchContainer.prototype, 'requestFilters');
-const generateHashSpy = sinon.spy(SearchContainer.prototype, 'generateHash');
const routerReplaceSpy = sinon.spy(Router.history, 'replace');
// mock the child component by replacing it with a function that returns a null element
jest.mock('components/search/SearchPage', () =>
jest.fn(() => null));
-const mockSearchHelper = (functionName, event, expectedResponse) => {
- jest.useFakeTimers();
- // override the specified function
- SearchHelper[functionName] = jest.fn(() => {
- // Axios normally returns a promise, replicate this, but return the expected result
- const networkCall = new Promise((resolve, reject) => {
- process.nextTick(() => {
- if (event === 'resolve') {
- resolve({
- data: expectedResponse
- });
- }
- else {
- reject({
- data: expectedResponse
- });
- }
- });
- });
+jest.mock('helpers/searchHelper', () => require('./filters/searchHelper'));
+jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
- return {
- promise: networkCall,
- cancel: jest.fn()
- };
- });
-};
-
-const unmockSearchHelper = () => {
- jest.useRealTimers();
- jest.unmock('helpers/searchHelper');
-};
describe('SearchContainer', () => {
it('should try to resolve the current URL hash on mount', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -73,14 +40,11 @@ describe('SearchContainer', () => {
container.instance().handleInitialUrl = handleInitialUrl;
container.instance().componentWillMount();
- jest.runAllTicks();
+
expect(handleInitialUrl).toHaveBeenCalledTimes(1);
});
it('should try to resolve the URL hash when new inbound hashes are passed in the URL', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -98,14 +62,10 @@ describe('SearchContainer', () => {
}
});
- jest.runAllTicks();
expect(receiveHash).toHaveBeenCalledTimes(1);
});
it('should not try to resolve the URL hash again when the URL changes programmatically', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -123,14 +83,10 @@ describe('SearchContainer', () => {
}
});
- jest.runAllTicks();
expect(receiveHash).toHaveBeenCalledTimes(0);
});
it('should try to generate a new URL hash when the Redux filters change', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -153,15 +109,11 @@ describe('SearchContainer', () => {
container.instance().componentWillReceiveProps(nextProps);
- jest.runAllTicks();
expect(generateHash).toHaveBeenCalledTimes(1);
});
describe('handleInitialUrl', () => {
it('should attempt to generate a hash from the existing Redux filters if no URL hash is found', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -174,9 +126,6 @@ describe('SearchContainer', () => {
expect(generateInitialHash).toHaveBeenCalledTimes(1);
});
it('should attempt to restore the filter set based on the current URL hash', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -192,9 +141,6 @@ describe('SearchContainer', () => {
describe('generateInitialHash', () => {
it('should use a hashless search URL if no filters are applied', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -212,9 +158,6 @@ describe('SearchContainer', () => {
});
it('should generate a hash if there are filters applied', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const filters = Object.assign({}, initialState, {
keyword: 'blerg'
});
@@ -238,9 +181,6 @@ describe('SearchContainer', () => {
describe('receiveHash', () => {
it('should request filters for the given hash', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -254,9 +194,6 @@ describe('SearchContainer', () => {
});
it('should do nothing if no hash is provided', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -272,9 +209,6 @@ describe('SearchContainer', () => {
describe('provideHash', () => {
it('should update the URL to the given hash', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -291,9 +225,6 @@ describe('SearchContainer', () => {
describe('applyFilters', () => {
it('should stop if the versions do not match', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -313,9 +244,6 @@ describe('SearchContainer', () => {
});
it('should trigger a Redux action to apply the filters', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const populateAction = jest.fn();
const actions = {
@@ -343,9 +271,6 @@ describe('SearchContainer', () => {
describe('determineIfUnfiltered', () => {
it('should return true if no filters are applied', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -354,9 +279,6 @@ describe('SearchContainer', () => {
expect(unfiltered).toBeTruthy();
});
it('should return false if filters have been applied', () => {
- mockSearchHelper('restoreUrlHash', 'resolve', mockFilters);
- mockSearchHelper('generateUrlHash', 'resolve', mockHash);
-
const container = shallow( );
@@ -369,4 +291,15 @@ describe('SearchContainer', () => {
expect(unfiltered).toBeFalsy();
});
});
+
+ describe('parseUpdateDate', () => {
+ it('should format the API response correctly and set the state', () => {
+ const container = shallow( );
+
+ container.instance().parseUpdateDate('01/01/1984');
+ expect(container.state().lastUpdate).toEqual('January 1, 1984');
+ });
+ });
});
diff --git a/tests/containers/search/filters/searchHelper.js b/tests/containers/search/filters/searchHelper.js
index 54fdcf2d55..417544491c 100644
--- a/tests/containers/search/filters/searchHelper.js
+++ b/tests/containers/search/filters/searchHelper.js
@@ -8,6 +8,8 @@ import { mockCFDA } from './cfda/mockCFDA';
import { mockNAICS } from './naics/mockNAICS';
import { mockPSC } from './psc/mockPSC';
+import { mockHash, mockFilters, mockRedux, mockActions } from '../mockSearchHashes';
+
// Fetch Locations for Autocomplete
export const fetchLocations = () => (
{
@@ -174,3 +176,44 @@ export const performPagedSearch = () => (
cancel: jest.fn()
}
);
+
+export const generateUrlHash = () => (
+ {
+ promise: new Promise((resolve) => {
+ process.nextTick(() => {
+ resolve({
+ data: mockHash
+ });
+ });
+ }),
+ cancel: jest.fn()
+ }
+);
+
+export const restoreUrlHash = () => (
+ {
+ promise: new Promise((resolve) => {
+ process.nextTick(() => {
+ resolve({
+ data: mockFilters
+ });
+ });
+ }),
+ cancel: jest.fn()
+ }
+);
+
+export const fetchLastUpdate = () => (
+ {
+ promise: new Promise((resolve) => {
+ process.nextTick(() => {
+ resolve({
+ data: {
+ last_update: '01/01/1984'
+ }
+ });
+ });
+ }),
+ cancel: jest.fn()
+ }
+);
diff --git a/tests/containers/search/visualizations/mockVisualizations.js b/tests/containers/search/visualizations/mockVisualizations.js
index f7e1511bc0..1578cac65f 100644
--- a/tests/containers/search/visualizations/mockVisualizations.js
+++ b/tests/containers/search/visualizations/mockVisualizations.js
@@ -30,11 +30,13 @@ export const awardingAgency = {
results: [
{
item: 'First Agency',
- aggregate: '456'
+ aggregate: '456',
+ treasury_account__awarding_toptier_agency__abbreviation: 'FA'
},
{
item: 'Second Agency',
- aggregate: '123'
+ aggregate: '123',
+ treasury_account__awarding_toptier_agency__abbreviation: 'SA'
}
]
};
@@ -94,11 +96,13 @@ export const fundingAgency = {
results: [
{
item: 'First Agency',
- aggregate: '456'
+ aggregate: '456',
+ treasury_account__funding_toptier_agency__abbreviation: 'FA'
},
{
item: 'Second Agency',
- aggregate: '123'
+ aggregate: '123',
+ treasury_account__funding_toptier_agency__abbreviation: 'SA'
}
]
};
diff --git a/tests/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer-test.jsx b/tests/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer-test.jsx
index 6a0c08106c..a4a74d94d9 100644
--- a/tests/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer-test.jsx
+++ b/tests/containers/search/visualizations/rank/SpendingByAwardingAgencyVisualizationContainer-test.jsx
@@ -94,7 +94,7 @@ describe('SpendingByAwardingAgencyVisualizationContainer', () => {
// validate the state contains the correctly parsed values
const expectedState = {
loading: false,
- labelSeries: ['First Agency', 'Second Agency'],
+ labelSeries: ['First Agency (FA)', 'Second Agency (SA)'],
dataSeries: [456, 123],
descriptions: ['Spending by First Agency: $456', 'Spending by Second Agency: $123'],
page: 1,
diff --git a/tests/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer-test.jsx b/tests/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer-test.jsx
index 2bdcca866e..db9fe94083 100644
--- a/tests/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer-test.jsx
+++ b/tests/containers/search/visualizations/rank/SpendingByFundingAgencyVisualizationContainer-test.jsx
@@ -97,7 +97,7 @@ describe('SpendingByFundingAgencyVisualizationContainer', () => {
// validate the state contains the correctly parsed values
const expectedState = {
loading: false,
- labelSeries: ['First Agency', 'Second Agency'],
+ labelSeries: ['First Agency (FA)', 'Second Agency (SA)'],
dataSeries: [456, 123],
descriptions: ['Spending by First Agency: $456', 'Spending by Second Agency: $123'],
page: 1,