(
-
-
-
-
-
+
+ this.setState({ selected })}
+ selected={this.state.selected}
+ denominator={favorites.length + favoritesPV.length}
+ />
+
+ {
+ favoritePositionsIsLoading && !favoritePositionsHasErrored &&
+
+ }
+ {
+ !favoritePositionsIsLoading && !favorites.length &&
+
+ }
+
-
- {
- favoritePositionsIsLoading && !favoritePositionsHasErrored &&
-
- }
- {
- !favoritePositionsIsLoading && !favorites.results.length &&
-
- }
-
-
-);
+ );
+ }
+}
FavoritePositions.propTypes = {
- favorites: POSITION_SEARCH_RESULTS.isRequired,
+ favorites: FAVORITE_POSITIONS_ARRAY,
+ favoritesPV: FAVORITE_POSITIONS_ARRAY,
favoritePositionsIsLoading: PropTypes.bool.isRequired,
favoritePositionsHasErrored: PropTypes.bool.isRequired,
bidList: BID_RESULTS.isRequired,
onSortChange: PropTypes.func.isRequired,
};
+FavoritePositions.defaultProps = {
+ favorites: [],
+ favoritesPV: [],
+};
+
export default FavoritePositions;
diff --git a/src/Components/FavoritePositions/FavoritePositions.test.jsx b/src/Components/FavoritePositions/FavoritePositions.test.jsx
index c901af9fe8..a8008be590 100644
--- a/src/Components/FavoritePositions/FavoritePositions.test.jsx
+++ b/src/Components/FavoritePositions/FavoritePositions.test.jsx
@@ -7,7 +7,8 @@ import bidListObject from '../../__mocks__/bidListObject';
describe('FavoritePositionsComponent', () => {
const props = {
- favorites: resultsObject,
+ favorites: resultsObject.results,
+ favoritesPV: resultsObject.results,
toggleFavoritePositionIsLoading: false,
toggleFavoritePositionHasErrored: false,
favoritePositionsIsLoading: false,
@@ -24,31 +25,41 @@ describe('FavoritePositionsComponent', () => {
expect(wrapper).toBeDefined();
});
+ it('is defined when selected === open', () => {
+ const wrapper = shallow(
+ ,
+ );
+ wrapper.setState({ selected: 'open' });
+ expect(wrapper).toBeDefined();
+ });
+
+ it('is defined when selected === pv', () => {
+ const wrapper = shallow(
+ ,
+ );
+ wrapper.setState({ selected: 'pv' });
+ expect(wrapper).toBeDefined();
+ });
+
it('can receive props', () => {
const wrapper = shallow(
,
);
- expect(wrapper.instance().props.favorites).toBe(resultsObject);
+ expect(wrapper.instance().props.favorites).toBe(props.favorites);
});
it('displays an alert if there are no positions', () => {
const wrapper = shallow(
,
);
expect(wrapper.find('NoFavorites').exists()).toBe(true);
});
- it('renders the Spinner when loading', () => {
- const wrapper = shallow(
- ,
- );
- expect(wrapper.instance().props.favorites).toBe(resultsObject);
- });
-
it('renders the Spinner when loading', () => {
const wrapper = shallow(
onClick(selected));
+ }
+ render() {
+ const { denominator, options } = this.props;
+ return (
+
+ {options.map((m) => {
+ const isActive = m.value === this.state.selected;
+ return (
+
+ );
+ })}
+
+ );
+ }
+}
+
+Nav.propTypes = {
+ options: PropTypes.arrayOf(
+ PropTypes.shape({
+ title: PropTypes.string.isRequired,
+ numerator: PropTypes.number.isRequired,
+ }),
+ ),
+ denominator: PropTypes.number.isRequired,
+ selected: PropTypes.string,
+ onClick: PropTypes.func,
+};
+
+Nav.defaultProps = {
+ options: [],
+ denominator: 1,
+ selected: '',
+ onClick: EMPTY_FUNCTION,
+};
+
+export default Nav;
diff --git a/src/Components/FavoritePositions/Nav/Nav.test.jsx b/src/Components/FavoritePositions/Nav/Nav.test.jsx
new file mode 100644
index 0000000000..2bebe5a524
--- /dev/null
+++ b/src/Components/FavoritePositions/Nav/Nav.test.jsx
@@ -0,0 +1,57 @@
+import { shallow } from 'enzyme';
+import React from 'react';
+import toJSON from 'enzyme-to-json';
+import sinon from 'sinon';
+import Nav from './Nav';
+
+describe('NavComponent', () => {
+ const props = {
+ options: [
+ { title: 'Test', value: '1', numerator: 2 },
+ { title: 'Test 2', value: '2', numerator: 3 },
+ ],
+ denominator: 5,
+ };
+
+ it('is defined', () => {
+ const wrapper = shallow(
+ ,
+ );
+ expect(wrapper).toBeDefined();
+ });
+
+ it('mounts with the selection prop', () => {
+ const exp = props.options[1].title;
+ const wrapper = shallow(
+ ,
+ );
+ expect(wrapper.instance().state.selected).toBe(exp);
+ });
+
+ it('updates the selected state if a new selected prop is set', () => {
+ const exp = 'test';
+ const wrapper = shallow(
+ ,
+ );
+ wrapper.setProps({ ...props, selected: exp });
+ expect(wrapper.instance().state.selected).toBe(exp);
+ });
+
+ it('sets state and calls the onClick prop when instance.onClick is called', () => {
+ const exp = 'test';
+ const spy = sinon.spy();
+ const wrapper = shallow(
+ ,
+ );
+ wrapper.instance().onClick(exp);
+ sinon.assert.calledOnce(spy);
+ expect(wrapper.instance().state.selected).toBe(exp);
+ });
+
+ it('matches snapshot', () => {
+ const wrapper = shallow(
+ ,
+ );
+ expect(toJSON(wrapper)).toMatchSnapshot();
+ });
+});
diff --git a/src/Components/FavoritePositions/Nav/NavItem.jsx b/src/Components/FavoritePositions/Nav/NavItem.jsx
new file mode 100644
index 0000000000..36373142ba
--- /dev/null
+++ b/src/Components/FavoritePositions/Nav/NavItem.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { EMPTY_FUNCTION } from '../../../Constants/PropTypes';
+
+export const NavItem = ({ title, value, numerator, denominator, isActive, onClick }) => {
+ // numerator can be a zero, but not null or undefined
+ const numeratorIsNotUndefinedOrNull = numerator !== null && numerator !== undefined;
+ const showFraction = numeratorIsNotUndefinedOrNull && denominator;
+ return (
+
+ onClick(value)}>
+ {title} { showFraction ? ({numerator}/{denominator}) : null }
+
+
+ );
+};
+
+NavItem.propTypes = {
+ title: PropTypes.string.isRequired,
+ value: PropTypes.string.isRequired,
+ numerator: PropTypes.number,
+ denominator: PropTypes.number,
+ isActive: PropTypes.bool,
+ onClick: PropTypes.func,
+};
+
+NavItem.defaultProps = {
+ numerator: undefined,
+ denominator: undefined,
+ isActive: false,
+ onClick: EMPTY_FUNCTION,
+};
+
+export default NavItem;
diff --git a/src/Components/FavoritePositions/Nav/NavItem.test.jsx b/src/Components/FavoritePositions/Nav/NavItem.test.jsx
new file mode 100644
index 0000000000..ae6502cc5f
--- /dev/null
+++ b/src/Components/FavoritePositions/Nav/NavItem.test.jsx
@@ -0,0 +1,34 @@
+import { shallow } from 'enzyme';
+import React from 'react';
+import NavItem from './NavItem';
+
+describe('NavItemComponent', () => {
+ const props = {
+ title: 'test',
+ value: 't',
+ numerator: 5,
+ denominator: 10,
+ isActive: false,
+ };
+
+ it('is defined', () => {
+ const wrapper = shallow(
+ ,
+ );
+ expect(wrapper).toBeDefined();
+ });
+
+ it('is defined when isActive === true', () => {
+ const wrapper = shallow(
+ ,
+ );
+ expect(wrapper).toBeDefined();
+ });
+
+ it('is defined when when numerator === null', () => {
+ const wrapper = shallow(
+ ,
+ );
+ expect(wrapper).toBeDefined();
+ });
+});
diff --git a/src/Components/FavoritePositions/Nav/__snapshots__/Nav.test.jsx.snap b/src/Components/FavoritePositions/Nav/__snapshots__/Nav.test.jsx.snap
new file mode 100644
index 0000000000..f98ca58c5b
--- /dev/null
+++ b/src/Components/FavoritePositions/Nav/__snapshots__/Nav.test.jsx.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`NavComponent matches snapshot 1`] = `
+
+
+
+
+`;
diff --git a/src/Components/FavoritePositions/Nav/index.js b/src/Components/FavoritePositions/Nav/index.js
new file mode 100644
index 0000000000..13fa327162
--- /dev/null
+++ b/src/Components/FavoritePositions/Nav/index.js
@@ -0,0 +1 @@
+export { default } from './Nav';
diff --git a/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap b/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap
index 60aec28d2f..5565d4b89b 100644
--- a/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap
+++ b/src/Components/FavoritePositions/__snapshots__/FavoritePositions.test.jsx.snap
@@ -15,6 +15,34 @@ exports[`FavoritePositionsComponent matches snapshot 1`] = `
title="Favorites"
/>
+
+
+
@@ -386,6 +414,177 @@ exports[`FavoritePositionsComponent matches snapshot 1`] = `
},
]
}
+ favoritesPV={
+ Array [
+ Object {
+ "bid_statistics": Array [
+ Object {
+ "at_skill": 0,
+ "bidcycle": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ "id": 6,
+ "in_grade": 0,
+ "in_grade_at_skill": 0,
+ "total_bids": 2,
+ },
+ ],
+ "bureau": "(AF) BUREAU OF AFRICAN AFFAIRS",
+ "classifications": Array [],
+ "create_date": "2006-09-20T00:00:00Z",
+ "current_assignment": Object {
+ "estimated_end_date": "2020-03-13T15:51:11.478084Z",
+ "start_date": "2018-03-13T15:51:11.478084Z",
+ "status": "active",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ "user": "John Doe",
+ },
+ "description": Object {
+ "content": "The Office Management Specialist (OMS) for the Deputy Chief of Mission (DCM) provides office management services in this Mission of six agencies, 387 American and Locally Employed Staff (LES) positions, and 279 guard force contractors. The OMS manages Executive Office operations and positionstains the DCM’s calendar in support of overall Mission goals and objectives. The OMS (DCM) drafts routine correspondence, memoranda and cables as appropriate and ensures documents are cleared and delivered in a timely fashion. The OMS serves as back up to the Ambassador’s OMS. POCs areSandra McInturff, DCM OMS, McInturffSL@state.gov, 231-77-677-7000, ext. 7452; Samue Watson, DCM, WatsonSR@state.gov, 231-77-677-7000, ext. 7452. Please submit lobbying documents to the 360 Community Lobbying Center http://appcenter.state.sbu/CLC. (Revised 1/2017)
+
+",
+ "date_created": "2018-03-08T18:00:33.241370Z",
+ "date_updated": "2018-03-13T15:55:00.018644Z",
+ "id": 90,
+ "is_editable_by_user": false,
+ "last_editing_user": null,
+ "point_of_contact": null,
+ "website": null,
+ },
+ "effective_date": "2012-10-22T00:00:00Z",
+ "grade": "06",
+ "id": 6,
+ "is_highlighted": false,
+ "is_overseas": true,
+ "languages": Array [],
+ "latest_bidcycle": Object {
+ "active": true,
+ "cycle_deadline_date": "2018-04-13T15:51:10.980554Z",
+ "cycle_end_date": "2018-06-13T15:51:10.980554Z",
+ "cycle_start_date": "2017-12-13T15:51:10.980554Z",
+ "id": 1,
+ "name": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ },
+ "organization": "(MONROVIA) MONROVIA LIBERIA",
+ "position_number": "10034001",
+ "post": Object {
+ "code": "LI6000000",
+ "cost_of_living_adjustment": 30,
+ "danger_pay": 0,
+ "differential_rate": 30,
+ "has_consumable_allowance": true,
+ "has_service_needs_differential": true,
+ "id": 160,
+ "location": Object {
+ "city": "Freetown",
+ "code": "00A",
+ "country": "Sierra Leone",
+ "id": 103,
+ "state": "",
+ },
+ "obc_id": null,
+ "rest_relaxation_point": "Paris",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ },
+ "posted_date": "2012-10-22T00:00:00Z",
+ "representation": "[10034001] OMS (DCM) (Monrovia, Liberia)",
+ "skill": "OFFICE MANAGEMENT (9017)",
+ "status": null,
+ "status_code": null,
+ "title": "OMS (DCM)",
+ "tour_of_duty": null,
+ "update_date": "2017-06-08T00:00:00Z",
+ },
+ Object {
+ "bid_statistics": Array [
+ Object {
+ "at_skill": 0,
+ "bidcycle": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ "id": 60,
+ "in_grade": 0,
+ "in_grade_at_skill": 0,
+ "total_bids": 0,
+ },
+ ],
+ "bureau": "(WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS",
+ "classifications": Array [],
+ "create_date": "2006-09-20T00:00:00Z",
+ "current_assignment": Object {
+ "estimated_end_date": "2020-03-13T15:51:13.126727Z",
+ "start_date": "2018-03-13T15:51:13.126727Z",
+ "status": "active",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ "user": "John Doe",
+ },
+ "description": Object {
+ "content": "(updated August 2012) The incumbent serves as one of four Assistant Regional Security Officers (ARSO) in the Regional Security Office in a two year assignment with a rotating management portfolio that provides generous exposure to all aspects of Diplomatic Security’s overseas programs. RSO Port-au-Prince currently consists of a 6 member Marine Security Guard Detachment, 750+direct hire Mission guard force, 17 direct-hire Bodyguards, 2 FSN Investigators, 6 member Surveillance Detection Team, a 2-person Residential Security Unit, and one Office Management Specialist. The incumbent has direct supervisory responsibilities. The incumbent is expected to manage the Marine Security Guard program, Mission residential and physical security programs, operational and administrative oversight of the direct hire Mission guard force, and complex budget issues. The incumbent also coordinates security for large-scale Mission events and visiting high ranking USG officials and conducts personnel, terrorist, criminal, and counterintelligence investigations as required. Point of Contact is RSO Jeffrey M. Roberts.",
+ "date_created": "2018-03-08T18:00:32.971745Z",
+ "date_updated": "2018-03-13T15:54:59.677323Z",
+ "id": 26,
+ "is_editable_by_user": false,
+ "last_editing_user": null,
+ "point_of_contact": null,
+ "website": null,
+ },
+ "effective_date": "2016-10-02T00:00:00Z",
+ "grade": "04",
+ "id": 60,
+ "is_highlighted": false,
+ "is_overseas": true,
+ "languages": Array [
+ Object {
+ "id": 1,
+ "language": "French (FR)",
+ "reading_proficiency": "2",
+ "representation": "French (FR) 2/2",
+ "spoken_proficiency": "2",
+ },
+ Object {
+ "id": 20,
+ "language": "Haitian Creole (HC)",
+ "reading_proficiency": "0",
+ "representation": "Haitian Creole (HC) 0/0",
+ "spoken_proficiency": "0",
+ },
+ ],
+ "latest_bidcycle": Object {
+ "active": true,
+ "cycle_deadline_date": "2018-04-13T15:51:10.980554Z",
+ "cycle_end_date": "2018-06-13T15:51:10.980554Z",
+ "cycle_start_date": "2017-12-13T15:51:10.980554Z",
+ "id": 1,
+ "name": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ },
+ "organization": "(PORT AU PRIN) PORT AU PRINCE HAITI",
+ "position_number": "56082000",
+ "post": Object {
+ "code": "HA7000000",
+ "cost_of_living_adjustment": 15,
+ "danger_pay": 15,
+ "differential_rate": 20,
+ "has_consumable_allowance": true,
+ "has_service_needs_differential": true,
+ "id": 110,
+ "location": Object {
+ "city": "Chicago",
+ "code": "101",
+ "country": "United States",
+ "id": 7,
+ "state": "IL",
+ },
+ "obc_id": null,
+ "rest_relaxation_point": "Miami",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ },
+ "posted_date": "2016-10-02T00:00:00Z",
+ "representation": "[56082000] SPECIAL AGENT (Port-Au-Prince, Haiti)",
+ "skill": "SECURITY (2501)",
+ "status": null,
+ "status_code": null,
+ "title": "SPECIAL AGENT",
+ "tour_of_duty": null,
+ "update_date": "2017-06-08T00:00:00Z",
+ },
+ ]
+ }
isLoading={false}
maxLength={300}
positions={
@@ -557,6 +756,173 @@ exports[`FavoritePositionsComponent matches snapshot 1`] = `
"tour_of_duty": null,
"update_date": "2017-06-08T00:00:00Z",
},
+ Object {
+ "bid_statistics": Array [
+ Object {
+ "at_skill": 0,
+ "bidcycle": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ "id": 6,
+ "in_grade": 0,
+ "in_grade_at_skill": 0,
+ "total_bids": 2,
+ },
+ ],
+ "bureau": "(AF) BUREAU OF AFRICAN AFFAIRS",
+ "classifications": Array [],
+ "create_date": "2006-09-20T00:00:00Z",
+ "current_assignment": Object {
+ "estimated_end_date": "2020-03-13T15:51:11.478084Z",
+ "start_date": "2018-03-13T15:51:11.478084Z",
+ "status": "active",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ "user": "John Doe",
+ },
+ "description": Object {
+ "content": "The Office Management Specialist (OMS) for the Deputy Chief of Mission (DCM) provides office management services in this Mission of six agencies, 387 American and Locally Employed Staff (LES) positions, and 279 guard force contractors. The OMS manages Executive Office operations and positionstains the DCM’s calendar in support of overall Mission goals and objectives. The OMS (DCM) drafts routine correspondence, memoranda and cables as appropriate and ensures documents are cleared and delivered in a timely fashion. The OMS serves as back up to the Ambassador’s OMS. POCs areSandra McInturff, DCM OMS, McInturffSL@state.gov, 231-77-677-7000, ext. 7452; Samue Watson, DCM, WatsonSR@state.gov, 231-77-677-7000, ext. 7452. Please submit lobbying documents to the 360 Community Lobbying Center http://appcenter.state.sbu/CLC. (Revised 1/2017)
+
+",
+ "date_created": "2018-03-08T18:00:33.241370Z",
+ "date_updated": "2018-03-13T15:55:00.018644Z",
+ "id": 90,
+ "is_editable_by_user": false,
+ "last_editing_user": null,
+ "point_of_contact": null,
+ "website": null,
+ },
+ "effective_date": "2012-10-22T00:00:00Z",
+ "grade": "06",
+ "id": 6,
+ "is_highlighted": false,
+ "is_overseas": true,
+ "languages": Array [],
+ "latest_bidcycle": Object {
+ "active": true,
+ "cycle_deadline_date": "2018-04-13T15:51:10.980554Z",
+ "cycle_end_date": "2018-06-13T15:51:10.980554Z",
+ "cycle_start_date": "2017-12-13T15:51:10.980554Z",
+ "id": 1,
+ "name": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ },
+ "organization": "(MONROVIA) MONROVIA LIBERIA",
+ "position_number": "10034001",
+ "post": Object {
+ "code": "LI6000000",
+ "cost_of_living_adjustment": 30,
+ "danger_pay": 0,
+ "differential_rate": 30,
+ "has_consumable_allowance": true,
+ "has_service_needs_differential": true,
+ "id": 160,
+ "location": Object {
+ "city": "Freetown",
+ "code": "00A",
+ "country": "Sierra Leone",
+ "id": 103,
+ "state": "",
+ },
+ "obc_id": null,
+ "rest_relaxation_point": "Paris",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ },
+ "posted_date": "2012-10-22T00:00:00Z",
+ "representation": "[10034001] OMS (DCM) (Monrovia, Liberia)",
+ "skill": "OFFICE MANAGEMENT (9017)",
+ "status": null,
+ "status_code": null,
+ "title": "OMS (DCM)",
+ "tour_of_duty": null,
+ "update_date": "2017-06-08T00:00:00Z",
+ },
+ Object {
+ "bid_statistics": Array [
+ Object {
+ "at_skill": 0,
+ "bidcycle": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ "id": 60,
+ "in_grade": 0,
+ "in_grade_at_skill": 0,
+ "total_bids": 0,
+ },
+ ],
+ "bureau": "(WHA) BUREAU OF WESTERN HEMISPHERIC AFFAIRS",
+ "classifications": Array [],
+ "create_date": "2006-09-20T00:00:00Z",
+ "current_assignment": Object {
+ "estimated_end_date": "2020-03-13T15:51:13.126727Z",
+ "start_date": "2018-03-13T15:51:13.126727Z",
+ "status": "active",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ "user": "John Doe",
+ },
+ "description": Object {
+ "content": "(updated August 2012) The incumbent serves as one of four Assistant Regional Security Officers (ARSO) in the Regional Security Office in a two year assignment with a rotating management portfolio that provides generous exposure to all aspects of Diplomatic Security’s overseas programs. RSO Port-au-Prince currently consists of a 6 member Marine Security Guard Detachment, 750+direct hire Mission guard force, 17 direct-hire Bodyguards, 2 FSN Investigators, 6 member Surveillance Detection Team, a 2-person Residential Security Unit, and one Office Management Specialist. The incumbent has direct supervisory responsibilities. The incumbent is expected to manage the Marine Security Guard program, Mission residential and physical security programs, operational and administrative oversight of the direct hire Mission guard force, and complex budget issues. The incumbent also coordinates security for large-scale Mission events and visiting high ranking USG officials and conducts personnel, terrorist, criminal, and counterintelligence investigations as required. Point of Contact is RSO Jeffrey M. Roberts.",
+ "date_created": "2018-03-08T18:00:32.971745Z",
+ "date_updated": "2018-03-13T15:54:59.677323Z",
+ "id": 26,
+ "is_editable_by_user": false,
+ "last_editing_user": null,
+ "point_of_contact": null,
+ "website": null,
+ },
+ "effective_date": "2016-10-02T00:00:00Z",
+ "grade": "04",
+ "id": 60,
+ "is_highlighted": false,
+ "is_overseas": true,
+ "languages": Array [
+ Object {
+ "id": 1,
+ "language": "French (FR)",
+ "reading_proficiency": "2",
+ "representation": "French (FR) 2/2",
+ "spoken_proficiency": "2",
+ },
+ Object {
+ "id": 20,
+ "language": "Haitian Creole (HC)",
+ "reading_proficiency": "0",
+ "representation": "Haitian Creole (HC) 0/0",
+ "spoken_proficiency": "0",
+ },
+ ],
+ "latest_bidcycle": Object {
+ "active": true,
+ "cycle_deadline_date": "2018-04-13T15:51:10.980554Z",
+ "cycle_end_date": "2018-06-13T15:51:10.980554Z",
+ "cycle_start_date": "2017-12-13T15:51:10.980554Z",
+ "id": 1,
+ "name": "Demo BidCycle 2018-03-13 15:51:10.980576+00:00",
+ },
+ "organization": "(PORT AU PRIN) PORT AU PRINCE HAITI",
+ "position_number": "56082000",
+ "post": Object {
+ "code": "HA7000000",
+ "cost_of_living_adjustment": 15,
+ "danger_pay": 15,
+ "differential_rate": 20,
+ "has_consumable_allowance": true,
+ "has_service_needs_differential": true,
+ "id": 110,
+ "location": Object {
+ "city": "Chicago",
+ "code": "101",
+ "country": "United States",
+ "id": 7,
+ "state": "IL",
+ },
+ "obc_id": null,
+ "rest_relaxation_point": "Miami",
+ "tour_of_duty": "2 YRS (2 R & R)",
+ },
+ "posted_date": "2016-10-02T00:00:00Z",
+ "representation": "[56082000] SPECIAL AGENT (Port-Au-Prince, Haiti)",
+ "skill": "SECURITY (2501)",
+ "status": null,
+ "status_code": null,
+ "title": "SPECIAL AGENT",
+ "tour_of_duty": null,
+ "update_date": "2017-06-08T00:00:00Z",
+ },
]
}
refreshFavorites={true}
diff --git a/src/Components/Header/DesktopNav/DesktopNav.jsx b/src/Components/Header/DesktopNav/DesktopNav.jsx
index 9f0f51037f..e0a03624a7 100644
--- a/src/Components/Header/DesktopNav/DesktopNav.jsx
+++ b/src/Components/Header/DesktopNav/DesktopNav.jsx
@@ -1,6 +1,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import FontAwesome from 'react-fontawesome';
+import { Flag } from 'flag';
import { USER_PROFILE } from '../../../Constants/PropTypes';
import Notifications from '../Notifications';
import GlossaryIcon from '../GlossaryIcon';
@@ -55,7 +56,9 @@ const DesktopNav = ({
{
isLoggedIn &&
-
+
+
+
}
diff --git a/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap b/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap
index 7df2940e03..8c5e89ec27 100644
--- a/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap
+++ b/src/Components/Header/DesktopNav/__snapshots__/DesktopNav.test.jsx.snap
@@ -78,6 +78,14 @@ exports[`DesktopNav matches snapshot when logged in 1`] = `
"representation": "[00180000] OMS (DCM) (Addis Ababa, Ethiopia)",
},
],
+ "favorite_positions_pv": Array [
+ Object {
+ "id": 10,
+ },
+ Object {
+ "id": 20,
+ },
+ ],
"grade": "03",
"id": 1,
"initials": "JD",
@@ -119,7 +127,11 @@ exports[`DesktopNav matches snapshot when logged in 1`] = `
className="header-nav-link"
>
-
+
+
+
diff --git a/src/Components/HomePagePositionsList/HomePagePositionsList.jsx b/src/Components/HomePagePositionsList/HomePagePositionsList.jsx
index 9a62da1990..dcdbbd6377 100644
--- a/src/Components/HomePagePositionsList/HomePagePositionsList.jsx
+++ b/src/Components/HomePagePositionsList/HomePagePositionsList.jsx
@@ -6,6 +6,7 @@ import { POSITION_DETAILS_ARRAY, FAVORITE_POSITIONS_ARRAY, BID_RESULTS, HOME_PAG
const propTypes = {
positions: POSITION_DETAILS_ARRAY,
favorites: FAVORITE_POSITIONS_ARRAY,
+ favoritesPV: FAVORITE_POSITIONS_ARRAY,
isLoading: PropTypes.bool,
bidList: BID_RESULTS.isRequired,
type: HOME_PAGE_CARD_TYPE,
@@ -19,6 +20,7 @@ const propTypes = {
const defaultProps = {
positions: [],
favorites: [],
+ favoritesPV: [],
isLoading: false,
type: 'default',
refreshFavorites: false,
@@ -27,14 +29,15 @@ const defaultProps = {
showCompareButton: false,
};
-const HomePagePositionsList = ({ positions, favorites, isLoading, bidList, type,
+const HomePagePositionsList = ({ positions, favorites, favoritesPV, isLoading, bidList, type,
refreshFavorites, title, showBidListButton, useShortFavButton, showCompareButton }) => (
{positions.map(p => (
-
+
))}
diff --git a/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap b/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap
index 761c1d84a7..abe9ec760c 100644
--- a/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap
+++ b/src/Components/HomePagePositionsList/__snapshots__/HomePagePositionsList.test.jsx.snap
@@ -157,6 +157,7 @@ exports[`HomePagePositionsList displays two rows 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isProjectedVacancy={false}
isRecentlyAvailable={false}
position={
@@ -395,6 +396,7 @@ exports[`HomePagePositionsList displays two rows 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isProjectedVacancy={false}
isRecentlyAvailable={false}
position={
@@ -633,6 +635,7 @@ exports[`HomePagePositionsList displays two rows 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isProjectedVacancy={false}
isRecentlyAvailable={false}
position={
@@ -871,6 +874,7 @@ exports[`HomePagePositionsList displays two rows 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isProjectedVacancy={false}
isRecentlyAvailable={false}
position={
@@ -1109,6 +1113,7 @@ exports[`HomePagePositionsList displays two rows 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isProjectedVacancy={false}
isRecentlyAvailable={false}
position={
@@ -1347,6 +1352,7 @@ exports[`HomePagePositionsList displays two rows 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isProjectedVacancy={false}
isRecentlyAvailable={false}
position={
@@ -1585,6 +1591,7 @@ exports[`HomePagePositionsList displays two rows 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isProjectedVacancy={false}
isRecentlyAvailable={false}
position={
diff --git a/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap b/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap
index d313a47008..f6892de987 100644
--- a/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap
+++ b/src/Components/HomePagePositionsSection/__snapshots__/HomePagePositionsSection.test.jsx.snap
@@ -170,6 +170,7 @@ exports[`HomePagePositionsSection matches snapshot 1`] = `
]
}
favorites={Array []}
+ favoritesPV={Array []}
isLoading={false}
positions={
Array [
diff --git a/src/Components/PositionDetailsItem/PositionDetailsItem.jsx b/src/Components/PositionDetailsItem/PositionDetailsItem.jsx
index 1d575d1ce8..d3a24d44b7 100644
--- a/src/Components/PositionDetailsItem/PositionDetailsItem.jsx
+++ b/src/Components/PositionDetailsItem/PositionDetailsItem.jsx
@@ -2,6 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types';
import { get } from 'lodash';
import { Flag } from 'flag';
+import { COMMON_PROPERTIES } from '../../Constants/EndpointParams';
import LanguageList from '../../Components/LanguageList/LanguageList';
import CondensedCardDataPoint from '../CondensedCardData/CondensedCardDataPoint';
import OBCUrl from '../OBCUrl';
@@ -33,6 +34,7 @@ import {
NO_POST_DIFFERENTIAL,
NO_DANGER_PAY,
NO_USER_LISTED,
+ NO_UPDATE_DATE,
} from '../../Constants/SystemMessages';
export const renderHandshake = stats => (
@@ -76,6 +78,15 @@ const PositionDetailsItem = (props) => {
const incumbent = propOrDefault(details, 'current_assignment.user', NO_USER_LISTED);
+ const getPostedDate = () => {
+ const posted = get(details, COMMON_PROPERTIES.posted);
+ if (posted) {
+ return formatDate(posted);
+ }
+ return NO_UPDATE_DATE;
+ };
+ const postedDate = getPostedDate();
+
const stats = getBidStatisticsObject(details.bid_statistics);
return (
@@ -102,6 +113,7 @@ const PositionDetailsItem = (props) => {
+
diff --git a/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap b/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap
index 5fc7b08786..4a7810db64 100644
--- a/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap
+++ b/src/Components/PositionDetailsItem/__snapshots__/PositionDetailsItem.test.jsx.snap
@@ -111,6 +111,11 @@ exports[`PositionDetailsItem matches snapshot 1`] = `
hasFixedTitleWidth={false}
title="Incumbent"
/>
+
+
+
-
-
-
+
+
+
+
+
diff --git a/src/Components/ProfileDashboard/UserProfile/__snapshots__/UserProfile.test.jsx.snap b/src/Components/ProfileDashboard/UserProfile/__snapshots__/UserProfile.test.jsx.snap
index a804111fba..7636a42ccb 100644
--- a/src/Components/ProfileDashboard/UserProfile/__snapshots__/UserProfile.test.jsx.snap
+++ b/src/Components/ProfileDashboard/UserProfile/__snapshots__/UserProfile.test.jsx.snap
@@ -33,6 +33,14 @@ exports[`UserProfileComponent matches snapshot 1`] = `
"representation": "[00180000] OMS (DCM) (Addis Ababa, Ethiopia)",
},
],
+ "favorite_positions_pv": Array [
+ Object {
+ "id": 10,
+ },
+ Object {
+ "id": 20,
+ },
+ ],
"grade": "03",
"id": 1,
"initials": "JD",
@@ -124,6 +132,14 @@ exports[`UserProfileComponent matches snapshot 1`] = `
"representation": "[00180000] OMS (DCM) (Addis Ababa, Ethiopia)",
},
],
+ "favorite_positions_pv": Array [
+ Object {
+ "id": 10,
+ },
+ Object {
+ "id": 20,
+ },
+ ],
"grade": "03",
"id": 1,
"initials": "JD",
diff --git a/src/Components/ResultsCard/ResultsCard.jsx b/src/Components/ResultsCard/ResultsCard.jsx
index 27de3c7007..0f739e35b3 100644
--- a/src/Components/ResultsCard/ResultsCard.jsx
+++ b/src/Components/ResultsCard/ResultsCard.jsx
@@ -76,6 +76,7 @@ class ResultsCard extends Component {
id,
result,
favorites,
+ favoritesPV,
} = this.props;
const { isProjectedVacancy } = this.context;
@@ -122,11 +123,12 @@ class ResultsCard extends Component {
];
options.favorite = {
- compareArray: favorites,
+ compareArray: isProjectedVacancy ? favoritesPV : favorites,
refKey: result.id,
hasBorder: true,
useButtonClass: true,
useLongText: true,
+ isPV: isProjectedVacancy,
};
options.compare = {
@@ -193,16 +195,13 @@ class ResultsCard extends Component {
}
/>
- {
- !isProjectedVacancy &&
{
!!favorites &&
}
-
+ {!isProjectedVacancy && }
- }
@@ -231,10 +230,12 @@ ResultsCard.propTypes = {
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
result: POSITION_DETAILS.isRequired,
favorites: FAVORITE_POSITIONS_ARRAY,
+ favoritesPV: FAVORITE_POSITIONS_ARRAY,
};
ResultsCard.defaultProps = {
favorites: [],
+ favoritesPV: [],
};
export default ResultsCard;
diff --git a/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx b/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx
index 46fb8aa1bb..3c101712d2 100644
--- a/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx
+++ b/src/Components/ResultsCondensedCard/ResultsCondensedCard.jsx
@@ -10,6 +10,7 @@ const ResultsCondensedCard = (
{
position,
favorites,
+ favoritesPV,
bidList,
type,
refreshFavorites,
@@ -30,12 +31,14 @@ const ResultsCondensedCard = (
@@ -56,7 +58,8 @@ class ResultsCondensedCardBottom extends Component {
hideText={useShortFavButton}
hasBorder
refKey={position.id}
- compareArray={favorites}
+ isPV={position.isPV}
+ compareArray={position.isPV ? favoritesPV : favorites}
useButtonClass={!useShortFavButton}
useButtonClassSecondary={useShortFavButton}
refresh={refreshFavorites}
@@ -66,7 +69,7 @@ class ResultsCondensedCardBottom extends Component {
render={this.renderBidListButton}
/>
{
- showCompareButton &&
+ showCompareButton && !isProjectedVacancy &&
}
@@ -79,11 +82,13 @@ class ResultsCondensedCardBottom extends Component {
ResultsCondensedCardBottom.propTypes = {
position: POSITION_DETAILS.isRequired,
favorites: FAVORITE_POSITIONS_ARRAY.isRequired,
+ favoritesPV: FAVORITE_POSITIONS_ARRAY.isRequired,
refreshFavorites: PropTypes.bool,
showBidListButton: PropTypes.bool,
showBidCount: PropTypes.bool,
useShortFavButton: PropTypes.bool,
showCompareButton: PropTypes.bool,
+ isProjectedVacancy: PropTypes.bool,
};
ResultsCondensedCardBottom.defaultProps = {
@@ -93,6 +98,7 @@ ResultsCondensedCardBottom.defaultProps = {
showBidCount: true,
useShortFavButton: false,
showCompareButton: false,
+ isProjectedVacancy: false,
};
export default ResultsCondensedCardBottom;
diff --git a/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx b/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx
index be0281a221..36486a5358 100644
--- a/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx
+++ b/src/Components/ResultsCondensedCardBottom/ResultsCondensedCardBottom.test.jsx
@@ -9,12 +9,14 @@ import { bidderUserObject } from '../../__mocks__/userObject';
describe('ResultsCondensedCardBottomComponent', () => {
const type = 'default';
const favorites = bidderUserObject.favorite_positions;
+ const favoritesPV = bidderUserObject.favorite_positions_pv;
it('is defined', () => {
const wrapper = shallow(
,
);
expect(wrapper).toBeDefined();
@@ -26,6 +28,7 @@ describe('ResultsCondensedCardBottomComponent', () => {
position={resultsObject.results[0]}
bidList={bidListObject.results}
favorites={favorites}
+ favoritesPV={favoritesPV}
/>,
);
expect(wrapper.instance().props.type).toBe(type);
@@ -37,6 +40,7 @@ describe('ResultsCondensedCardBottomComponent', () => {
position={resultsObject.results[0]}
bidList={bidListObject.results}
favorites={favorites}
+ favoritesPV={favoritesPV}
showBidCount={false}
/>,
);
@@ -49,6 +53,7 @@ describe('ResultsCondensedCardBottomComponent', () => {
position={resultsObject.results[0]}
bidList={bidListObject.results}
favorites={favorites}
+ favoritesPV={favoritesPV}
showBidCount
/>,
);
@@ -61,6 +66,7 @@ describe('ResultsCondensedCardBottomComponent', () => {
position={resultsObject.results[0]}
bidList={bidListObject.results}
favorites={favorites}
+ favoritesPV={favoritesPV}
showBidListButton
/>,
);
@@ -73,6 +79,7 @@ describe('ResultsCondensedCardBottomComponent', () => {
position={resultsObject.results[0]}
bidList={bidListObject.results}
favorites={favorites}
+ favoritesPV={favoritesPV}
/>,
);
expect(toJSON(wrapper)).toMatchSnapshot();
@@ -84,6 +91,7 @@ describe('ResultsCondensedCardBottomComponent', () => {
position={resultsObject.results[0]}
bidList={bidListObject.results}
favorites={favorites}
+ favoritesPV={favoritesPV}
showBidListButton
/>,
);
diff --git a/src/Components/ResultsCondensedCardTop/ResultsCondensedCardTop.jsx b/src/Components/ResultsCondensedCardTop/ResultsCondensedCardTop.jsx
index 6f03fd42e5..ff1fa13af8 100644
--- a/src/Components/ResultsCondensedCardTop/ResultsCondensedCardTop.jsx
+++ b/src/Components/ResultsCondensedCardTop/ResultsCondensedCardTop.jsx
@@ -49,7 +49,7 @@ const ResultsCondensedCardTop = ({ position, type, isProjectedVacancy, isRecentl
}
>
{useType && }
- {position.title} View position
+ {position.title} {!isProjectedVacancy && View position}
diff --git a/src/Components/ResultsContainer/ResultsContainer.jsx b/src/Components/ResultsContainer/ResultsContainer.jsx
index 1ccdd2af1f..4016135d32 100644
--- a/src/Components/ResultsContainer/ResultsContainer.jsx
+++ b/src/Components/ResultsContainer/ResultsContainer.jsx
@@ -4,15 +4,13 @@ import ScrollUpButton from '../ScrollUpButton';
import PaginationWrapper from '../PaginationWrapper/PaginationWrapper';
import ResultsList from '../ResultsList/ResultsList';
import { POSITION_SEARCH_RESULTS, EMPTY_FUNCTION, SAVED_SEARCH_MESSAGE, SAVED_SEARCH_OBJECT,
- SORT_BY_PARENT_OBJECT, PILL_ITEM_ARRAY, USER_PROFILE, NEW_SAVED_SEARCH_SUCCESS_OBJECT,
+ SORT_BY_PARENT_OBJECT, PILL_ITEM_ARRAY, USER_PROFILE,
BID_RESULTS } from '../../Constants/PropTypes';
import Spinner from '../Spinner';
import Alert from '../Alert/Alert';
import ResultsControls from '../ResultsControls/ResultsControls';
import ResultsPillContainer from '../ResultsPillContainer/ResultsPillContainer';
import SaveNewSearchContainer from '../SaveNewSearchContainer';
-import SaveNewSearchAlert from '../SaveNewSearchAlert';
-import Dismiss from '../Dismiss';
class ResultsContainer extends Component {
constructor(props) {
@@ -29,18 +27,12 @@ class ResultsContainer extends Component {
const { results, isLoading, hasErrored, sortBy, pageSize, hasLoaded, totalResults,
defaultSort, pageSizes, defaultPageSize, refreshKey, pillFilters, userProfile,
defaultPageNumber, queryParamUpdate, onQueryParamToggle,
- newSavedSearchHasErrored, saveSearch, newSavedSearchSuccess,
- currentSavedSearch, newSavedSearchIsSaving, resetSavedSearchAlerts, bidList,
+ newSavedSearchHasErrored, saveSearch,
+ currentSavedSearch, newSavedSearchIsSaving, bidList,
} = this.props;
const { isProjectedVacancy } = this.context;
return (
- {
- newSavedSearchSuccess.title &&
-
-
-
- }
@@ -130,11 +122,9 @@ ResultsContainer.propTypes = {
scrollToTop: PropTypes.func,
userProfile: USER_PROFILE,
saveSearch: PropTypes.func.isRequired,
- newSavedSearchSuccess: NEW_SAVED_SEARCH_SUCCESS_OBJECT.isRequired,
newSavedSearchHasErrored: SAVED_SEARCH_MESSAGE.isRequired,
newSavedSearchIsSaving: PropTypes.bool.isRequired,
currentSavedSearch: SAVED_SEARCH_OBJECT,
- resetSavedSearchAlerts: PropTypes.func.isRequired,
totalResults: PropTypes.number,
bidList: BID_RESULTS.isRequired,
};
diff --git a/src/Components/ResultsContainer/__snapshots__/ResultsContainer.test.jsx.snap b/src/Components/ResultsContainer/__snapshots__/ResultsContainer.test.jsx.snap
index 01990a6987..8c91e1f955 100644
--- a/src/Components/ResultsContainer/__snapshots__/ResultsContainer.test.jsx.snap
+++ b/src/Components/ResultsContainer/__snapshots__/ResultsContainer.test.jsx.snap
@@ -215,7 +215,6 @@ exports[`ResultsContainerComponent matches snapshot 1`] = `
currentSavedSearch={Object {}}
newSavedSearchHasErrored={false}
newSavedSearchIsSaving={false}
- newSavedSearchSuccess={Object {}}
saveSearch={[Function]}
/>
{
+const ResultsList = ({ results, isLoading, favorites, favoritesPV, bidList }) => {
const mapResults = results.results || [];
return (
@@ -14,6 +14,7 @@ const ResultsList = ({ results, isLoading, favorites, bidList }) => {
@@ -116,11 +114,9 @@ Results.propTypes = {
scrollToTop: PropTypes.func,
userProfile: USER_PROFILE,
saveSearch: PropTypes.func.isRequired,
- newSavedSearchSuccess: NEW_SAVED_SEARCH_SUCCESS_OBJECT.isRequired,
newSavedSearchHasErrored: SAVED_SEARCH_MESSAGE.isRequired,
newSavedSearchIsSaving: PropTypes.bool.isRequired,
currentSavedSearch: SAVED_SEARCH_OBJECT,
- resetSavedSearchAlerts: PropTypes.func.isRequired,
fetchMissionAutocomplete: PropTypes.func.isRequired,
missionSearchResults: MISSION_DETAILS_ARRAY.isRequired,
missionSearchIsLoading: PropTypes.bool.isRequired,
diff --git a/src/Components/SavedSearchMessages/Success.jsx b/src/Components/SavedSearchMessages/Success.jsx
new file mode 100644
index 0000000000..2c66af3c96
--- /dev/null
+++ b/src/Components/SavedSearchMessages/Success.jsx
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { Link } from 'react-router-dom';
+
+const Success = ({ name, isUpdated }) => {
+ let prefix = 'New';
+ let suffix = 'saved';
+ if (isUpdated) {
+ prefix = 'Your saved';
+ suffix = 'updated';
+ }
+ return (
+
{prefix} search with the name {name} has been {suffix}! Go to Saved Searches.
+ );
+};
+
+Success.propTypes = {
+ name: PropTypes.string.isRequired,
+ isUpdated: PropTypes.bool,
+};
+
+Success.defaultProps = {
+ isUpdated: false,
+};
+
+export default Success;
diff --git a/src/Components/SavedSearchMessages/Success.test.jsx b/src/Components/SavedSearchMessages/Success.test.jsx
new file mode 100644
index 0000000000..aff2be05c9
--- /dev/null
+++ b/src/Components/SavedSearchMessages/Success.test.jsx
@@ -0,0 +1,15 @@
+import { shallow } from 'enzyme';
+import React from 'react';
+import Success from './Success';
+
+describe('Success', () => {
+ it('is defined', () => {
+ const wrapper = shallow(
);
+ expect(wrapper).toBeDefined();
+ });
+
+ it('is defined when isUpdated is true', () => {
+ const wrapper = shallow(
);
+ expect(wrapper).toBeDefined();
+ });
+});
diff --git a/src/Components/SavedSearches/SavedSearches.jsx b/src/Components/SavedSearches/SavedSearches.jsx
index 291452b2ce..24b0dec144 100644
--- a/src/Components/SavedSearches/SavedSearches.jsx
+++ b/src/Components/SavedSearches/SavedSearches.jsx
@@ -1,16 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
-import Alert from '../Alert/Alert';
import ProfileSectionTitle from '../ProfileSectionTitle';
import SelectForm from '../SelectForm';
import Spinner from '../Spinner';
import SavedSearchesList from './SavedSearchesList';
import {
SAVED_SEARCH_PARENT_OBJECT,
- DELETE_SAVED_SEARCH_HAS_ERRORED,
- DELETE_SAVED_SEARCH_SUCCESS,
- CLONE_SAVED_SEARCH_HAS_ERRORED,
- CLONE_SAVED_SEARCH_SUCCESS,
MAPPED_PARAM_ARRAY,
} from '../../Constants/PropTypes';
import { SAVED_SEARCH_SORTS } from '../../Constants/Sort';
@@ -22,21 +17,11 @@ const SavedSearches = (props) => {
goToSavedSearch,
deleteSearch,
onSortChange,
- deleteSavedSearchIsLoading,
- deleteSavedSearchHasErrored,
- deleteSavedSearchSuccess,
- cloneSavedSearch,
- cloneSavedSearchIsLoading,
- cloneSavedSearchHasErrored,
- cloneSavedSearchSuccess,
mappedParams,
filtersIsLoading,
} = props;
- const isLoading = (
- filtersIsLoading || savedSearchesIsLoading ||
- cloneSavedSearchIsLoading || deleteSavedSearchIsLoading
- );
+ const isLoading = (filtersIsLoading || savedSearchesIsLoading);
return (
{
/>
- {
- // Deleting a saved search has errored
- !deleteSavedSearchIsLoading && !deleteSavedSearchSuccess && deleteSavedSearchHasErrored &&
-
- }
- {
- // Deleting a saved search was successful
- !deleteSavedSearchIsLoading && deleteSavedSearchSuccess && !deleteSavedSearchHasErrored &&
-
- }
- {
- // Cloning a saved search has errored
- !cloneSavedSearchIsLoading && !cloneSavedSearchSuccess && cloneSavedSearchHasErrored &&
-
- }
- {
- // Cloning a saved search was successful
- !cloneSavedSearchIsLoading && cloneSavedSearchSuccess && !cloneSavedSearchHasErrored &&
-
- }
{
isLoading &&
@@ -107,7 +52,6 @@ const SavedSearches = (props) => {
savedSearches={savedSearches}
goToSavedSearch={goToSavedSearch}
deleteSearch={deleteSearch}
- cloneSavedSearch={cloneSavedSearch}
mappedParams={mappedParams}
/>
}
@@ -120,13 +64,6 @@ SavedSearches.propTypes = {
savedSearchesIsLoading: PropTypes.bool.isRequired,
goToSavedSearch: PropTypes.func.isRequired,
deleteSearch: PropTypes.func.isRequired,
- deleteSavedSearchIsLoading: PropTypes.bool.isRequired,
- deleteSavedSearchHasErrored: DELETE_SAVED_SEARCH_HAS_ERRORED.isRequired,
- deleteSavedSearchSuccess: DELETE_SAVED_SEARCH_SUCCESS.isRequired,
- cloneSavedSearch: PropTypes.func.isRequired,
- cloneSavedSearchIsLoading: PropTypes.bool.isRequired,
- cloneSavedSearchHasErrored: CLONE_SAVED_SEARCH_HAS_ERRORED.isRequired,
- cloneSavedSearchSuccess: CLONE_SAVED_SEARCH_SUCCESS.isRequired,
mappedParams: MAPPED_PARAM_ARRAY,
filtersIsLoading: PropTypes.bool.isRequired,
onSortChange: PropTypes.func.isRequired,
diff --git a/src/Components/SavedSearches/SavedSearches.test.jsx b/src/Components/SavedSearches/SavedSearches.test.jsx
index a19329e0bd..9b4614697e 100644
--- a/src/Components/SavedSearches/SavedSearches.test.jsx
+++ b/src/Components/SavedSearches/SavedSearches.test.jsx
@@ -11,13 +11,6 @@ describe('SavedSearchesComponent', () => {
savedSearchesHasErrored: false,
goToSavedSearch: () => {},
deleteSearch: () => {},
- deleteSavedSearchIsLoading: false,
- deleteSavedSearchHasErrored: false,
- deleteSavedSearchSuccess: false,
- cloneSavedSearchIsLoading: false,
- cloneSavedSearchHasErrored: false,
- cloneSavedSearchSuccess: false,
- cloneSavedSearch: () => {},
filtersIsLoading: false,
onSortChange: () => {},
defaultSort: '',
@@ -32,31 +25,6 @@ describe('SavedSearchesComponent', () => {
expect(wrapper).toBeDefined();
});
- [
- { deleteSavedSearchIsLoading: false,
- deleteSavedSearchSuccess: false,
- deleteSavedSearchHasErrored: 'message' },
- { deleteSavedSearchIsLoading: false,
- deleteSavedSearchSuccess: 'message',
- deleteSavedSearchHasErrored: false },
- { cloneSavedSearchIsLoading: false,
- cloneSavedSearchSuccess: false,
- cloneSavedSearchHasErrored: 'message' },
- { cloneSavedSearchIsLoading: false,
- cloneSavedSearchSuccess: 'message',
- cloneSavedSearchHasErrored: false },
- ].forEach((group, i) => {
- it(`is defined for each alert group - at iterator ${i}`, () => {
- const wrapper = shallow(
-
,
- );
- expect(wrapper).toBeDefined();
- });
- });
-
it('can receive props', () => {
const wrapper = shallow(
{
goToSavedSearch: () => {},
deleteSavedSearch: () => {},
deleteSearch: () => {},
- cloneSavedSearch: () => {},
mappedParams: [],
};
diff --git a/src/Components/SavedSearches/__snapshots__/SavedSearches.test.jsx.snap b/src/Components/SavedSearches/__snapshots__/SavedSearches.test.jsx.snap
index e113dac771..23e9beb597 100644
--- a/src/Components/SavedSearches/__snapshots__/SavedSearches.test.jsx.snap
+++ b/src/Components/SavedSearches/__snapshots__/SavedSearches.test.jsx.snap
@@ -51,7 +51,6 @@ exports[`SavedSearchesComponent matches snapshot 1`] = `
(
const endDate = get(entry, 'current_assignment.estimated_end_date');
const formattedEndDate = endDate ? formatDate(endDate) : null;
return {
- ...entry,
+ ...mapValues(entry, x => !x ? '' : x), // eslint-disable-line no-confusing-arrow
+ position_number: getFormattedNumCSV(entry.position_number),
+ grade: getFormattedNumCSV(entry.grade),
estimated_end_date: formattedEndDate,
};
})
@@ -88,11 +90,13 @@ class SearchResultsExportLink extends Component {
{ this.csvLink = x; }}
target="_blank"
filename={this.props.filename}
data={data}
headers={HEADERS}
+ uFEFF={false}
/>
);
diff --git a/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx b/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx
index f0a3443f3a..d08835f3cd 100644
--- a/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx
+++ b/src/Components/SearchResultsExportLink/SearchResultsExportLink.test.jsx
@@ -45,12 +45,12 @@ describe('SearchResultsExportLink', () => {
it('processes data correctly', () => {
const data = [{ a: 1, b: 2, current_assignment: { estimated_end_date: '2019-01-08T00:00:00Z' } }];
const output = processData(data);
- expect(output).toEqual([
+ expect(output[0]).toMatchObject(
{ a: 1,
b: 2,
current_assignment: { estimated_end_date: '2019-01-08T00:00:00Z' },
estimated_end_date: '01/07/2019' },
- ]);
+ );
});
it('matches snapshot', () => {
diff --git a/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap b/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap
index 3f1262bdad..32e2e6e6bc 100644
--- a/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap
+++ b/src/Components/SearchResultsExportLink/__snapshots__/SearchResultsExportLink.test.jsx.snap
@@ -73,7 +73,8 @@ exports[`SearchResultsExportLink matches snapshot 1`] = `
separator=","
tabIndex="-1"
target="_blank"
- uFEFF={true}
+ transform={[Function]}
+ uFEFF={false}
/>
`;
diff --git a/src/Constants/SystemMessages.js b/src/Constants/SystemMessages.js
index 947ddf4cf5..01851a6762 100644
--- a/src/Constants/SystemMessages.js
+++ b/src/Constants/SystemMessages.js
@@ -1,5 +1,6 @@
import FavoriteSuccess from '../Components/FavoriteMessages/Success';
import BidAddSuccess from '../Components/BidListMessages/Success';
+import SavedSearchSuccess from '../Components/SavedSearchMessages/Success';
export const DEFAULT_TEXT = 'None listed';
@@ -57,11 +58,14 @@ export const SUBMIT_BID_ERROR = 'Error trying to submit this bid.';
export const NEW_SAVED_SEARCH_SUCCESS_TITLE = 'Success';
export const UPDATED_SAVED_SEARCH_SUCCESS_TITLE = 'Saved search updated';
+export const DELETE_SAVED_SEARCH_SUCCESS_TITLE = 'Success';
+export const DELETE_SAVED_SEARCH_ERROR_TITLE = 'Saved search error';
-export const NEW_SAVED_SEARCH_SUCCESS = name =>
- `New search with the name "${name}" has been saved! You can go to your profile to view all of your saved searches.`;
-export const UPDATED_SAVED_SEARCH_SUCCESS = name =>
- `Your saved search with the name "${name}" has been updated! You can go to your profile to view all of your saved searches.`;
+
+export const NEW_SAVED_SEARCH_SUCCESS = name => SavedSearchSuccess({ name });
+export const UPDATED_SAVED_SEARCH_SUCCESS = name => SavedSearchSuccess({ name, isUpdated: true });
+export const DELETE_SAVED_SEARCH_SUCCESS = 'Successfully deleted the selected search';
+export const DELETE_SAVED_SEARCH_ERROR = 'An error occurred trying to delete this search.';
export const CANNOT_BID_SUFFIX = ', but can be favorited for the future.';
export const CANNOT_BID_DEFAULT = `This position is not available to bid on${CANNOT_BID_SUFFIX}`;
diff --git a/src/Constants/SystemMessages.test.js b/src/Constants/SystemMessages.test.js
index bf2fb8031f..1061dccc23 100644
--- a/src/Constants/SystemMessages.test.js
+++ b/src/Constants/SystemMessages.test.js
@@ -53,14 +53,13 @@ describe('SystemMessages', () => {
});
it('should have all expected messages that accept parameters defined', () => {
- const textToCheck = 'test_word';
const messages = [
'UPDATED_SAVED_SEARCH_SUCCESS',
'NEW_SAVED_SEARCH_SUCCESS',
];
messages.forEach((message) => {
- expect(SystemMessages[message](textToCheck).indexOf(textToCheck)).toBeDefined();
+ expect(SystemMessages[message]('name')).toBeDefined();
});
});
diff --git a/src/Containers/BidTracker/BidTracker.test.jsx b/src/Containers/BidTracker/BidTracker.test.jsx
index 260ad1cb93..bd1a322d33 100644
--- a/src/Containers/BidTracker/BidTracker.test.jsx
+++ b/src/Containers/BidTracker/BidTracker.test.jsx
@@ -41,7 +41,8 @@ describe('BidTracker', () => {
/>);
const spy = sinon.spy(wrapper.instance(), 'scrollToId');
wrapper.instance().componentDidUpdate();
- sinon.assert.calledOnce(spy);
+ // called once on mount, once after didUpdate()
+ sinon.assert.calledTwice(spy);
});
it('calls scrollIntoView when scrollToId is called', () => {
diff --git a/src/Containers/BidderPortfolio/BidderPortfolio.jsx b/src/Containers/BidderPortfolio/BidderPortfolio.jsx
index 085a2c14b4..2bb05b40cd 100644
--- a/src/Containers/BidderPortfolio/BidderPortfolio.jsx
+++ b/src/Containers/BidderPortfolio/BidderPortfolio.jsx
@@ -16,7 +16,7 @@ class BidderPortfolio extends Component {
this.state = {
key: 0,
query: { value: window.location.search.replace('?', '') || '' },
- defaultPageSize: { value: 8 },
+ defaultPageSize: { value: 24 },
defaultPageNumber: { value: 1 },
defaultKeyword: { value: '' },
};
diff --git a/src/Containers/Compare/Compare.jsx b/src/Containers/Compare/Compare.jsx
index 246796925f..8f72aa39b9 100644
--- a/src/Containers/Compare/Compare.jsx
+++ b/src/Containers/Compare/Compare.jsx
@@ -50,7 +50,7 @@ export class Compare extends Component {
return (
({
@@ -36,9 +39,10 @@ export const mapStateToProps = state => ({
hasErrored: state.userProfileFavoritePositionHasErrored || false,
});
-export const mapDispatchToProps = dispatch => ({
- onToggle: (id, remove, refresh = false) =>
- dispatch(userProfileToggleFavoritePosition(id, remove, refresh)),
+export const mapDispatchToProps = (dispatch, ownProps) => ({
+ onToggle: (id, remove, refresh = false) => {
+ dispatch(userProfileToggleFavoritePosition(id, remove, refresh, get(ownProps, 'isPV')));
+ },
});
export default connect(mapStateToProps, mapDispatchToProps)(FavoriteContainer);
diff --git a/src/Containers/Favorites/Favorites.jsx b/src/Containers/Favorites/Favorites.jsx
index be74dafd21..6232bef9f4 100644
--- a/src/Containers/Favorites/Favorites.jsx
+++ b/src/Containers/Favorites/Favorites.jsx
@@ -42,7 +42,8 @@ class FavoritePositionsContainer extends Component {
return (
@@ -231,12 +227,10 @@ class Results extends Component {
setAccordion={setAccordion}
scrollToTop={scrollToTop}
userProfile={userProfile}
- newSavedSearchSuccess={newSavedSearchSuccess}
newSavedSearchIsSaving={newSavedSearchIsSaving}
newSavedSearchHasErrored={newSavedSearchHasErrored}
saveSearch={this.saveSearch}
currentSavedSearch={currentSavedSearch}
- resetSavedSearchAlerts={resetSavedSearchAlerts}
fetchMissionAutocomplete={fetchMissionAutocomplete}
missionSearchResults={missionSearchResults}
missionSearchIsLoading={missionSearchIsLoading}
@@ -271,12 +265,10 @@ Results.propTypes = {
selectedAccordion: ACCORDION_SELECTION_OBJECT,
setAccordion: PropTypes.func.isRequired,
userProfile: USER_PROFILE,
- newSavedSearchSuccess: NEW_SAVED_SEARCH_SUCCESS_OBJECT,
newSavedSearchIsSaving: PropTypes.bool.isRequired,
newSavedSearchHasErrored: SAVED_SEARCH_MESSAGE,
saveSearch: PropTypes.func.isRequired,
currentSavedSearch: SAVED_SEARCH_OBJECT,
- resetSavedSearchAlerts: PropTypes.func.isRequired,
fetchMissionAutocomplete: PropTypes.func.isRequired,
missionSearchResults: MISSION_DETAILS_ARRAY.isRequired,
missionSearchIsLoading: PropTypes.bool.isRequired,
@@ -301,11 +293,9 @@ Results.defaultProps = {
filtersIsLoading: true,
selectedAccordion: ACCORDION_SELECTION,
userProfile: {},
- newSavedSearchSuccess: {},
newSavedSearchHasErrored: false,
newSavedSearchIsSaving: false,
currentSavedSearch: {},
- resetSavedSearchAlerts: EMPTY_FUNCTION,
fetchMissionAutocomplete: EMPTY_FUNCTION,
missionSearchResults: [],
missionSearchIsLoading: false,
@@ -333,7 +323,6 @@ const mapStateToProps = state => ({
selectedAccordion: state.selectedAccordion,
routerLocations: state.routerLocations,
userProfile: state.userProfile,
- newSavedSearchSuccess: state.newSavedSearchSuccess,
newSavedSearchIsSaving: state.newSavedSearchIsSaving,
newSavedSearchHasErrored: state.newSavedSearchHasErrored,
currentSavedSearch: state.currentSavedSearch,
@@ -355,7 +344,6 @@ export const mapDispatchToProps = dispatch => ({
setAccordion: accordion => dispatch(setSelectedAccordion(accordion)),
onNavigateTo: dest => dispatch(push(dest)),
saveSearch: (object, id) => dispatch(saveSearch(object, id)),
- resetSavedSearchAlerts: () => dispatch(routeChangeResetState()),
fetchMissionAutocomplete: query => dispatch(missionSearchFetchData(query)),
fetchPostAutocomplete: query => dispatch(postSearchFetchData(query)),
toggleSearchBarVisibility: bool => dispatch(toggleSearchBar(bool)),
diff --git a/src/Containers/SavedSearches/SavedSearches.jsx b/src/Containers/SavedSearches/SavedSearches.jsx
index 5e478023f3..79237d91aa 100644
--- a/src/Containers/SavedSearches/SavedSearches.jsx
+++ b/src/Containers/SavedSearches/SavedSearches.jsx
@@ -3,10 +3,8 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { withRouter } from 'react-router';
import { push } from 'react-router-redux';
-import { savedSearchesFetchData, setCurrentSavedSearch, deleteSavedSearch, routeChangeResetState,
-cloneSavedSearch } from '../../actions/savedSearch';
-import { SAVED_SEARCH_PARENT_OBJECT, DELETE_SAVED_SEARCH_HAS_ERRORED, DELETE_SAVED_SEARCH_SUCCESS,
-CLONE_SAVED_SEARCH_HAS_ERRORED, CLONE_SAVED_SEARCH_SUCCESS, EMPTY_FUNCTION } from '../../Constants/PropTypes';
+import { savedSearchesFetchData, setCurrentSavedSearch, deleteSavedSearch } from '../../actions/savedSearch';
+import { SAVED_SEARCH_PARENT_OBJECT } from '../../Constants/PropTypes';
import { DEFAULT_USER_PROFILE, POSITION_RESULTS_OBJECT } from '../../Constants/DefaultProps';
import SavedSearchesMap from '../SavedSearchesMap';
import { formQueryString } from '../../utilities';
@@ -23,8 +21,6 @@ class SavedSearchesContainer extends Component {
componentWillMount() {
this.getSavedSearches();
- // reset the alert messages
- this.props.routeChangeResetState();
}
getSavedSearches() {
@@ -45,10 +41,7 @@ class SavedSearchesContainer extends Component {
}
render() {
- const { savedSearches, deleteSearch, cloneSearch, ChildElement,
- savedSearchesIsLoading, deleteSavedSearchHasErrored,
- deleteSavedSearchIsLoading, deleteSavedSearchSuccess, cloneSavedSearchIsLoading,
- cloneSavedSearchHasErrored, cloneSavedSearchSuccess } = this.props;
+ const { savedSearches, deleteSearch, ChildElement, savedSearchesIsLoading } = this.props;
return (
@@ -78,14 +64,6 @@ SavedSearchesContainer.propTypes = {
savedSearchesIsLoading: PropTypes.bool.isRequired,
setCurrentSavedSearch: PropTypes.func.isRequired,
deleteSearch: PropTypes.func.isRequired,
- deleteSavedSearchIsLoading: PropTypes.bool.isRequired,
- deleteSavedSearchHasErrored: DELETE_SAVED_SEARCH_HAS_ERRORED.isRequired,
- deleteSavedSearchSuccess: DELETE_SAVED_SEARCH_SUCCESS.isRequired,
- routeChangeResetState: PropTypes.func.isRequired,
- cloneSearch: PropTypes.func.isRequired,
- cloneSavedSearchIsLoading: PropTypes.bool.isRequired,
- cloneSavedSearchHasErrored: CLONE_SAVED_SEARCH_HAS_ERRORED.isRequired,
- cloneSavedSearchSuccess: CLONE_SAVED_SEARCH_SUCCESS.isRequired,
ChildElement: PropTypes.func.isRequired,
};
@@ -95,14 +73,6 @@ SavedSearchesContainer.defaultProps = {
savedSearches: POSITION_RESULTS_OBJECT,
savedSearchesIsLoading: true,
savedSearchesHasErrored: false,
- deleteSavedSearchIsLoading: false,
- deleteSavedSearchHasErrored: false,
- deleteSavedSearchSuccess: false,
- routeChangeResetState: EMPTY_FUNCTION,
- cloneSearch: EMPTY_FUNCTION,
- cloneSavedSearchIsLoading: false,
- cloneSavedSearchHasErrored: false,
- cloneSavedSearchSuccess: false,
};
SavedSearchesContainer.contextTypes = {
@@ -115,12 +85,6 @@ const mapStateToProps = (state, ownProps) => ({
savedSearches: state.savedSearchesSuccess,
savedSearchesIsLoading: state.savedSearchesIsLoading,
savedSearchesHasErrored: state.savedSearchesHasErrored,
- deleteSavedSearchIsLoading: state.deleteSavedSearchIsLoading,
- deleteSavedSearchHasErrored: state.deleteSavedSearchHasErrored,
- deleteSavedSearchSuccess: state.deleteSavedSearchSuccess,
- cloneSavedSearchIsLoading: state.cloneSavedSearchIsLoading,
- cloneSavedSearchHasErrored: state.cloneSavedSearchHasErrored,
- cloneSavedSearchSuccess: state.cloneSavedSearchSuccess,
});
export const mapDispatchToProps = dispatch => ({
@@ -128,8 +92,6 @@ export const mapDispatchToProps = dispatch => ({
savedSearchesFetchData: sortType => dispatch(savedSearchesFetchData(sortType)),
setCurrentSavedSearch: e => dispatch(setCurrentSavedSearch(e)),
deleteSearch: id => dispatch(deleteSavedSearch(id)),
- routeChangeResetState: () => dispatch(routeChangeResetState()),
- cloneSearch: id => dispatch(cloneSavedSearch(id)),
});
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(SavedSearchesContainer));
diff --git a/src/Containers/SavedSearchesMap/SavedSearchesMap.jsx b/src/Containers/SavedSearchesMap/SavedSearchesMap.jsx
index 8db151ed8f..71de96efeb 100644
--- a/src/Containers/SavedSearchesMap/SavedSearchesMap.jsx
+++ b/src/Containers/SavedSearchesMap/SavedSearchesMap.jsx
@@ -7,10 +7,6 @@ import { mapSavedSearchesToSingleQuery } from '../../utilities';
import { DEFAULT_USER_PROFILE, POSITION_RESULTS_OBJECT } from '../../Constants/DefaultProps';
import {
SAVED_SEARCH_PARENT_OBJECT,
- DELETE_SAVED_SEARCH_HAS_ERRORED,
- DELETE_SAVED_SEARCH_SUCCESS,
- CLONE_SAVED_SEARCH_HAS_ERRORED,
- CLONE_SAVED_SEARCH_SUCCESS,
EMPTY_FUNCTION,
FILTERS_PARENT,
} from '../../Constants/PropTypes';
@@ -35,7 +31,6 @@ class SavedSearchesMap extends Component {
fetchFilters,
savedSearches,
savedSearchesIsLoading,
- cloneSavedSearchIsLoading,
deleteSavedSearchIsLoading,
} = props;
@@ -44,7 +39,6 @@ class SavedSearchesMap extends Component {
// is anything loading from the parent? if so, don't try to fetch filters
const isLoading = (
savedSearchesIsLoading ||
- cloneSavedSearchIsLoading ||
deleteSavedSearchIsLoading
);
@@ -68,10 +62,8 @@ class SavedSearchesMap extends Component {
}
render() {
- const { savedSearches, deleteSearch, filters, ChildElement, cloneSavedSearch,
- savedSearchesHasErrored, savedSearchesIsLoading, deleteSavedSearchHasErrored,
- deleteSavedSearchIsLoading, deleteSavedSearchSuccess, cloneSavedSearchIsLoading,
- cloneSavedSearchHasErrored, cloneSavedSearchSuccess, goToSavedSearch,
+ const { savedSearches, deleteSearch, filters, ChildElement,
+ savedSearchesHasErrored, savedSearchesIsLoading, goToSavedSearch,
filtersIsLoading, onSortChange } = this.props;
const props = {
savedSearches,
@@ -79,14 +71,7 @@ class SavedSearchesMap extends Component {
filters,
savedSearchesHasErrored,
savedSearchesIsLoading,
- deleteSavedSearchHasErrored,
- deleteSavedSearchIsLoading,
- deleteSavedSearchSuccess,
- cloneSavedSearchIsLoading,
- cloneSavedSearchHasErrored,
- cloneSavedSearchSuccess,
goToSavedSearch,
- cloneSavedSearch,
filtersIsLoading,
onSortChange,
mappedParams: filters.mappedParams || [],
@@ -103,13 +88,6 @@ SavedSearchesMap.propTypes = {
savedSearchesIsLoading: PropTypes.bool.isRequired,
savedSearchesHasErrored: PropTypes.bool.isRequired,
deleteSearch: PropTypes.func.isRequired,
- deleteSavedSearchIsLoading: PropTypes.bool.isRequired,
- deleteSavedSearchHasErrored: DELETE_SAVED_SEARCH_HAS_ERRORED.isRequired,
- deleteSavedSearchSuccess: DELETE_SAVED_SEARCH_SUCCESS.isRequired,
- cloneSavedSearch: PropTypes.func.isRequired,
- cloneSavedSearchIsLoading: PropTypes.bool.isRequired,
- cloneSavedSearchHasErrored: CLONE_SAVED_SEARCH_HAS_ERRORED.isRequired,
- cloneSavedSearchSuccess: CLONE_SAVED_SEARCH_SUCCESS.isRequired,
filters: FILTERS_PARENT,
goToSavedSearch: PropTypes.func.isRequired,
fetchFilters: PropTypes.func.isRequired,
@@ -124,14 +102,7 @@ SavedSearchesMap.defaultProps = {
savedSearches: POSITION_RESULTS_OBJECT,
savedSearchesIsLoading: false,
savedSearchesHasErrored: false,
- deleteSavedSearchIsLoading: false,
- deleteSavedSearchHasErrored: false,
- deleteSavedSearchSuccess: false,
routeChangeResetState: EMPTY_FUNCTION,
- cloneSavedSearch: EMPTY_FUNCTION,
- cloneSavedSearchIsLoading: false,
- cloneSavedSearchHasErrored: false,
- cloneSavedSearchSuccess: false,
filters: { filters: [] },
goToSavedSearch: EMPTY_FUNCTION,
fetchFilters: EMPTY_FUNCTION,
diff --git a/src/Containers/queryParams.js b/src/Containers/queryParams.js
index 7722f6465f..84ea2871fc 100644
--- a/src/Containers/queryParams.js
+++ b/src/Containers/queryParams.js
@@ -1,5 +1,5 @@
import queryString from 'query-string';
-import { toString } from 'lodash';
+import { get } from 'lodash';
// when we need to update an existing query string with properties from an object
function queryParamUpdate(newQueryObject, oldQueryString, returnAsObject = false) {
@@ -16,7 +16,8 @@ function queryParamUpdate(newQueryObject, oldQueryString, returnAsObject = false
const newQuery = Object.assign({}, parsedQuery, newQueryObject);
// remove any params with no value
Object.keys(newQuery).forEach((key) => {
- if (!(toString(newQuery[key]) && toString(newQuery[key]).length)) {
+ const newQuery$ = get(newQuery, key) || '';
+ if (!(newQuery$.toString().length)) {
delete newQuery[key];
}
});
diff --git a/src/__mocks__/userObject.js b/src/__mocks__/userObject.js
index e69b4dab9a..9edae5e24b 100644
--- a/src/__mocks__/userObject.js
+++ b/src/__mocks__/userObject.js
@@ -48,6 +48,14 @@ export const bidderUserObject = {
representation: '[00180000] OMS (DCM) (Addis Ababa, Ethiopia)',
},
],
+ favorite_positions_pv: [
+ {
+ id: 10,
+ },
+ {
+ id: 20,
+ },
+ ],
};
export const cdoUserObject = Object.assign({}, bidderUserObject, { cdo: null, is_cdo: true });
diff --git a/src/actions/favoritePositions.js b/src/actions/favoritePositions.js
index d0c6bd8c33..831e9ebeac 100644
--- a/src/actions/favoritePositions.js
+++ b/src/actions/favoritePositions.js
@@ -1,3 +1,4 @@
+import { get } from 'lodash';
import api from '../api';
export function favoritePositionsHasErrored(bool) {
@@ -25,19 +26,54 @@ export function favoritePositionsFetchData(sortType) {
return (dispatch) => {
dispatch(favoritePositionsIsLoading(true));
dispatch(favoritePositionsHasErrored(false));
+ const data$ = { favorites: [], favoritesPV: [] };
let url = '/position/favorites/';
- if (sortType) { url += `?ordering=${sortType}`; }
+ let urlPV = '/projected_vacancy/favorites/';
+ if (sortType) {
+ const append = `?ordering=${sortType}`;
+ url += append;
+ urlPV += append;
+ }
- api().get(url)
- .then(response => response.data)
- .then((results) => {
- dispatch(favoritePositionsFetchDataSuccess(results));
- dispatch(favoritePositionsHasErrored(false));
- dispatch(favoritePositionsIsLoading(false));
- })
- .catch(() => {
+ const fetchFavorites = () =>
+ api().get(url)
+ .then(({ data }) => data)
+ .catch(error => error);
+
+ const fetchPVFavorites = () =>
+ api().get(urlPV)
+ .then(({ data }) => data)
+ .catch(error => error);
+
+ const queryProms = [fetchFavorites(), fetchPVFavorites()];
+
+ Promise.all(queryProms)
+ .then((results) => {
+ // if any promise returned with errors, return the error
+ let err;
+ results.forEach((result) => {
+ if (result instanceof Error) {
+ err = result;
+ }
+ });
+ if (err) {
dispatch(favoritePositionsHasErrored(true));
dispatch(favoritePositionsIsLoading(false));
- });
+ } else {
+ // object 0 is favorites
+ data$.favorites = get(results, '[0].results', []);
+ data$.results = get(results, '[0].results', []);
+ // object 1 is PV favorites
+ // add PV property
+ data$.favoritesPV = get(results, '[1].results', []).map(m => ({ ...m, isPV: true }));
+ dispatch(favoritePositionsFetchDataSuccess(data$));
+ dispatch(favoritePositionsHasErrored(false));
+ dispatch(favoritePositionsIsLoading(false));
+ }
+ })
+ .catch(() => {
+ dispatch(favoritePositionsHasErrored(true));
+ dispatch(favoritePositionsIsLoading(false));
+ });
};
}
diff --git a/src/actions/glossary.js b/src/actions/glossary.js
index 9d2a426b06..60c1d70655 100644
--- a/src/actions/glossary.js
+++ b/src/actions/glossary.js
@@ -87,7 +87,7 @@ export function glossaryFetchData(bypassLoading = false) {
}
api()
- .get('/glossary/?is_archived=false')
+ .get('/glossary/?is_archived=false&limit=500')
.then(({ data }) => {
dispatch(glossaryFetchDataSuccess(data));
dispatch(glossaryIsLoading(false));
@@ -109,7 +109,7 @@ export function glossaryEditorFetchData(bypassLoading = false) {
}
api()
- .get('/glossary/')
+ .get('/glossary/?limit=500')
.then(({ data }) => {
dispatch(glossaryEditorFetchDataSuccess(data));
dispatch(glossaryEditorIsLoading(false));
diff --git a/src/actions/glossary.test.js b/src/actions/glossary.test.js
index 9efc3c9e83..ddd7b4784c 100644
--- a/src/actions/glossary.test.js
+++ b/src/actions/glossary.test.js
@@ -6,11 +6,11 @@ const { mockStore, mockAdapter } = setupAsyncMocks();
describe('async actions', () => {
beforeEach(() => {
- mockAdapter.onGet('http://localhost:8000/api/v1/glossary/').reply(200,
+ mockAdapter.onGet('http://localhost:8000/api/v1/glossary/?limit=500').reply(200,
{ results: glossaryItems },
);
- mockAdapter.onGet('http://localhost:8000/api/v1/glossary/?is_archived=false').reply(200,
+ mockAdapter.onGet('http://localhost:8000/api/v1/glossary/?is_archived=false&limit=500').reply(200,
{ results: glossaryItems },
);
diff --git a/src/actions/savedSearch.js b/src/actions/savedSearch.js
index 0e94507069..457ff1ae59 100644
--- a/src/actions/savedSearch.js
+++ b/src/actions/savedSearch.js
@@ -1,6 +1,7 @@
import api from '../api';
import * as SystemMessages from '../Constants/SystemMessages';
import { propOrDefault } from '../utilities';
+import { toastSuccess, toastError } from './toast';
export function newSavedSearchHasErrored(bool) {
return {
@@ -141,11 +142,19 @@ export function deleteSavedSearch(id) {
dispatch(deleteSavedSearchIsLoading(false));
dispatch(deleteSavedSearchHasErrored(false));
dispatch(deleteSavedSearchSuccess('Successfully deleted the selected search.'));
+ dispatch(toastSuccess(
+ SystemMessages.DELETE_SAVED_SEARCH_SUCCESS,
+ SystemMessages.DELETE_SAVED_SEARCH_SUCCESS_TITLE,
+ ));
dispatch(currentSavedSearch(false));
dispatch(savedSearchesFetchData());
})
.catch((err) => {
- dispatch(deleteSavedSearchHasErrored(JSON.stringify(err.response.data) || 'An error occurred trying to delete this search.'));
+ dispatch(deleteSavedSearchHasErrored(JSON.stringify(propOrDefault(err, 'response.data', 'An error occurred trying to delete this search.'))));
+ dispatch(toastError(
+ SystemMessages.DELETE_SAVED_SEARCH_ERROR,
+ SystemMessages.DELETE_SAVED_SEARCH_ERROR_TITLE,
+ ));
dispatch(deleteSavedSearchIsLoading(false));
dispatch(deleteSavedSearchSuccess(false));
});
@@ -219,12 +228,27 @@ export function saveSearch(data, id) {
{ title: SystemMessages.NEW_SAVED_SEARCH_SUCCESS_TITLE,
message: SystemMessages.NEW_SAVED_SEARCH_SUCCESS(response.data.name) },
));
+ // eslint-disable-next-line
+ const success = id => id ?
+ dispatch(toastSuccess(
+ SystemMessages.UPDATED_SAVED_SEARCH_SUCCESS(response.data.name),
+ SystemMessages.UPDATED_SAVED_SEARCH_SUCCESS_TITLE,
+ )) :
+ dispatch(toastSuccess(
+ SystemMessages.NEW_SAVED_SEARCH_SUCCESS(response.data.name),
+ SystemMessages.NEW_SAVED_SEARCH_SUCCESS_TITLE,
+ ));
+ success(id);
dispatch(setCurrentSavedSearch(response.data));
})
.catch((err) => {
dispatch(newSavedSearchHasErrored(
{ title: 'Error', message: propOrDefault(err, 'response.data', 'An error occurred trying to save this search.') },
));
+ dispatch(toastError(
+ 'An error occurred trying to save this search.',
+ 'Error',
+ ));
dispatch(newSavedSearchIsSaving(false));
dispatch(newSavedSearchSuccess(false));
});
diff --git a/src/actions/userProfile.js b/src/actions/userProfile.js
index 72271cdff8..60c1d039c4 100644
--- a/src/actions/userProfile.js
+++ b/src/actions/userProfile.js
@@ -1,5 +1,5 @@
import axios from 'axios';
-import { indexOf } from 'lodash';
+import { get, indexOf } from 'lodash';
import api from '../api';
import { favoritePositionsFetchData } from './favoritePositions';
@@ -63,18 +63,22 @@ export function userProfileFetchData(bypass, cb) {
const getUserAccount = () => api().get('/profile/');
// permissions
const getUserPermissions = () => api().get('/permission/user/');
+ // PV favorites
+ const getPVFavorites = () => api().get('/projected_vacancy/favorites/');
// use api' Promise.all to fetch the profile and permissions, and then combine them
// into one object
- axios.all([getUserAccount(), getUserPermissions()])
- .then(axios.spread((acct, perms) => {
+ axios.all([getUserAccount(), getUserPermissions(), getPVFavorites()])
+ .then(axios.spread((acct, perms, pvFavs) => {
// form the userProfile object
const account = acct.data;
const permissions = perms.data;
+ const pvFavorites = get(pvFavs, 'data.results', []).map(m => ({ id: m.id }));
const newProfileObject = {
...account,
is_superuser: indexOf(permissions.groups, 'superuser') > -1,
permission_groups: permissions.groups,
+ favorite_positions_pv: pvFavorites,
};
// then perform dispatches
@@ -104,12 +108,13 @@ export function userProfileFetchData(bypass, cb) {
// Since we have to pass the entire array to the API, we want to make sure it's accurate.
// If we need a full refresh of Favorite Positions, such as for the profile's favorite sub-section,
// we can pass a third arg, refreshFavorites.
-export function userProfileToggleFavoritePosition(id, remove, refreshFavorites = false) {
+export function userProfileToggleFavoritePosition(id, remove, refreshFavorites = false,
+ isPV = false) {
const idString = id.toString();
return (dispatch) => {
const config = {
method: remove ? 'delete' : 'put',
- url: `/position/${idString}/favorite/`,
+ url: isPV ? `/projected_vacancy/${idString}/favorite/` : `/position/${idString}/favorite/`,
};
/**
@@ -119,14 +124,14 @@ export function userProfileToggleFavoritePosition(id, remove, refreshFavorites =
const getAction = () => api()(config);
// position
- const getPosition = () => api().get(`/position/${id}/`);
+ const getPosition = () => api().get(isPV ? `/fsbid/projected_vacancies/position_number__in=${id}/` : `/position/${id}/`);
dispatch(userProfileFavoritePositionIsLoading(true, id));
dispatch(userProfileFavoritePositionHasErrored(false));
axios.all([getAction(), getPosition()])
.then(axios.spread((action, position) => {
- const pos = position.data;
+ const pos = isPV ? get(position, 'data.results[0]', {}) : position.data;
const message = remove ?
SystemMessages.DELETE_FAVORITE_SUCCESS(pos) : SystemMessages.ADD_FAVORITE_SUCCESS(pos);
const title = remove ? SystemMessages.DELETE_FAVORITE_TITLE
diff --git a/src/reducers/favoritePositions/favoritePositions.js b/src/reducers/favoritePositions/favoritePositions.js
index e82f419411..41a792f4df 100644
--- a/src/reducers/favoritePositions/favoritePositions.js
+++ b/src/reducers/favoritePositions/favoritePositions.js
@@ -14,7 +14,7 @@ export function favoritePositionsIsLoading(state = false, action) {
return state;
}
}
-export function favoritePositions(state = { results: [] }, action) {
+export function favoritePositions(state = { favorites: [], favoritesPV: [] }, action) {
switch (action.type) {
case 'FAVORITE_POSITIONS_FETCH_DATA_SUCCESS':
return action.results;
diff --git a/src/sass/_glossaryEditor.scss b/src/sass/_glossaryEditor.scss
index ab9b9e67dc..5b5d3e53cf 100644
--- a/src/sass/_glossaryEditor.scss
+++ b/src/sass/_glossaryEditor.scss
@@ -1,4 +1,8 @@
.glossary-editor-page {
+ h2 {
+ color: initial;
+ }
+
.hello-greeting {
float: left;
}
diff --git a/src/sass/_profile.scss b/src/sass/_profile.scss
index 5a77fe0b44..cff541e174 100644
--- a/src/sass/_profile.scss
+++ b/src/sass/_profile.scss
@@ -577,16 +577,16 @@ $padding-no-border: $total-padding - $border;
.portfolio-top-nav-container {
border-bottom: 1px solid $color-black;
margin-bottom: 30px;
+}
- .is-underlined {
- border-bottom: 8px solid $color-black;
- border-spacing: 6px;
- }
+.is-underlined {
+ border-bottom: 8px solid $color-black;
+ border-spacing: 6px;
+}
- .is-not-underlined {
- border-bottom: 8px solid transparent;
- border-spacing: 6px;
- }
+.is-not-underlined {
+ border-bottom: 8px solid transparent;
+ border-spacing: 6px;
}
.portfolio-total-results {
diff --git a/src/utilities.js b/src/utilities.js
index 7f107e66b7..e9a033c980 100644
--- a/src/utilities.js
+++ b/src/utilities.js
@@ -167,6 +167,24 @@ export const scrollToTop = (config = {}) => {
scroll.scrollToTop({ ...defaultScrollConfig, ...config });
};
+export const scrollToId = ({ el, config = {} }) => {
+ // Get an element's distance from the top of the page
+ const getElemDistance = (elem) => {
+ let location = 0;
+ if (elem.offsetParent) {
+ do {
+ location += elem.offsetTop;
+ elem = elem.offsetParent; // eslint-disable-line
+ } while (elem);
+ }
+ return location >= 0 ? location : 0;
+ };
+ const elem = document.querySelector(el);
+ const location = getElemDistance(elem);
+
+ scrollTo(location, config);
+};
+
// When we want to grab a label, but aren't sure which one exists.
// We set custom ones first in the list.
export const getItemLabel = itemData =>
@@ -547,3 +565,19 @@ export const getScrollDistanceFromBottom = () => {
const bodyHeight = document.body.offsetHeight;
return (Math.max(bodyHeight - (scrollPosition + windowSize), 0));
};
+
+// eslint-disable-next-line no-confusing-arrow
+export const getFormattedNumCSV = (v) => {
+ if (v === null || v === undefined) {
+ return '';
+ }
+ // else
+ return !isNaN(v) ? `=${v}` : v;
+};
+
+export const spliceStringForCSV = (v) => {
+ if (v[1] === '=') {
+ return `=${v.slice(0, 1)}${v.slice(2)}`;
+ }
+ return v;
+};
diff --git a/yarn.lock b/yarn.lock
index c809bcfada..188ba57f8d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8550,11 +8550,6 @@ react-autowhatever@^10.1.0:
react-themeable "^1.1.0"
section-iterator "^2.0.0"
-react-csv@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/react-csv/-/react-csv-1.1.1.tgz#718dadd2607a1795bfc8dbd32ca282a043739634"
- integrity sha512-eQSrkEfzPN+1IeXT58kJaDNmpFr3RhdjfE09jTrw+jdTEp0g0Ig9NOI+TpvMBbCukE1HQhOf4dyrLDRFKgZzbA==
-
react-dev-utils@^3.1.0, react-dev-utils@^3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/react-dev-utils/-/react-dev-utils-3.1.1.tgz#09ae7209a81384248db56547e718e65bd3b20eb5"