diff --git a/README.md b/README.md
index dd4032b..4ec0844 100644
--- a/README.md
+++ b/README.md
@@ -28,24 +28,87 @@ The integration tests just mock any API interactions, so to make sure the app re
## How it Works
-### Component Architecture
+### Changing data
+
+The [src/data](./src/data) directory holds static data the app uses, like names of Government agencies, API urls, and information about each query field. It's possible to change this data without touching any other code.
+
+Data is stored as json or other javascript data types.
+
+#### Fields
+
+Available API Fields are stored in [fields.js](./src/data/fields.js) as a json object of objects, keyed by field attribute. The value of each field is an object with the following keys:
+
+| Key | Data Type | Default | Required | Usage |
+|------|-----------|---------|-------|----------|
+| live | boolean | false | Yes | fields are hidden in the app unless live is `true` |
+| attribute | string | n/a | Yes | url-friendly, machine-readable name of the field that is used for the url query parameter. Attribute should correspond to the field object's key |
+| title | string | n/a | Yes | Display name of the field |
+| order | number | n/a | Yes | Available fields are ordered (ascending) by this number. It's possible to use a float or negative number to re-order fields. |
+| input | string, one of: `"text"`, `"select"` | n/a | Yes | auto-generates the input field in the app. Type `"select"` requires `input_options` |
+| input_options | array of objects (see below) | n/a | Yes when using input type `"select"`; otherwise No | provides the options for a field's select input |
+| category | string | n/a | No | The field category, should fields need to be grouped |
-Each report is a separate page under `/src/pages`. The page contains the basic configuration for which columns a report will pull in from the API. The setup for these is straightforward: Each column gets a human readable name (`title`), and a function to pluck out the relevant value from the data returned from the API (`accessor`—many of these use the new [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) syntax, which is just something to be aware of).
+#### Field input options
-The pages wrap each ` ` in a ` `. This component uses React [Context](https://reactjs.org/docs/context.html) to make the reducer used to manage queries and filters available to child components.
+for any field with input type `"select"`, the value of `input_options` must be an array of objects with the following keys:
-The ` ` component performs the main query to the API and renders the data into a table.
+| Key | Data Type | Required | Usage |
+|-----|-----------|----------|-------|
+| label | string | Yes | Display name of the option |
+| value | string, number, or boolean | Yes | the value of the option |
-` ` renders the appropriate filters for each report and, when those filter values change, passes the request back up to the report. It also makes a couple of API calls to populate the agency and scan data filters.
+#### Agency / Bureau Data
-` ` is pretty straightforward, and sends page navigation requests back up to the ` ` (via the ` `).
+[agency_bureau_data.js](./src/data/agency_bureau_data.js) is an array of objects with the following keys:
+
+| Key | Data Type | Required | Usage |
+|-----|-----------|----------|-------|
+| Agency Name | string | Yes | Display name of agency |
+| Bureau Name | string | Yes | Display name of bureau |
+| Agency Code | string | No | Code of agency |
+| Bureau Code | string | No | Code of bureau |
+| Treasury Code | string | No | Code of treasury |
+| CGAC Code | string | No | unique code for each agency |
+
+#### API
+
+[api.js](./src/data/api.js) holds any info related to the API, like the current endpoint, pathname, or demo key.
+
+#### Links
+
+[links.js](./src/data/links.js) is where any other external links can be configured.
+
+### Component Architecture
+
+The entry point for the app is [` `](./src/components/modules/query-builder.js).
+
+The QueryBuilder component contains the major modules of the app:
+- ` ` generates the list of API fields able to be filtered by the user
+- ` ` displays the filters the user has selected
+- ` ` contains any user affordances, like copying the API link
+- ` ` contains the instructional text
Spotlight UI uses fairly new React features, so here are some links to relevant documentation:
- [Basic hooks (`useState`, and `useEffect`)](https://reactjs.org/docs/hooks-reference.html#basic-hooks)
-- [`useReducer`](https://reactjs.org/docs/hooks-reference.html#usereducer)
- [Context](https://reactjs.org/docs/context.html)
+### State Management
+
+The app uses [redux](https://redux.js.org/) for global state management and [react-redux](https://react-redux.js.org/) to connect components to the global state.
+
+Currently there's only one Reducer being used in the app, managing the user's field selections.
+
+The Redux store is created in [src/redux/index.js](./src/redux/index.js) and used in ` `.
+
+Individual Reducers conform to the [ducks pattern](https://github.com/erikras/ducks-modular-redux) and are found under [src/redux/ducks](./src/redux/ducks).
+
+This app uses a Redux middleware to sync the app url to field selections. This allows a user to share their field selections by sharing the app url. It also allows the user to maintain where they left off on a page reload. Middleware can be found under [src/redux/middleware](./src/redux/middleware).
+
+If state is relevant to a component only, then it's managed through React's `useState` and `useEffect` hooks.
+
+Reducers are tested using Jest.
+
### Building and deploying
Spotlight UI is configured to deploy to Federalist. Builds are validated by CircleCI.
diff --git a/cypress/e2e/homepage.test.js b/cypress/e2e/homepage.test.js
new file mode 100644
index 0000000..29e7ef1
--- /dev/null
+++ b/cypress/e2e/homepage.test.js
@@ -0,0 +1,76 @@
+import FIELD_OPTIONS from '../../src/data/fields';
+import { API_DOMAIN } from '../../src/data/api';
+
+const testField = Object.values(FIELD_OPTIONS).filter(field => field.live && field.input === 'text')[0];
+
+describe('Homepage', () => {
+ describe('default state', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:8000/');
+ });
+ it('disables copy url button', () => {
+ cy.get('button[title="copy url"]').eq(0).should('be.disabled');
+ });
+ it('shows all live text input filters', () => {
+ const liveInputCount = Object.values(FIELD_OPTIONS).filter(field => field.live && field.input === 'text').length;
+
+ cy.get('input.usa-input').should('have.length', liveInputCount);
+ });
+ it('shows all live select filters', () => {
+ const liveSelectCount = Object.values(FIELD_OPTIONS).filter(field => field.live && field.input === 'select').length;
+
+ cy.get('select.usa-select').should('have.length', liveSelectCount);
+ });
+ });
+ describe('when user sets a filter', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:8000/');
+ cy.get(`input[name="${testField.attribute}"]`).eq(0).type('foo');
+ });
+ it('displays api url', () => {
+ cy.get('div#api-url-text').contains(API_DOMAIN);
+ });
+ it('enables copy url button', () => {
+ cy.get('button[title="copy url"]').eq(0).should('not.be.disabled');
+ });
+ it('displays removeable filter selection', () => {
+ cy.get(`button[title="Remove ${testField.title} filter"]`).should('have.length', 1);
+ });
+ it('displays clear all selections button', () => {
+ cy.get('button[title="clear all selections"]').should('have.length', 1);
+ });
+ });
+ describe('when user clicks copy url', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:8000/');
+ cy.get(`input[name="${testField.attribute}"]`).eq(0).type('foo');
+ cy.get('button[title="copy url"]').eq(0).click();
+ });
+ it('shows success message', () => {
+ cy.get('span[role="alert"]').contains('Copied!').should('have.length', 1);
+ });
+ it('removes success message if query changes afterward', () => {
+ cy.get(`button[title="Remove ${testField.title} filter"]`).eq(0).click();
+ cy.get('span[role="alert"]').should('have.length', 0);
+ });
+ });
+ describe('when user removes a filter', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:8000/');
+ cy.get(`input[name="${testField.attribute}"]`).eq(0).type('foo');
+ cy.get(`button[title="Remove ${testField.title} filter"]`).eq(0).click();
+ });
+ it('removes api url', () => {
+ cy.get('div#api-url-text').should('have.length', 0);
+ });
+ it('disables copy url button', () => {
+ cy.get('button[title="copy url"]').eq(0).should('be.disabled');
+ });
+ it('removes filter selection', () => {
+ cy.get(`button[title="Remove ${testField.title} filter"]`).should('have.length', 0);
+ });
+ it('removes clear all selections button', () => {
+ cy.get('button[title="clear all selections"]').should('have.length', 0);
+ });
+ });
+});
diff --git a/cypress/e2e/navigation.test.js b/cypress/e2e/navigation.test.js
deleted file mode 100644
index 32a93f2..0000000
--- a/cypress/e2e/navigation.test.js
+++ /dev/null
@@ -1,44 +0,0 @@
-/* eslint-plugin-disable jest */
-describe('Spotlight', () => {
- it('loads the homepage', () => {
- cy.visit('http://localhost:8000/');
- cy.findByTestId('site-title');
- });
-
- describe('loads each report from the navigation', () => {
- const reports = [
- { text: 'Design', path: 'design' },
- { text: 'Security', path: 'security' },
- { text: 'Accessibility', path: 'accessibility' },
- { text: 'Performance', path: 'performance' },
- { text: 'Third-Party Links', path: 'critical-components' },
- ];
-
- reports.map(r => {
- context(r.text, () => {
- it(`loads successfully`, () => {
- cy.visit('http://localhost:8000/');
- cy.get('.usa-menu-btn').click();
- cy.get('.usa-accordion__button.usa-nav__link').click();
- cy.get('.usa-nav__submenu').contains(r.text).click();
- cy.url().should('include', r.path);
- });
-
- it('does not have an error alert', () => {
- cy.findByTestId('alert-error').should('not.exist');
- });
-
- it('filters domains based on user input', () => {
- cy.findByTestId('report-table');
- cy.get('input#domain').type('18f.gsa');
- cy.findByTestId('report-table').find('tr').should('have.length', 1);
- });
-
- it('shows an informative alert when no results are available', () => {
- cy.get('select#agency').select('Broadcasting Board of Governors');
- cy.findByTestId('alert-info');
- });
- });
- });
- });
-});
diff --git a/package-lock.json b/package-lock.json
index 21e9a05..8064455 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11682,6 +11682,12 @@
"har-schema": "^2.0.0"
}
},
+ "harmony-reflect": {
+ "version": "1.6.1",
+ "resolved": "https://registry.npmjs.org/harmony-reflect/-/harmony-reflect-1.6.1.tgz",
+ "integrity": "sha512-WJTeyp0JzGtHcuMsi7rw2VwtkvLa+JyfEKJCFyfcS0+CDkjQ5lHPu7zEhFZP+PDSRrEgXa5Ah0l1MbgbE41XjA==",
+ "dev": true
+ },
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@@ -12358,6 +12364,15 @@
"resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-3.2.0.tgz",
"integrity": "sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ=="
},
+ "identity-obj-proxy": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz",
+ "integrity": "sha1-lNK9qWCERT7zb7xarsN+D3nx/BQ=",
+ "dev": true,
+ "requires": {
+ "harmony-reflect": "^1.4.6"
+ }
+ },
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
@@ -19005,9 +19020,9 @@
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ=="
},
"query-string": {
- "version": "6.12.1",
- "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.12.1.tgz",
- "integrity": "sha512-OHj+zzfRMyj3rmo/6G8a5Ifvw3AleL/EbcHMD27YA31Q+cO5lfmQxECkImuNVjcskLcvBRVHNAB3w6udMs1eAA==",
+ "version": "6.13.6",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-6.13.6.tgz",
+ "integrity": "sha512-/WWZ7d9na6s2wMEGdVCVgKWE9Rt7nYyNIf7k8xmHXcesPMlEzicWo3lbYwHyA4wBktI2KrXxxZeACLbE84hvSQ==",
"requires": {
"decode-uri-component": "^0.2.0",
"split-on-first": "^1.0.0",
@@ -19462,6 +19477,28 @@
"scheduler": "^0.18.0"
}
},
+ "react-redux": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.2.tgz",
+ "integrity": "sha512-8+CQ1EvIVFkYL/vu6Olo7JFLWop1qRUeb46sGtIMDCSpgwPQq8fPLpirIB0iTqFe9XYEFPHssdX8/UwN6pAkEA==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "hoist-non-react-statics": "^3.3.2",
+ "loose-envify": "^1.4.0",
+ "prop-types": "^15.7.2",
+ "react-is": "^16.13.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.12.1",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz",
+ "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==",
+ "requires": {
+ "regenerator-runtime": "^0.13.4"
+ }
+ }
+ }
+ },
"react-refresh": {
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.7.2.tgz",
@@ -23060,6 +23097,11 @@
"prepend-http": "^2.0.0"
}
},
+ "url-search-params-polyfill": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/url-search-params-polyfill/-/url-search-params-polyfill-8.1.0.tgz",
+ "integrity": "sha512-MRG3vzXyG20BJ2fox50/9ZRoe+2h3RM7DIudVD2u/GY9MtayO1Dkrna76IUOak+uoUPVWbyR0pHCzxctP/eDYQ=="
+ },
"url-to-options": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz",
diff --git a/package.json b/package.json
index d30e7bf..60ccf6e 100644
--- a/package.json
+++ b/package.json
@@ -25,6 +25,9 @@
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-helmet": "^5.2.1",
+ "react-redux": "^7.2.2",
+ "redux": "^4.0.5",
+ "url-search-params-polyfill": "^8.1.0",
"uswds": "^2.7.0",
"uuid": "^7.0.3"
},
@@ -42,6 +45,7 @@
"eslint-plugin-jest": "^23.10.0",
"eslint-plugin-react": "^7.17.0",
"husky": "^4.2.5",
+ "identity-obj-proxy": "^3.0.0",
"jest": "^25.5.4",
"node-fetch": "^2.6.0",
"prettier": "^2.0.5",
diff --git a/src/components/__tests__/pagination.spec.js b/src/components/__tests__/pagination.spec.js
deleted file mode 100644
index dfc6e9b..0000000
--- a/src/components/__tests__/pagination.spec.js
+++ /dev/null
@@ -1,48 +0,0 @@
-import React from 'react';
-import { render, fireEvent } from '@testing-library/react';
-import Pagination from '../pagination';
-import '@testing-library/jest-dom';
-
-describe('Pagination', () => {
- it('renders previous and next page buttons', () => {
- const { getByText } = render( );
- expect(getByText('Prev')).toBeInTheDocument();
- expect(getByText('Next')).toBeInTheDocument();
- });
-
- it('disables "Prev" and "1" buttons on the first page', () => {
- const { getByText, getAllByText } = render(
-
- );
- expect(getByText('Prev').closest('span')).toHaveClass('disabled');
- expect(getAllByText('1')[1].closest('span')).toHaveAttribute(
- 'aria-current'
- );
- });
-
- it.skip('disables "Next" and "5" buttons on the last page', () => {
- // FIXME:
- // Not sure how to set this now that the current page state lives
- // inside the component rather than coming in as a prop 🤔
-
- const { getByText } = render( );
- const btn5 = getByText('5');
-
- fireEvent.click(btn5);
-
- expect(getByText('Next').closest('button')).toHaveAttribute('disabled');
- expect(btn5.closest('button')).toHaveAttribute('disabled');
- });
-
- it('renders links to intermediate pages of records', () => {
- const { getAllByText } = render( );
- expect(getAllByText('1')[1]).toBeInTheDocument();
- expect(getAllByText('5')[0]).toBeInTheDocument();
- });
-
- it('renders links to skip to the beginning & end of the list', () => {
- const { getAllByText } = render( );
- expect(getAllByText('1')[0].closest('li')).toHaveClass('firstPage');
- expect(getAllByText('5')[1].closest('li')).toHaveClass('lastPage');
- });
-});
diff --git a/src/components/__tests__/report.spec.js b/src/components/__tests__/report.spec.js
deleted file mode 100644
index be35e66..0000000
--- a/src/components/__tests__/report.spec.js
+++ /dev/null
@@ -1,267 +0,0 @@
-import React from 'react';
-import {
- render,
- act,
- fireEvent,
- waitFor,
- cleanup,
-} from '@testing-library/react';
-import '@testing-library/jest-dom';
-import axiosMock from 'axios';
-import Report from '../report';
-import ReportQueryProvider from '../report-query-provider';
-import { API_BASE_URL } from '../../constants';
-
-jest.mock('axios');
-
-const columns = [
- { title: `Domain`, accessor: obj => obj.domain },
- { title: `Agency`, accessor: obj => obj.agency },
- { title: `Supports HSTS`, accessor: obj => obj.data.HSTS },
- { title: `Supports HTTPS`, accessor: obj => obj.data['HTTPS Live'] },
- { title: `Headers`, accessor: obj => obj.data.endpoints?.https?.headers },
-];
-
-const dateUrl = `${API_BASE_URL}lists/dates/`;
-const agencyUrl = `${API_BASE_URL}lists/pshtt/agencies`;
-const reportUrl = `${API_BASE_URL}scans/pshtt/?page=1`;
-const csvUrl = `${API_BASE_URL}scans/pshtt/csv/`;
-
-const renderReport = () => {
- const utils = render(
-
-
-
- );
- return utils;
-};
-
-const mockFn = (url, respObj) => {
- switch (url) {
- case dateUrl:
- return { data: ['2020-04-20', '2020-04-21'] };
- case agencyUrl:
- return { data: ['AMTRAK', 'Consumer Financial Protection Bureau'] };
- case reportUrl:
- return respObj ? { data: respObj } : '';
- default: {
- return { data: { ...respObj, filtered: 'YES!' } };
- }
- }
-};
-
-describe('A ', () => {
- afterEach(() => {
- cleanup;
- jest.clearAllMocks();
- });
-
- describe('that loads correctly', () => {
- const respObj = {
- count: 4914,
- results: [
- {
- domain: '1.usa.gov',
- scantype: 'pshtt',
- domaintype: '',
- organization: '',
- agency: 'General Services Administration',
- data: {
- HSTS: true,
- 'HTTPS Live': true,
- endpoints: {
- https: {
- headers: { Connection: 'keep-alive' },
- },
- },
- },
- },
- ],
- };
-
- axiosMock.get.mockImplementation(url => mockFn(url, respObj));
-
- it('displays a loading indicator', () => {
- const utils = renderReport();
- waitFor(() => {
- expect(utils.getByTestId('loading-table')).toBeInTheDocument();
- });
- });
-
- it('filters data based on user input', async () => {
- let utils;
-
- await act(async () => {
- utils = renderReport();
- });
-
- await waitFor(() => {
- expect(axiosMock.get).toHaveBeenCalledTimes(3);
- expect(axiosMock.get).toHaveBeenCalledWith(dateUrl);
- expect(axiosMock.get).toHaveBeenCalledWith(agencyUrl);
- expect(axiosMock.get).toHaveBeenCalledWith(reportUrl);
- });
-
- const domainFilter = utils.getByTestId('domain-filter');
-
- fireEvent.change(domainFilter, {
- target: { value: '18f' },
- });
-
- await waitFor(() => {
- expect(axiosMock.get).toHaveBeenLastCalledWith(
- `${reportUrl}&domain=18f*`
- );
- });
-
- const filterUrl = `${reportUrl}&domain=18f*&agency="Consumer+Financial+Protection+Bureau"`;
- const agencyFilter = utils.getByTestId('agency-filter');
-
- // It applies a filter when an agency is selected
- fireEvent.change(agencyFilter, {
- target: { value: 'Consumer Financial Protection Bureau' },
- });
-
- await waitFor(() => {
- expect(axiosMock.get).toHaveBeenLastCalledWith(filterUrl);
- });
-
- // It sets the CSV download link to the filter string
- expect(
- utils.getByText('Download these results as a CSV').closest('a')
- ).toHaveAttribute(
- 'href',
- `${csvUrl}?domain=18f*&agency="Consumer+Financial+Protection+Bureau"`
- );
-
- // It removes a filter when the agency is deselected
- fireEvent.change(agencyFilter, {
- target: { value: ' ' },
- });
-
- await waitFor(() => {
- expect(axiosMock.get).toHaveBeenLastCalledWith(
- expect.stringMatching(/&domain=18f*/)
- );
- });
-
- //It changes the scan date when the fileter value changes
- const scanDateFilter = utils.getByTestId('scan-date-filter');
-
- fireEvent.change(scanDateFilter, {
- target: { value: '2020-04-20' },
- });
-
- await waitFor(() => {
- expect(axiosMock.get).toHaveBeenLastCalledWith(
- expect.stringMatching(/2020-04-20/)
- );
- });
- });
-
- it('updates the page when a pagination link is clicked', async () => {
- const utils = renderReport();
-
- await waitFor(() => {
- const pageOneSpan = utils.getByTestId('page-span-1');
- const pageTwoLink = utils.getByTestId('page-2');
- expect(pageOneSpan).toHaveAttribute('aria-current', 'true');
- expect(pageTwoLink).toHaveAttribute('aria-current', 'false');
-
- fireEvent.click(pageTwoLink);
-
- const pageTwoSpan = utils.getByTestId('page-span-2');
- expect(pageTwoSpan).toHaveAttribute('aria-current', 'true');
- });
- });
-
- it('renders domains as clickable links', async () => {
- const utils = renderReport();
- await waitFor(() => {
- expect(utils.getByText('1.usa.gov').closest('a')).toHaveAttribute(
- 'href',
- 'http://1.usa.gov'
- );
- });
- });
-
- it('displays a record count', async () => {
- const utils = renderReport();
- await waitFor(() => {
- expect(utils.getByText('4914 Results')).toBeInTheDocument();
- });
- });
- });
-
- describe('when loading records from failed scans', () => {
- const invalidObj = {
- count: 1,
- next: null,
- previous: null,
- results: [
- {
- domain: 'ag.gov',
- scantype: 'lighthouse',
- domaintype: 'Federal Agency - Executive',
- organization: 'USDA',
- agency: 'U.S. Department of Agriculture',
- data: {
- invalid: true,
- },
- scan_data_url:
- 'https://s3-us-gov-west-1.amazonaws.com/cg-852a6196-0fdb-4a01-a16f-6c24379722cb/lighthouse/ag.gov.json',
- lastmodified: '2020-05-21T01:58:20Z',
- },
- ],
- };
-
- beforeEach(async () => {
- axiosMock.get.mockImplementation(url => mockFn(url, invalidObj));
- });
-
- afterEach(() => {
- cleanup;
- jest.clearAllMocks();
- });
-
- it('indicates the data are invalid', async () => {
- const utils = renderReport();
- await waitFor(() => {
- expect(utils.getByText(/ag.gov/i).closest('tr')).toHaveClass('invalid');
- });
- });
- });
-
- describe('that fails to load data from the API', () => {
- beforeEach(async () => {
- axiosMock.get.mockImplementation(url => mockFn(url));
- });
-
- afterEach(() => {
- cleanup;
- jest.clearAllMocks();
- });
-
- it('loads without crashing', async () => {
- const utils = renderReport();
-
- await waitFor(() => {
- expect(utils.queryByTestId('loading-table')).not.toBeInTheDocument();
- expect(utils.getByTestId('filter-form')).toBeInTheDocument();
- });
- });
-
- it('displays an error message', async () => {
- const utils = renderReport();
-
- await waitFor(() => {
- expect(utils.getByTestId('alert-error')).toBeInTheDocument();
- expect(utils.queryByTestId('alert-info')).not.toBeInTheDocument();
- });
- });
- });
-});
diff --git a/src/components/available-field.js b/src/components/available-field.js
new file mode 100644
index 0000000..7f1f7cb
--- /dev/null
+++ b/src/components/available-field.js
@@ -0,0 +1,68 @@
+import React from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+import * as propTypes from '../prop-types';
+import { connect } from 'react-redux';
+import Checkbox from './uswds/checkbox';
+import TextInput from './uswds/text-input';
+import Dropdown from './uswds/dropdown';
+import { faFilter } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+export const AvailableField = (props) => {
+ const { attribute, title, input, input_options } = props.field;
+ const { value } = props;
+ return (
+
+ { input &&
+
+
+ { title }
+
+ { input === 'text' &&
+
+ }
+ { input === 'select' &&
+
+ }
+
+ }
+
+ );
+};
+
+AvailableField.propTypes = {
+ field: propTypes.AvailableFieldPropTypes.isRequired,
+ onFieldChange: PropTypes.func.isRequired,
+ value: PropTypes.any,
+};
+
+const mapStateToProps = (state, ownProps) => ({
+ value: state.selectedFields[ownProps.field.attribute] ? state.selectedFields[ownProps.field.attribute].value : '',
+});
+
+const areStatesEqual = (prev, next) => (
+ prev.selectedFields === next.selectedFields
+);
+
+export default connect(
+ mapStateToProps,
+ null,
+ null,
+ { areStatesEqual },
+)(AvailableField);
diff --git a/src/components/available-field.test.js b/src/components/available-field.test.js
new file mode 100644
index 0000000..5ccb958
--- /dev/null
+++ b/src/components/available-field.test.js
@@ -0,0 +1,92 @@
+import React from 'react';
+import '@testing-library/jest-dom';
+import { render, fireEvent, screen } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { AvailableField } from './available-field.js';
+import FIELD_OPTIONS from '../data/fields';
+
+describe(' ', function() {
+ describe('when field has input type text', function() {
+
+ test('renders a text input', async () => {
+ render( );
+ const input = await screen.getByLabelText(/Target Url/i, {
+ selector: 'input'
+ });
+ expect(input).toBeInTheDocument();
+ });
+ });
+ describe('when field has input type select', function() {
+
+ test('renders a select menu', async () => {
+ render( );
+ const input = await screen.getByLabelText(/Final Url is Live/i, {
+ selector: 'select'
+ });
+ expect(input).toBeInTheDocument();
+ });
+ });
+ describe('when passed a value', function() {
+
+ test('sets input value for text input', async () => {
+ render( );
+ const input = await screen.getByLabelText(/Target Url/i, {
+ selector: 'input'
+ });
+ expect(input.value).toEqual('foo');
+ });
+ test('sets input value for select input', async () => {
+ render( );
+ const input = await screen.getByLabelText(/Final Url is Live/i, {
+ selector: 'select'
+ });
+ expect(input.value).toEqual('true');
+ });
+ });
+ describe('when input value changes', function() {
+ test('calls onFieldChange prop for text input', async () => {
+ const mockFn = jest.fn();
+ render( );
+ const input = await screen.getByLabelText(/Target Url/i, {
+ selector: 'input'
+ });
+ fireEvent.change(input, { target: { value: 'bar' } })
+
+ expect(mockFn.mock.calls.length).toBe(1);
+ });
+ test('calls onFieldChange prop for select input', async () => {
+ const mockFn = jest.fn();
+ render( );
+ const input = await screen.getByLabelText(/Final Url is Live/i, {
+ selector: 'select'
+ });
+ fireEvent.change(input, { target: { value: 'false' } })
+
+ expect(mockFn.mock.calls.length).toBe(1);
+ });
+ });
+});
diff --git a/src/components/design-report.js b/src/components/design-report.js
deleted file mode 100644
index 6a99994..0000000
--- a/src/components/design-report.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import PropTypes from 'prop-types';
-import React from 'react';
-import useFetch from '../hooks/useFetch';
-
-const Report = ({ reportType }) => {
- const columns = [{ title: `Domain Enforces HTTPS` }, { title: `HSTS` }];
-
- return (
- <>
- {reportType}
-
-
-
-
- >
- );
-};
-
-Report.propTypes = {
- reportType: PropTypes.string,
-};
-
-Report.defaultProps = {
- reportType: `Security`,
-};
-
-export default Report;
-
-const ReportTable = ({ children }) => (
-
-);
-
-const ReportTableHead = ({ columns }) => {
- return (
-
-
- {columns.map(c => (
-
- {c.title}
-
- ))}
-
-
- );
-};
-
-const ReportTableBody = ({ data }) => {
- return ;
-};
diff --git a/src/components/footer.js b/src/components/footer.js
deleted file mode 100644
index bfd915c..0000000
--- a/src/components/footer.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import React from 'react';
-import { Link } from 'gatsby';
-import gsaLogo from '../images/gsa-logo-w100.png';
-import { faEnvelope } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-
-const Footer = () => {
- const toggleOrgDetails = e => {
- const btn = e.target;
- const orgDetails = document.getElementById('org-details');
- orgDetails.hidden = !orgDetails.hidden;
- const ariaExpanded =
- btn.getAttribute('aria-expanded') == 'true' ? 'false' : 'true';
- btn.setAttribute('aria-expanded', ariaExpanded);
- };
-
- return (
-
-
-
-
-
-
-
-
-
- About Site Scanner
-
-
-
-
- Contact Us
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- toggleOrgDetails(e)}
- >
- Learn More
-
-
-
-
-
-
-
-
- );
-};
-
-export default Footer;
diff --git a/src/components/header.js b/src/components/header.js
deleted file mode 100644
index cd4b146..0000000
--- a/src/components/header.js
+++ /dev/null
@@ -1,92 +0,0 @@
-import { Link } from 'gatsby';
-import Banner from './uswds/banner';
-import PropTypes from 'prop-types';
-import React from 'react';
-import close from '../../node_modules/uswds/dist/img/close.svg';
-
-const Header = ({ siteTitle }) => (
- <>
-
-
-
-
-
-
-
-
- {siteTitle}
-
-
-
-
Menu
-
-
-
-
-
-
-
-
- Reports
-
-
-
- Design
-
-
- Security
-
-
- Accessibility
-
-
- Analytics
-
-
- Performance
-
-
- Third-Party Links
-
-
-
-
-
- About
-
-
-
-
- Contact Us
-
-
-
-
-
-
- >
-);
-
-Header.propTypes = {
- siteTitle: PropTypes.string,
-};
-
-Header.defaultProps = {
- siteTitle: ``,
-};
-
-export default Header;
diff --git a/src/components/layout.js b/src/components/layout.js
index 2f3d51a..1dc28fc 100644
--- a/src/components/layout.js
+++ b/src/components/layout.js
@@ -1,37 +1,12 @@
-/**
- * Layout component that queries for data
- * with Gatsby's useStaticQuery component
- *
- * See: https://www.gatsbyjs.org/docs/use-static-query/
- */
-
-import React from 'react';
+import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
-import Footer from './footer';
import { useStaticQuery, graphql } from 'gatsby';
-import Header from './header';
-
const Layout = ({ children, hero }) => {
- const data = useStaticQuery(graphql`
- query SiteTitleQuery {
- site {
- siteMetadata {
- title
- }
- }
- }
- `);
-
return (
- <>
-
- {hero}
-
+
{children}
-
-
- >
+
);
};
diff --git a/src/components/modules/available-fields.js b/src/components/modules/available-fields.js
new file mode 100644
index 0000000..27af59b
--- /dev/null
+++ b/src/components/modules/available-fields.js
@@ -0,0 +1,90 @@
+import React, { Fragment, useEffect } from 'react'; // eslint-disable-line
+import * as propTypes from '../../prop-types';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { groupBy, orderBy, sortBy } from 'lodash';
+import FIELD_OPTIONS from '../../data/fields';
+import Accordion from '../uswds/accordion';
+import Checkbox from '../uswds/checkbox';
+import AvailableField from '../available-field';
+import {
+ selectField, unselectField, setFieldValue,
+} from '../../redux/ducks/selectedFields';
+import FIELD_CATEGORY_ORDER from '../../data/field-category-order';
+import { TERMS_LINK } from '../../data/links';
+
+export const AvailableFields = (props) => {
+ const availableGroups = selectedFields => groupBy(selectedFields, 'category');
+ const groups = availableGroups(props.availableFields);
+ const sortedGroupKeys = sortBy(Object.keys(groups), key => FIELD_CATEGORY_ORDER[key]);
+ const sanitizeField = field => ({
+ ...field,
+ input_options: undefined,
+ })
+ const handleOnFieldChange = (field, e) => {
+ props.actions.setFieldValue({
+ ...sanitizeField(field),
+ value: e.target.value.trim(),
+ });
+ !e.target.value.trim().length && props.actions.unselectField(field);
+ }
+ const groupAllChecked = (groupName) => {
+ const filterByGroup = field => field.category === groupName;
+ return Object.values(props.selectedFields).filter(filterByGroup).length ===
+ Object.values(props.availableFields).filter(filterByGroup).length;
+ }
+ return (
+
+
+ { sortedGroupKeys.map(key => {
+ return orderBy(groups[key], ['order'], ['asc']).map(field => (
+
handleOnFieldChange(field, e) }
+ />
+ ))
+ })}
+
+ );
+};
+
+AvailableFields.propTypes = {
+ availableFields: propTypes.AvailableFieldsPropTypes.isRequired,
+ selectedFields: propTypes.SelectedFieldsPropTypes.isRequired,
+};
+
+AvailableFields.defaultProps = {
+ availableFields: Object.values(FIELD_OPTIONS).filter(field => field.live),
+}
+
+const mapStateToProps = (state) => ({
+ selectedFields: state.selectedFields,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ actions: bindActionCreators({
+ selectField,
+ unselectField,
+ setFieldValue,
+ }, dispatch)
+});
+
+const areStatesEqual = (prev, next) => (
+ prev.selectedFields === next.selectedFields
+);
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+ null,
+ { areStatesEqual },
+)(AvailableFields);
diff --git a/src/components/modules/available-fields.test.js b/src/components/modules/available-fields.test.js
new file mode 100644
index 0000000..9253039
--- /dev/null
+++ b/src/components/modules/available-fields.test.js
@@ -0,0 +1,24 @@
+import React from 'react';
+import '@testing-library/jest-dom';
+import { render, fireEvent, screen } from '../../test-utils';
+import { act } from 'react-dom/test-utils';
+import { orderBy } from 'lodash';
+import { AvailableFields } from './available-fields';
+import FIELD_OPTIONS from '../../data/fields';
+
+describe(' ', function() {
+ test('fields are ordered according to specified order', async () => {
+ const { container } = render( );
+
+ const expectedLabels = container.querySelectorAll('label');
+
+ const fieldTitlesOrdered = orderBy(
+ Object.values(FIELD_OPTIONS).filter(field => field.live),
+ ['order'], ['asc']
+ ).map(field => field.title);
+
+ expectedLabels.forEach(function(label, index) {
+ expect(label.innerHTML).toMatch(fieldTitlesOrdered[index]);
+ });
+ });
+});
diff --git a/src/components/modules/builder-actions.js b/src/components/modules/builder-actions.js
new file mode 100644
index 0000000..7695713
--- /dev/null
+++ b/src/components/modules/builder-actions.js
@@ -0,0 +1,125 @@
+import React, { Fragment, useState, useEffect } from 'react'; //eslint-disable-line
+import PropTypes from 'prop-types';
+import * as propTypes from '../../prop-types';
+import { connect } from 'react-redux';
+import { buildApiUrl, deepPluck } from '../../utils';
+import { faEnvelope, faShareAlt } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import * as LINKS from '../../data/links';
+
+const styles = {
+ url: {
+ width: '100%',
+ fontWeight: 'bold',
+ color: '#000',
+ padding: '1rem',
+ marginTop: '1rem',
+ marginBottom: '1rem',
+ wordBreak: 'break-all',
+ },
+}
+
+export const BuilderActions = (props) => {
+ const {
+ isDisabled,
+ selectedFields,
+ } = props;
+
+ const [copied, setIsCopied] = useState();
+ const [url, setUrl] = useState();
+
+ const buildUrl = () => {
+ return buildApiUrl(deepPluck(selectedFields, 'value'));
+ }
+
+ const copyUrl = () => {
+ typeof navigator !== `undefined` &&
+ navigator.clipboard &&
+ navigator.clipboard.writeText(url).then(() => {
+ setIsCopied(true);
+ });
+ }
+
+ const copyQueryLink = () => {
+ typeof navigator !== `undefined` &&
+ typeof window !== `undefined` &&
+ navigator.clipboard &&
+ navigator.clipboard.writeText(window.location.href);
+ }
+
+ useEffect(() => {
+ setIsCopied(false);
+ setUrl(buildUrl());
+ }, [selectedFields]);
+
+ return (
+
+ { !isDisabled &&
+
+ Your API Url:
+
+ { url }
+
+
+ }
+ { typeof navigator !== `undefined` && navigator.clipboard &&
+
+ Copy Url
+
+ }
+ { copied &&
+
+ Copied!
+
+ }
+
+
+ Share your query builder settings by copying the page's url
+
+
+ );
+};
+
+BuilderActions.propTypes = {
+ isDisabled: PropTypes.bool.isRequired,
+ selectedFields: propTypes.SelectedFieldsPropTypes.isRequired,
+};
+
+export function mapStateToProps(state) {
+ return {
+ isDisabled: !Object.keys(state.selectedFields).length,
+ selectedFields: state.selectedFields,
+ };
+}
+
+export function areStatesEqual(prev, next) {
+ return prev.selectedFields === next.selectedFields;
+}
+
+export default connect(
+ mapStateToProps,
+ null,
+ null,
+ { areStatesEqual }
+)(BuilderActions);
diff --git a/src/components/modules/builder-actions.test.js b/src/components/modules/builder-actions.test.js
new file mode 100644
index 0000000..38931aa
--- /dev/null
+++ b/src/components/modules/builder-actions.test.js
@@ -0,0 +1,75 @@
+import React from 'react';
+import '@testing-library/jest-dom';
+import { render, fireEvent, screen } from '@testing-library/react';
+import { act } from 'react-dom/test-utils';
+import { BuilderActions } from './builder-actions';
+import FIELD_OPTIONS from '../../data/fields';
+import { API_DOMAIN } from '../../data/api';
+
+describe(' ', function() {
+ describe('when isDisabled is true', function() {
+
+ test('it disables copy url button', async () => {
+ navigator.clipboard = { writeText: jest.fn() };
+
+ render();
+ const button = await screen.getByRole('button', {
+ name: /Copy Url/i
+ })
+ expect(button).toHaveAttribute('disabled');
+ });
+ });
+ describe('when isDisabled is false', function() {
+ test('it enables copy url button', async () => {
+ navigator.clipboard = { writeText: jest.fn() };
+
+ render();
+
+ const button = await screen.getByRole('button', {
+ name: /Copy Url/i
+ })
+ expect(button).not.toHaveAttribute('disabled');
+ });
+ test('it shows the API url', async () => {
+ navigator.clipboard = { writeText: jest.fn() };
+
+ render();
+
+ const apiUrl = await screen.getByText(new RegExp(API_DOMAIN, 'i'))
+ expect(apiUrl).toBeInTheDocument();
+ });
+ });
+ /* To account for browsers with no Clipboard API (IE) */
+ describe('when navigator is not present', function() {
+ test('hides the copy url button', async () => {
+ navigator.clipboard = undefined;
+
+ render();
+ const button = await screen.queryByRole('button', {
+ name: /Copy Url/i
+ })
+ expect(button).not.toBeInTheDocument();
+ });
+ });
+ describe('when copied button is clicked', function() {
+ test('shows a success message', async () => {
+ navigator.clipboard = {
+ writeText: jest.fn(() => Promise.resolve())
+ };
+
+ render();
+
+ const button = await screen.getByRole('button', {
+ name: /Copy Url/i
+ });
+
+ await act(async () => {
+ fireEvent.click(button);
+ });
+
+ const msg = await screen.getByText(/Copied/i);
+
+ expect(msg).toBeInTheDocument();
+ });
+ });
+})
diff --git a/src/components/modules/instructions.js b/src/components/modules/instructions.js
new file mode 100644
index 0000000..bc5b6d4
--- /dev/null
+++ b/src/components/modules/instructions.js
@@ -0,0 +1,33 @@
+import React, { Fragment } from 'react'; // eslint-disable-line
+import * as LINKS from '../../data/links';
+
+const Instructions = (props) => {
+ return (
+
+
+
+
+ This site is in Beta . Help us improve it by emailing site-scanning@gsa.gov
+
+
+
+ Site Scanning Query Builder
+ How it Works
+
+
+ Set the filters you want
+
+
+ Copy the generated URL
+
+
+ Use this URL in our Google Sheets or Microsoft Excel template to pull the data into a spreadsheet!
+
+
+
+ );
+};
+
+Instructions.propTypes = {};
+
+export default Instructions;
diff --git a/src/components/modules/query-builder.css b/src/components/modules/query-builder.css
new file mode 100644
index 0000000..8a07490
--- /dev/null
+++ b/src/components/modules/query-builder.css
@@ -0,0 +1,31 @@
+.main {
+ display: flex;
+ flex-direction: column-reverse;
+}
+
+.left {
+ overflow: auto;
+ background-color: #ddd;
+ border-right: #ddd 1px solid;
+}
+
+.right {
+ background-color: white;
+ flex: 1;
+ padding: 1rem;
+}
+
+@media all and (min-width: 1024px) {
+ .main {
+ flex-direction: row;
+ }
+ .left {
+ position: fixed;
+ height: 100vh;
+ width: 30%;
+ }
+ .right {
+ padding: 1rem 4rem;
+ margin-left: 30%;
+ }
+}
diff --git a/src/components/modules/query-builder.js b/src/components/modules/query-builder.js
new file mode 100644
index 0000000..f9b0305
--- /dev/null
+++ b/src/components/modules/query-builder.js
@@ -0,0 +1,27 @@
+import React from 'react'; // eslint-disable-line
+import { Provider } from 'react-redux';
+import store from '../../redux/index';
+import AvailableFields from './available-fields';
+import SelectedFields from './selected-fields';
+import BuilderActions from './builder-actions';
+import Instructions from './instructions';
+import './query-builder.css';
+
+const QueryBuilder = () => {
+ return (
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default QueryBuilder;
diff --git a/src/components/modules/selected-fields.js b/src/components/modules/selected-fields.js
new file mode 100644
index 0000000..0b8d749
--- /dev/null
+++ b/src/components/modules/selected-fields.js
@@ -0,0 +1,71 @@
+import React from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+import * as propTypes from '../../prop-types';
+import { bindActionCreators } from 'redux';
+import { connect } from 'react-redux';
+import { groupBy, sortBy } from 'lodash';
+import { unselectField } from '../../redux/ducks/selectedFields';
+import FIELD_CATEGORY_ORDER from '../../data/field-category-order';
+import SelectedFieldGroup from '../selected-field-group';
+
+const SelectedFields = (props) => {
+ const groups = groupBy(Object.values(props.selectedFields), 'category');
+ const sortedGroupKeys = sortBy(Object.keys(groups), key => FIELD_CATEGORY_ORDER[key]);
+ const clearAll = () => {
+ Object.values(props.selectedFields).forEach(field => {
+ props.actions.unselectField(field);
+ });
+ }
+ return (
+
+
Your Selections
+ { !Object.keys(props.selectedFields).length &&
+
You have nothing selected from available filters.
+ }
+ { sortedGroupKeys.map(key => (
+
+ )) }
+ { !!Object.keys(props.selectedFields).length &&
+
+ Clear All
+
+ }
+
+ );
+};
+
+SelectedFields.propTypes = {
+ selectedFields: propTypes.SelectedFieldsPropTypes.isRequired,
+};
+
+const mapStateToProps = (state) => ({
+ selectedFields: state.selectedFields,
+});
+
+const mapDispatchToProps = (dispatch) => ({
+ actions: bindActionCreators({
+ unselectField,
+ }, dispatch)
+});
+
+const areStatesEqual = (prev, next) => (
+ prev.selectedFields === next.selectedFields
+);
+
+export default connect(
+ mapStateToProps,
+ mapDispatchToProps,
+ null,
+ { areStatesEqual },
+)(SelectedFields);
+
diff --git a/src/components/pagination.js b/src/components/pagination.js
deleted file mode 100644
index 4b86991..0000000
--- a/src/components/pagination.js
+++ /dev/null
@@ -1,160 +0,0 @@
-import React, { useState, useEffect } from 'react';
-
-const Pagination = ({ recordCount, handleFilterQuery }) => {
- const [recordsPerPage, setRecordsPerPage] = useState(100);
- const [currentPage, setCurrentPage] = useState(1);
- const [positionInList, setPositionInList] = useState('beginning');
- const numPages = Math.ceil(recordCount / recordsPerPage);
-
- const MAX_VISIBLE = 5;
- const IS_BEGINNING =
- (currentPage <= MAX_VISIBLE && numPages > MAX_VISIBLE) || numPages == 0;
- const IS_MIDDLE =
- currentPage > MAX_VISIBLE && currentPage <= numPages - MAX_VISIBLE;
-
- const checkPositionInList = () => {
- if (IS_BEGINNING) {
- setPositionInList('beginning');
- } else if (IS_MIDDLE) {
- setPositionInList('middle');
- } else {
- setPositionInList('end');
- }
- };
-
- const handlePageNav = pageNum => {
- setCurrentPage(pageNum);
- handleFilterQuery({ page: pageNum });
- checkPositionInList();
- };
-
- let pageLinks = [];
-
- if (IS_BEGINNING) {
- for (let i = 1; i <= MAX_VISIBLE; i++) {
- pageLinks.push(
-
- );
- }
- } else if (IS_MIDDLE) {
- for (let i = currentPage; i < currentPage + MAX_VISIBLE; i++) {
- pageLinks.push(
-
- );
- }
- } else {
- let index = numPages - MAX_VISIBLE;
- index = index > 0 ? index : 1;
- for (let i = index; i <= numPages; i++) {
- pageLinks.push(
-
- );
- }
- }
-
- useEffect(() => {
- checkPositionInList();
- }, [currentPage]);
-
- return numPages <= 1 ? (
- ''
- ) : (
- <>
-
-
-
- {currentPage === 1 ? (
- Prev
- ) : (
- {
- e.preventDefault();
- handlePageNav(currentPage - 1);
- }}
- >
- Prev
-
- )}
-
-
-
-
- {pageLinks.map((link, i) => (
- {link}
- ))}
-
-
-
-
- {currentPage === numPages ? (
- Next
- ) : (
- {
- e.preventDefault();
- handlePageNav(currentPage + 1);
- }}
- >
- Next
-
- )}
-
-
-
- >
- );
-};
-
-const PaginationLink = ({ isCurrent, pageNum, handlePageNav, className }) => {
- return isCurrent ? (
-
- {pageNum}
-
- ) : (
- {
- e.preventDefault();
- handlePageNav(pageNum);
- }}
- aria-current={isCurrent}
- aria-label={`Page ${pageNum}`}
- className={className}
- data-testid={`page-${pageNum}`}
- >
- {pageNum}
-
- );
-};
-
-export default Pagination;
diff --git a/src/components/report-filters.js b/src/components/report-filters.js
deleted file mode 100644
index 471679c..0000000
--- a/src/components/report-filters.js
+++ /dev/null
@@ -1,436 +0,0 @@
-import React, { useState, useEffect, useContext } from 'react';
-import axios from 'axios';
-import { API_BASE_URL } from '../constants';
-import { DispatchQueryContext } from './report-query-provider';
-
-const ReportFilters = ({ reportType }) => {
- const dictionary = {
- security: 'pshtt',
- design: 'uswds2',
- criticalComponents: 'third_parties',
- analytics: 'dap',
- performance: 'lighthouse',
- accessibility: 'lighthouse',
- };
- const [loading, setLoading] = useState(false);
- const [agencies, setAgencies] = useState([]);
- const [scanDates, setScanDates] = useState([]);
-
- const scanType = dictionary[reportType] || reportType;
-
- const dispatchQuery = useContext(DispatchQueryContext);
-
- const fetchList = (reportType, list) => {
- return axios.get(`${API_BASE_URL}lists/${reportType}/${list}`);
- };
-
- const handleFilterChange = filter => {
- const filterName = Object.keys(filter)[0];
- if ((filter[filterName] == '" "') | (filter[filterName] == '')) {
- dispatchQuery({
- type: `REMOVE_FILTERS`,
- filtersToRemove: [filterName],
- });
- } else {
- dispatchQuery({
- type: `APPLY_FILTER`,
- newFilter: { filters: filter },
- });
- }
- };
-
- useEffect(() => {
- const fetchData = async () => {
- const agencies = await fetchList(scanType, 'agencies');
- const dates = await axios.get(`${API_BASE_URL}lists/dates/`);
- setAgencies(agencies.data);
- setScanDates(dates.data);
- setLoading(false);
- };
-
- fetchData();
- }, []);
-
- return loading ? (
- Loading…
- ) : (
-
- );
-};
-
-export default ReportFilters;
-
-const FilterForm = ({
- reportType,
- agencies,
- scanDates,
- handleFilterChange,
- dispatchQuery,
-}) => {
- let reportSpecificFilters;
-
- if (reportType == 'design') {
- reportSpecificFilters = (
-
- );
- }
-
- if (reportType == 'security') {
- reportSpecificFilters = (
-
- );
- }
-
- if (reportType == 'analytics') {
- reportSpecificFilters = (
-
- );
- }
-
- if (reportType == 'performance') {
- reportSpecificFilters = (
-
- );
- }
-
- if (reportType == 'accessibility') {
- reportSpecificFilters = (
-
- );
- }
-
- return (
-
- );
-};
-
-const AgenciesFilter = ({ agencies, handleFilterChange }) => {
- return (
- <>
-
- Agency
-
-
- handleFilterChange({ [e.target.name]: `"${e.target.value}"` })
- }
- >
-
- - Select -
-
- {agencies.length == 0
- ? null
- : agencies.map(a => (
-
- {a}
-
- ))}
-
- >
- );
-};
-
-const DomainFilter = ({ handleFilterChange }) => {
- return (
- <>
-
- Domain
-
-
- handleFilterChange({ [e.target.name]: `${e.target.value}*` })
- }
- />
- >
- );
-};
-
-const ScanDateFilter = ({ dispatchQuery, scanDates }) => {
- return (
- <>
-
- Scan Date
-
-
- dispatchQuery({
- type: 'CHANGE_SCAN_DATE',
- scanDate: e.target.value,
- })
- }
- >
- {scanDates.map(date => (
-
- {date}
-
- ))}
-
- >
- );
-};
-
-const UswdsFilters = ({ handleFilterChange }) => {
- const checkboxFilters = [
- {
- property: 'data.publicsansfont_detected',
- label: 'Public Sans Font',
- },
- {
- property: 'data.merriweatherfont_detected',
- label: 'Merriweather Font',
- },
- {
- property: 'data.sourcesansfont_detected',
- label: 'Source Sans Font',
- },
- {
- property: 'data.flag_detected',
- label: 'Flag',
- },
- {
- property: 'data.flagincss_detected',
- label: 'Flag in CSS',
- },
- {
- property: 'data.tables',
- label: 'Tables',
- },
- {
- property: 'data.usa_classes_detected',
- label: 'USA Classes',
- },
- {
- property: 'data.usa_detected',
- label: 'USA',
- },
- {
- property: 'data.uswds_detected',
- label: 'USWDS',
- },
- {
- property: 'data.uswdsincss_detected',
- label: 'USWDS in CSS',
- },
- ];
- return (
- <>
-
-
- USWDS Features
-
- {checkboxFilters.map(f => (
-
- ))}
-
-
- >
- );
-};
-
-const UswdsVersionFilter = ({ handleFilterChange }) => {
- const versions = [
- '- Select -',
- 'v2.3.1',
- 'v2.0.3',
- 'v1.1.0',
- 'v1.4.1',
- 'v1.6.3',
- 'v2.2.1',
- 'v0.14.0',
- ];
-
- return (
- <>
-
- USWDS Version
-
-
- handleFilterChange({ 'data.uswdsversion': e.target.value })
- }
- >
- {versions.map(v => {
- const version = v == '- Select -' ? '' : v;
- return (
-
- {v}
-
- );
- })}
-
- >
- );
-};
-
-const NumericFilterCheckbox = ({ handleFilterChange, label, property }) => {
- return (
-
-
- handleFilterChange({
- [property]: e.target.checked ? `[1 TO *]` : '',
- })
- }
- />
-
- {label}
-
-
- );
-};
-
-const SecurityFilters = ({ handleFilterChange }) => {
- return (
- <>
-
-
- >
- );
-};
-
-const AnalyticsFilters = ({ handleFilterChange }) => {
- return (
-
- );
-};
-
-const PerformanceFilters = ({ handleFilterChange }) => {
- return (
-
- );
-};
-
-const AccessibilityFilters = ({ handleFilterChange }) => {
- return (
- <>
-
-
-
-
-
-
-
- >
- );
-};
-
-const PresentAbsentFilter = ({
- handleFilterChange,
- label,
- property,
- presentText,
- absentText,
- boolean,
-}) => {
- const id = label.toLowerCase().split(' ').join('-');
- const presentVal = boolean ? 'true' : 1;
- const absentVal = boolean ? 'false' : 0;
-
- return (
- <>
-
- {label}
-
- handleFilterChange({ [property]: e.target.value })}
- >
- - Select -
- {presentText || `Present`}
- {absentText || `Not Present`}
-
- >
- );
-};
diff --git a/src/components/report-query-provider.js b/src/components/report-query-provider.js
deleted file mode 100644
index d3d07ae..0000000
--- a/src/components/report-query-provider.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import React, { useReducer, createContext } from 'react';
-import { merge } from 'lodash';
-
-export const QueryContext = createContext();
-export const DispatchQueryContext = createContext();
-
-const ReportQueryProvider = ({ children }) => {
- const removeFilters = (obj, filtersToRemove) => {
- const copy = { ...obj };
- filtersToRemove.map(f => delete copy.filters[f]);
- return copy;
- };
-
- const queryReducer = (state, action) => {
- switch (action.type) {
- case 'CHANGE_PAGE':
- return { ...state, page: action.page };
- case 'APPLY_FILTER':
- return { ...merge(state, action.newFilter) };
- case 'REMOVE_FILTERS':
- return removeFilters({ ...state }, [...action.filtersToRemove]);
- case 'CHANGE_SCAN_DATE':
- return { ...state, scanDate: action.scanDate };
- default:
- return state;
- }
- };
-
- const [query, dispatchQuery] = useReducer(queryReducer, { page: 1 });
-
- return (
-
-
- {children}
-
-
- );
-};
-
-export default ReportQueryProvider;
diff --git a/src/components/report.js b/src/components/report.js
deleted file mode 100644
index ecf0a12..0000000
--- a/src/components/report.js
+++ /dev/null
@@ -1,312 +0,0 @@
-import PropTypes from 'prop-types';
-import React, { useState, useEffect, useContext, useRef } from 'react';
-import ReportFilters from './report-filters';
-import { API_BASE_URL } from '../constants';
-import axios from 'axios';
-import { v1 as uuidv1 } from 'uuid';
-import { QueryContext, DispatchQueryContext } from './report-query-provider';
-import Pagination from './pagination';
-import Alert from './uswds/alert';
-
-const Report = ({ reportType, columns, endpoint }) => {
- const [reportData, setReportData] = useState([]);
- const [recordCount, setRecordCount] = useState(0);
- const [errors, setErrors] = useState();
- const [loading, setLoading] = useState(true);
- const query = useContext(QueryContext);
- const dispatchQuery = useContext(DispatchQueryContext);
-
- const isInitialLoad = useRef(true);
-
- const strFromQuery = queryObj => {
- let str = `page=${queryObj.page}`;
- if (queryObj.filters) {
- Object.keys(queryObj.filters).map(k => {
- str += `&${k}=${queryObj.filters[k]}`;
- });
- str = str.replace(/ /g, '+');
- }
- return str;
- };
-
- const queryString = strFromQuery(query);
- const queryBaseUrl = query.scanDate
- ? `${API_BASE_URL}date/${query.scanDate}/`
- : API_BASE_URL;
-
- const fetchReportData = async () => {
- let result;
- result = await axios.get(`${queryBaseUrl}${endpoint}/?${queryString}`);
-
- if (typeof result.data == 'object') {
- setErrors(null);
- setRecordCount(result.data.count);
- setReportData(result.data.results);
- setReportData(result.data.results);
- } else {
- setErrors({ ...errors, apiError: `no data` });
- setRecordCount(0);
- setReportData([]);
- }
- };
-
- const handlePageChange = ({ page }) => {
- dispatchQuery({ type: 'CHANGE_PAGE', page: page });
- };
-
- useEffect(() => {
- setLoading(true);
- fetchReportData();
- }, [queryString, query.scanDate]);
-
- useEffect(() => {
- if (!isInitialLoad.current) {
- setLoading(false);
- } else {
- isInitialLoad.current = false;
- }
- }, [reportData]);
-
- return (
- <>
- {errors && (
-
- There was an error loading data. Please try refreshing the page. If
- the error persists, please let us know .
-
- )}
- {!loading && !errors && recordCount == 0 && (
-
- )}
-
-
-
-
-
-
-
-
-
-
- >
- );
-};
-
-Report.propTypes = {
- reportType: PropTypes.string,
- columns: PropTypes.arrayOf(PropTypes.object),
- endpoint: PropTypes.string,
-};
-
-export default Report;
-const CsvLink = ({ queryUrl }) => {
- const csvUrl = queryUrl.replace(/page=\d+(&)?/, '');
- return Download these results as a CSV ;
-};
-
-const RecordCount = ({ recordCount }) => {
- return {recordCount} Results ;
-};
-
-const ReportTable = ({ children }) => (
-
-);
-
-ReportTable.propTypes = {
- children: PropTypes.oneOfType([
- PropTypes.element,
- PropTypes.arrayOf(PropTypes.element),
- ]),
-};
-
-const ReportTableHead = ({ columns }) => {
- return (
-
-
- {columns.map(c => (
-
- {c.title}
-
- ))}
-
-
- );
-};
-
-ReportTableHead.propTypes = {
- columns: PropTypes.arrayOf(PropTypes.object),
-};
-
-const ReportTableBody = ({ columns, records, isLoading }) => {
- return isLoading ? (
-
-
-
-
-
-
-
- ) : (
-
- {records.map(r => (
-
- ))}
-
- );
-};
-
-ReportTableBody.propTypes = {
- columns: PropTypes.arrayOf(PropTypes.object),
- records: PropTypes.arrayOf(PropTypes.object),
- isLoading: PropTypes.bool,
-};
-
-const ReportTableRow = ({ columns, record }) => {
- const invalidRecord = record.data.invalid;
-
- return invalidRecord ? (
-
- {record.domain}
-
-
- ) : (
-
- {columns.map((c, i) => (
-
- ))}
-
- );
-};
-
-ReportTableRow.propTypes = {
- columns: PropTypes.arrayOf(PropTypes.object),
- record: PropTypes.object,
-};
-
-const ReportTableCell = ({ value, isFirst, isUrl }) => {
- const parseValue = value => {
- if (isUrl) return {value} ;
- if (typeof value == 'boolean') return String(value);
- if (typeof value == 'object') return ;
- return value;
- };
-
- value = parseValue(value);
-
- const boolClass = value => {
- if (value == 'true' || value == '1') return 'true';
- if (value == 'false' || value == '0') return 'false';
- return null;
- };
-
- return isFirst ? (
- {value}
- ) : (
- {value}
- );
-};
-
-ReportTableCell.propTypes = {
- value: PropTypes.oneOfType([
- PropTypes.node,
- PropTypes.bool,
- PropTypes.object,
- ]),
- isFirst: PropTypes.bool,
-};
-
-const ReportTableCellInvalid = ({ width }) => {
- return (
-
- Spotlight was unable to prepare a report for this domain
-
- );
-};
-
-ReportTableCellInvalid.propTypes = {
- columns: PropTypes.arrayOf(PropTypes.object),
- record: PropTypes.object,
-};
-
-const ObjectList = ({ object }) => {
- const isArray = Array.isArray(object);
- return object === null ? (
- ''
- ) : (
-
- {Object.keys(object).map(k => (
-
- {!isArray && {k}: }
- {object[k]}
-
- ))}
-
- );
-};
-
-ObjectList.propTypes = {
- record: PropTypes.object,
-};
-
-const Spinner = () => {
- return (
-
-
-
-
-
-
-
- Loading
-
-
- );
-};
diff --git a/src/components/selected-field-group.js b/src/components/selected-field-group.js
new file mode 100644
index 0000000..f9c273f
--- /dev/null
+++ b/src/components/selected-field-group.js
@@ -0,0 +1,33 @@
+import React from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+import * as propTypes from '../prop-types';
+import { orderBy } from 'lodash';
+import { faTimesCircle } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+
+const SelectedFieldGroup = (props) => {
+ const fieldsOrdered = orderBy(props.fields, ['order'], ['asc']);
+ return fieldsOrdered.map(field => (
+
+ props.onClickField(field)}
+ title={`Remove ${field.title} filter`}
+ >
+
+
+ { field.title }{ field.value && `:`}{ field.value && {field.value} }
+
+ ));
+}
+
+SelectedFieldGroup.propTypes = {
+ groupName: PropTypes.string.isRequired,
+ fields: PropTypes.arrayOf(propTypes.SelectedFieldPropTypes).isRequired,
+}
+
+export default SelectedFieldGroup;
diff --git a/src/components/uswds/accordion-item.js b/src/components/uswds/accordion-item.js
new file mode 100644
index 0000000..7803c91
--- /dev/null
+++ b/src/components/uswds/accordion-item.js
@@ -0,0 +1,53 @@
+import React, { Fragment } from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+
+export const AccordionItem = (props) => {
+ const {
+ heading,
+ id,
+ content,
+ expanded,
+ handleToggle,
+ customStyles,
+ } = props
+
+ return (
+
+
+
+ {heading}
+
+
+
+ {content}
+
+
+ )
+}
+
+AccordionItem.propTypes = {
+ content: PropTypes.object,
+ expanded: PropTypes.bool,
+ handleToggle: PropTypes.func.isRequired,
+ heading: PropTypes.string,
+ id: PropTypes.string,
+ customStyles: PropTypes.shape({
+ heading: PropTypes.object,
+ content: PropTypes.object,
+ }),
+};
+
+export default AccordionItem;
diff --git a/src/components/uswds/accordion.js b/src/components/uswds/accordion.js
new file mode 100644
index 0000000..8720298
--- /dev/null
+++ b/src/components/uswds/accordion.js
@@ -0,0 +1,57 @@
+import React, { useEffect, useState } from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+import AccordionItem from './accordion-item';
+
+const Accordion = (props) => {
+ const { items, customStyles } = props;
+
+ const [openItems, setOpenState] = useState(
+ items.filter((i) => !!i.expanded).map((i) => i.id)
+ )
+
+ const toggleItem = itemId => {
+ const newOpenItems = [...openItems]
+ const itemIndex = openItems.indexOf(itemId)
+
+ if (itemIndex > -1) {
+ newOpenItems.splice(itemIndex, 1)
+ } else {
+ newOpenItems.push(itemId)
+ }
+
+ setOpenState(newOpenItems)
+ }
+
+ useEffect(()=>{
+ props.defaultExpandedId && toggleItem(props.defaultExpandedId)
+ },[])
+
+ return (
+
+ { items.map((item, i) => (
+
-1 }
+ handleToggle={ () => toggleItem(item.id) }
+ customStyles={customStyles}
+ />
+ )) }
+
+ )
+};
+
+Accordion.propTypes = {
+ items: PropTypes.arrayOf(PropTypes.shape({
+ id: PropTypes.string,
+ heading: PropTypes.string,
+ content: PropTypes.object,
+ })),
+ defaultExpandedId: PropTypes.string,
+ customStyles: PropTypes.shape({
+ heading: PropTypes.object,
+ content: PropTypes.object,
+ }),
+};
+
+export default Accordion;
diff --git a/src/components/uswds/checkbox.js b/src/components/uswds/checkbox.js
new file mode 100644
index 0000000..f1aec02
--- /dev/null
+++ b/src/components/uswds/checkbox.js
@@ -0,0 +1,33 @@
+import React from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+
+const Checkbox = (props) => {
+ return (
+
+
+
+ { props.label}
+
+
+ );
+};
+
+Checkbox.propTypes = {
+ id: PropTypes.string.isRequired,
+ label: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ checked: PropTypes.bool.isRequired,
+ onChange: PropTypes.func.isRequired,
+};
+
+export default Checkbox;
diff --git a/src/components/uswds/dropdown.js b/src/components/uswds/dropdown.js
new file mode 100644
index 0000000..ca28c83
--- /dev/null
+++ b/src/components/uswds/dropdown.js
@@ -0,0 +1,39 @@
+import React from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+
+const Dropdown = (props) => {
+ return (
+
+ - Select -
+ { props.options.map((option) => (
+
+ { option.label }
+
+ ))}
+
+ );
+};
+
+Dropdown.propTypes = {
+ id: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.string,
+ options: PropTypes.arrayOf(PropTypes.shape({
+ label: PropTypes.string,
+ value: PropTypes.any,
+ })),
+ ariaLabel: PropTypes.string,
+};
+
+export default Dropdown;
diff --git a/src/components/uswds/text-input.js b/src/components/uswds/text-input.js
new file mode 100644
index 0000000..ce23d60
--- /dev/null
+++ b/src/components/uswds/text-input.js
@@ -0,0 +1,27 @@
+import React from 'react'; // eslint-disable-line
+import PropTypes from 'prop-types';
+
+const TextInput = (props) => {
+ return (
+
+ );
+};
+
+TextInput.propTypes = {
+ id: PropTypes.string.isRequired,
+ name: PropTypes.string.isRequired,
+ onChange: PropTypes.func.isRequired,
+ value: PropTypes.string,
+ placeholder: PropTypes.string,
+ ariaLabel: PropTypes.string,
+};
+
+export default TextInput;
diff --git a/src/constants.js b/src/constants.js
deleted file mode 100644
index 207aed3..0000000
--- a/src/constants.js
+++ /dev/null
@@ -1,3 +0,0 @@
-export const API_BASE_URL = `https://spotlight.app.cloud.gov/api/v1/`;
-// Alternatively, to use the full subdomain scanner:
-// export const API_BASE_URL = `https://scanner-ui-spontaneous-koala-eo.app.cloud.gov/api/v1/`;
diff --git a/src/data/agency_bureau_data.js b/src/data/agency_bureau_data.js
new file mode 100644
index 0000000..0535d69
--- /dev/null
+++ b/src/data/agency_bureau_data.js
@@ -0,0 +1,2948 @@
+const AGENCY_BUREAU_DATA = [
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Senate",
+ "Agency Code": "1",
+ "Bureau Code": "5",
+ "Treasury Code": "0",
+ "CGAC Code": "0"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "House of Representatives",
+ "Agency Code": "1",
+ "Bureau Code": "10",
+ "Treasury Code": "0",
+ "CGAC Code": "0"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Joint Items",
+ "Agency Code": "1",
+ "Bureau Code": "11",
+ "Treasury Code": "0",
+ "CGAC Code": "0"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Capitol Police",
+ "Agency Code": "1",
+ "Bureau Code": "13",
+ "Treasury Code": "2",
+ "CGAC Code": "2"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Office of Compliance",
+ "Agency Code": "1",
+ "Bureau Code": "12",
+ "Treasury Code": "9",
+ "CGAC Code": "9"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Congressional Budget Office",
+ "Agency Code": "1",
+ "Bureau Code": "14",
+ "Treasury Code": "8",
+ "CGAC Code": "8"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Architect of the Capitol",
+ "Agency Code": "1",
+ "Bureau Code": "15",
+ "Treasury Code": "1",
+ "CGAC Code": "1"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Botanic Garden",
+ "Agency Code": "1",
+ "Bureau Code": "18",
+ "Treasury Code": "9",
+ "CGAC Code": "9"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Library of Congress",
+ "Agency Code": "1",
+ "Bureau Code": "25",
+ "Treasury Code": "3",
+ "CGAC Code": "3"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Government Printing Office",
+ "Agency Code": "1",
+ "Bureau Code": "30",
+ "Treasury Code": "4",
+ "CGAC Code": "4"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Government Accountability Office",
+ "Agency Code": "1",
+ "Bureau Code": "35",
+ "Treasury Code": "5",
+ "CGAC Code": "5"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "United States Tax Court",
+ "Agency Code": "1",
+ "Bureau Code": "40",
+ "Treasury Code": "23",
+ "CGAC Code": "23"
+ },
+ {
+ "Agency Name": "Legislative Branch",
+ "Bureau Name": "Legislative Branch Boards and Commissions",
+ "Agency Code": "1",
+ "Bureau Code": "45",
+ "Treasury Code": "9",
+ "CGAC Code": "9"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "Judicial Branch",
+ "Agency Code": "2",
+ "Bureau Code": "0",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "Supreme Court of the United States",
+ "Agency Code": "2",
+ "Bureau Code": "5",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "United States Court of Appeals for the Federal Circuit",
+ "Agency Code": "2",
+ "Bureau Code": "7",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "United States Court of International Trade",
+ "Agency Code": "2",
+ "Bureau Code": "15",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "Courts of Appeals, District Courts, and other Judicial Services",
+ "Agency Code": "2",
+ "Bureau Code": "25",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "Administrative Office of the United States Courts",
+ "Agency Code": "2",
+ "Bureau Code": "26",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "Federal Judicial Center",
+ "Agency Code": "2",
+ "Bureau Code": "30",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "Judicial Retirement Funds",
+ "Agency Code": "2",
+ "Bureau Code": "35",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Judicial Branch",
+ "Bureau Name": "United States Sentencing Commission",
+ "Agency Code": "2",
+ "Bureau Code": "39",
+ "Treasury Code": "10",
+ "CGAC Code": "10"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Department of Agriculture",
+ "Agency Code": "5",
+ "Bureau Code": "0",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Office of the Secretary",
+ "Agency Code": "5",
+ "Bureau Code": "3",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Executive Operations",
+ "Agency Code": "5",
+ "Bureau Code": "4",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Office of Chief Information Officer",
+ "Agency Code": "5",
+ "Bureau Code": "12",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Office of Chief Financial Officer",
+ "Agency Code": "5",
+ "Bureau Code": "14",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Office of Civil Rights",
+ "Agency Code": "5",
+ "Bureau Code": "7",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Hazardous Materials Management",
+ "Agency Code": "5",
+ "Bureau Code": "16",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Buildings and Facilities",
+ "Agency Code": "5",
+ "Bureau Code": "19",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Office of Inspector General",
+ "Agency Code": "5",
+ "Bureau Code": "8",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Office of the General Counsel",
+ "Agency Code": "5",
+ "Bureau Code": "10",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Economic Research Service",
+ "Agency Code": "5",
+ "Bureau Code": "13",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "National Agricultural Statistics Service",
+ "Agency Code": "5",
+ "Bureau Code": "15",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Agricultural Research Service",
+ "Agency Code": "5",
+ "Bureau Code": "18",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "National Institute of Food and Agriculture",
+ "Agency Code": "5",
+ "Bureau Code": "20",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Animal and Plant Health Inspection Service",
+ "Agency Code": "5",
+ "Bureau Code": "32",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Food Safety and Inspection Service",
+ "Agency Code": "5",
+ "Bureau Code": "35",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Grain Inspection, Packers and Stockyards Administration",
+ "Agency Code": "5",
+ "Bureau Code": "37",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Agricultural Marketing Service",
+ "Agency Code": "5",
+ "Bureau Code": "45",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Risk Management Agency",
+ "Agency Code": "5",
+ "Bureau Code": "47",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Farm Service Agency",
+ "Agency Code": "5",
+ "Bureau Code": "49",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Natural Resources Conservation Service",
+ "Agency Code": "5",
+ "Bureau Code": "53",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Rural Development",
+ "Agency Code": "5",
+ "Bureau Code": "55",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Rural Housing Service",
+ "Agency Code": "5",
+ "Bureau Code": "63",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Rural Business - Cooperative Service",
+ "Agency Code": "5",
+ "Bureau Code": "65",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Rural Utilities Service",
+ "Agency Code": "5",
+ "Bureau Code": "60",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Foreign Agricultural Service",
+ "Agency Code": "5",
+ "Bureau Code": "68",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Food and Nutrition Service",
+ "Agency Code": "5",
+ "Bureau Code": "84",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Agriculture",
+ "Bureau Name": "Forest Service",
+ "Agency Code": "5",
+ "Bureau Code": "96",
+ "Treasury Code": "12",
+ "CGAC Code": "12"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "Department of Commerce",
+ "Agency Code": "6",
+ "Bureau Code": "0",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "Departmental Management",
+ "Agency Code": "6",
+ "Bureau Code": "5",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "Economic Development Administration",
+ "Agency Code": "6",
+ "Bureau Code": "6",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "Bureau of the Census",
+ "Agency Code": "6",
+ "Bureau Code": "7",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "Economics and Statistics Administration",
+ "Agency Code": "6",
+ "Bureau Code": "8",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "International Trade and Investment Administration",
+ "Agency Code": "6",
+ "Bureau Code": "25",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "Bureau of Industry and Security",
+ "Agency Code": "6",
+ "Bureau Code": "30",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "Minority Business Development Agency",
+ "Agency Code": "6",
+ "Bureau Code": "40",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "National Oceanic and Atmospheric Administration",
+ "Agency Code": "6",
+ "Bureau Code": "48",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "U.S. Patent and Trademark Office",
+ "Agency Code": "6",
+ "Bureau Code": "51",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "National Technical Information Service",
+ "Agency Code": "6",
+ "Bureau Code": "54",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "National Institute of Standards and Technology",
+ "Agency Code": "6",
+ "Bureau Code": "55",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Commerce",
+ "Bureau Name": "National Telecommunications and Information Administration",
+ "Agency Code": "6",
+ "Bureau Code": "60",
+ "Treasury Code": "13",
+ "CGAC Code": "13"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Department of Defense - Military Programs",
+ "Agency Code": "7",
+ "Bureau Code": "0",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Military Personnel",
+ "Agency Code": "7",
+ "Bureau Code": "5",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Operation and Maintenance",
+ "Agency Code": "7",
+ "Bureau Code": "10",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "International Reconstruction and Other Assistance",
+ "Agency Code": "7",
+ "Bureau Code": "12",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Procurement",
+ "Agency Code": "7",
+ "Bureau Code": "15",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Research, Development, Test, and Evaluation",
+ "Agency Code": "7",
+ "Bureau Code": "20",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Military Construction",
+ "Agency Code": "7",
+ "Bureau Code": "25",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Family Housing",
+ "Agency Code": "7",
+ "Bureau Code": "30",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Revolving and Management Funds",
+ "Agency Code": "7",
+ "Bureau Code": "40",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Allowances",
+ "Agency Code": "7",
+ "Bureau Code": "45",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Trust Funds",
+ "Agency Code": "7",
+ "Bureau Code": "55",
+ "Treasury Code": "0*",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Navy, Marine Corps",
+ "Agency Code": "7",
+ "Bureau Code": "17",
+ "Treasury Code": "17",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Army",
+ "Agency Code": "7",
+ "Bureau Code": "21",
+ "Treasury Code": "21",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Air Force",
+ "Agency Code": "7",
+ "Bureau Code": "57",
+ "Treasury Code": "57",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Defense - Military Programs",
+ "Bureau Name": "Defense-wide",
+ "Agency Code": "7",
+ "Bureau Code": "97",
+ "Treasury Code": "97",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Department of Education",
+ "Agency Code": "18",
+ "Bureau Code": "0",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Office of Elementary and Secondary Education",
+ "Agency Code": "18",
+ "Bureau Code": "10",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Office of Innovation and Improvement",
+ "Agency Code": "18",
+ "Bureau Code": "12",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Office of English Language Acquisition",
+ "Agency Code": "18",
+ "Bureau Code": "15",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Office of Special Education and Rehabilitative Services",
+ "Agency Code": "18",
+ "Bureau Code": "20",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Office of Vocational and Adult Education",
+ "Agency Code": "18",
+ "Bureau Code": "30",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Office of Postsecondary Education",
+ "Agency Code": "18",
+ "Bureau Code": "40",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Office of Federal Student Aid",
+ "Agency Code": "18",
+ "Bureau Code": "45",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Institute of Education Sciences",
+ "Agency Code": "18",
+ "Bureau Code": "50",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Departmental Management",
+ "Agency Code": "18",
+ "Bureau Code": "80",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Education",
+ "Bureau Name": "Hurricane Education Recovery",
+ "Agency Code": "18",
+ "Bureau Code": "85",
+ "Treasury Code": "91",
+ "CGAC Code": "91"
+ },
+ {
+ "Agency Name": "Department of Energy",
+ "Bureau Name": "Department of Energy",
+ "Agency Code": "19",
+ "Bureau Code": "0",
+ "Treasury Code": "89",
+ "CGAC Code": "89"
+ },
+ {
+ "Agency Name": "Department of Energy",
+ "Bureau Name": "National Nuclear Security Administration",
+ "Agency Code": "19",
+ "Bureau Code": "5",
+ "Treasury Code": "89",
+ "CGAC Code": "89"
+ },
+ {
+ "Agency Name": "Department of Energy",
+ "Bureau Name": "Environmental and Other Defense Activities",
+ "Agency Code": "19",
+ "Bureau Code": "10",
+ "Treasury Code": "89",
+ "CGAC Code": "89"
+ },
+ {
+ "Agency Name": "Department of Energy",
+ "Bureau Name": "Energy Programs",
+ "Agency Code": "19",
+ "Bureau Code": "20",
+ "Treasury Code": "89",
+ "CGAC Code": "89"
+ },
+ {
+ "Agency Name": "Department of Energy",
+ "Bureau Name": "Power Marketing Administration",
+ "Agency Code": "19",
+ "Bureau Code": "50",
+ "Treasury Code": "89",
+ "CGAC Code": "89"
+ },
+ {
+ "Agency Name": "Department of Energy",
+ "Bureau Name": "Departmental Administration",
+ "Agency Code": "19",
+ "Bureau Code": "60",
+ "Treasury Code": "89",
+ "CGAC Code": "89"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Department of Health and Human Services",
+ "Agency Code": "9",
+ "Bureau Code": "0",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Food and Drug Administration",
+ "Agency Code": "9",
+ "Bureau Code": "10",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Health Resources and Services Administration",
+ "Agency Code": "9",
+ "Bureau Code": "15",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Indian Health Service",
+ "Agency Code": "9",
+ "Bureau Code": "17",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Centers for Disease Control and Prevention",
+ "Agency Code": "9",
+ "Bureau Code": "20",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "National Institutes of Health",
+ "Agency Code": "9",
+ "Bureau Code": "25",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Substance Abuse and Mental Health Services Administration",
+ "Agency Code": "9",
+ "Bureau Code": "30",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Agency for Healthcare Research and Quality",
+ "Agency Code": "9",
+ "Bureau Code": "33",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Centers for Medicare and Medicaid Services",
+ "Agency Code": "9",
+ "Bureau Code": "38",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Administration for Children and Families",
+ "Agency Code": "9",
+ "Bureau Code": "70",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Administration for Community Living",
+ "Agency Code": "9",
+ "Bureau Code": "75",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Departmental Management",
+ "Agency Code": "9",
+ "Bureau Code": "90",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Program Support Center",
+ "Agency Code": "9",
+ "Bureau Code": "91",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Health and Human Services",
+ "Bureau Name": "Office of the Inspector General",
+ "Agency Code": "9",
+ "Bureau Code": "92",
+ "Treasury Code": "75",
+ "CGAC Code": "75"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Department of Homeland Security",
+ "Agency Code": "24",
+ "Bureau Code": "0",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Departmental Management and Operations",
+ "Agency Code": "24",
+ "Bureau Code": "10",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Office of the Inspector General",
+ "Agency Code": "24",
+ "Bureau Code": "20",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Citizenship and Immigration Services",
+ "Agency Code": "24",
+ "Bureau Code": "30",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "United States Secret Service",
+ "Agency Code": "24",
+ "Bureau Code": "40",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Transportation Security Administration",
+ "Agency Code": "24",
+ "Bureau Code": "45",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Federal Law Enforcement Training Center",
+ "Agency Code": "24",
+ "Bureau Code": "49",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Immigration and Customs Enforcement",
+ "Agency Code": "24",
+ "Bureau Code": "55",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "U.S. Customs and Border Protection",
+ "Agency Code": "24",
+ "Bureau Code": "58",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "United States Coast Guard",
+ "Agency Code": "24",
+ "Bureau Code": "60",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "National Protection and Programs Directorate",
+ "Agency Code": "24",
+ "Bureau Code": "65",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Federal Emergency Management Agency",
+ "Agency Code": "24",
+ "Bureau Code": "70",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Science and Technology",
+ "Agency Code": "24",
+ "Bureau Code": "80",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Homeland Security",
+ "Bureau Name": "Domestic Nuclear Detection Office",
+ "Agency Code": "24",
+ "Bureau Code": "85",
+ "Treasury Code": "70",
+ "CGAC Code": "70"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Department of Housing and Urban Development",
+ "Agency Code": "25",
+ "Bureau Code": "0",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Public and Indian Housing Programs",
+ "Agency Code": "25",
+ "Bureau Code": "3",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Community Planning and Development",
+ "Agency Code": "25",
+ "Bureau Code": "6",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Housing Programs",
+ "Agency Code": "25",
+ "Bureau Code": "9",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Government National Mortgage Association",
+ "Agency Code": "25",
+ "Bureau Code": "12",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Policy Development and Research",
+ "Agency Code": "25",
+ "Bureau Code": "28",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Fair Housing and Equal Opportunity",
+ "Agency Code": "25",
+ "Bureau Code": "29",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Office of Lead Hazard Control and Healthy Homes",
+ "Agency Code": "25",
+ "Bureau Code": "32",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Office of Sustainable Housing and Communities",
+ "Agency Code": "25",
+ "Bureau Code": "33",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of Housing and Urban Development",
+ "Bureau Name": "Management and Administration",
+ "Agency Code": "25",
+ "Bureau Code": "35",
+ "Treasury Code": "86",
+ "CGAC Code": "86"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Department of the Interior",
+ "Agency Code": "10",
+ "Bureau Code": "0",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Bureau of Land Management",
+ "Agency Code": "10",
+ "Bureau Code": "4",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Bureau of Ocean Energy Management",
+ "Agency Code": "10",
+ "Bureau Code": "6",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Bureau of Safety and Environmental Enforcement",
+ "Agency Code": "10",
+ "Bureau Code": "22",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Office of Surface Mining Reclamation and Enforcement",
+ "Agency Code": "10",
+ "Bureau Code": "8",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Bureau of Reclamation",
+ "Agency Code": "10",
+ "Bureau Code": "10",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Central Utah Project",
+ "Agency Code": "10",
+ "Bureau Code": "11",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "United States Geological Survey",
+ "Agency Code": "10",
+ "Bureau Code": "12",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "United States Fish and Wildlife Service",
+ "Agency Code": "10",
+ "Bureau Code": "18",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "National Park Service",
+ "Agency Code": "10",
+ "Bureau Code": "24",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Bureau of Indian Affairs and Bureau of Indian Education",
+ "Agency Code": "10",
+ "Bureau Code": "76",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Departmental Offices",
+ "Agency Code": "10",
+ "Bureau Code": "84",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Insular Affairs",
+ "Agency Code": "10",
+ "Bureau Code": "85",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Office of the Solicitor",
+ "Agency Code": "10",
+ "Bureau Code": "86",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Office of Inspector General",
+ "Agency Code": "10",
+ "Bureau Code": "88",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Office of the Special Trustee for American Indians",
+ "Agency Code": "10",
+ "Bureau Code": "90",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "National Indian Gaming Commission",
+ "Agency Code": "10",
+ "Bureau Code": "92",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of the Interior",
+ "Bureau Name": "Department-Wide Programs",
+ "Agency Code": "10",
+ "Bureau Code": "95",
+ "Treasury Code": "14",
+ "CGAC Code": "14"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Department of Justice",
+ "Agency Code": "11",
+ "Bureau Code": "0",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "General Administration",
+ "Agency Code": "11",
+ "Bureau Code": "3",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "United States Parole Commission",
+ "Agency Code": "11",
+ "Bureau Code": "4",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Legal Activities and U.S. Marshals",
+ "Agency Code": "11",
+ "Bureau Code": "5",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "National Security Division",
+ "Agency Code": "11",
+ "Bureau Code": "8",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Radiation Exposure Compensation",
+ "Agency Code": "11",
+ "Bureau Code": "6",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Interagency Law Enforcement",
+ "Agency Code": "11",
+ "Bureau Code": "7",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Federal Bureau of Investigation",
+ "Agency Code": "11",
+ "Bureau Code": "10",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Drug Enforcement Administration",
+ "Agency Code": "11",
+ "Bureau Code": "12",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Bureau of Alcohol, Tobacco, Firearms, and Explosives",
+ "Agency Code": "11",
+ "Bureau Code": "14",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Federal Prison System",
+ "Agency Code": "11",
+ "Bureau Code": "20",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Office of Justice Programs",
+ "Agency Code": "11",
+ "Bureau Code": "21",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Justice",
+ "Bureau Name": "Violent Crime Reduction Trust Fund",
+ "Agency Code": "11",
+ "Bureau Code": "30",
+ "Treasury Code": "15",
+ "CGAC Code": "15"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Department of Labor",
+ "Agency Code": "12",
+ "Bureau Code": "0",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Employment and Training Administration",
+ "Agency Code": "12",
+ "Bureau Code": "5",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Employee Benefits Security Administration",
+ "Agency Code": "12",
+ "Bureau Code": "11",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Pension Benefit Guaranty Corporation",
+ "Agency Code": "12",
+ "Bureau Code": "12",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Employment Standards Administration",
+ "Agency Code": "12",
+ "Bureau Code": "17",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Office of Workers' Compensation Programs",
+ "Agency Code": "12",
+ "Bureau Code": "15",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Wage and Hour Division",
+ "Agency Code": "12",
+ "Bureau Code": "16",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Office of Federal Contract Compliance Programs",
+ "Agency Code": "12",
+ "Bureau Code": "22",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Office of Labor Management Standards",
+ "Agency Code": "12",
+ "Bureau Code": "23",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Occupational Safety and Health Administration",
+ "Agency Code": "12",
+ "Bureau Code": "18",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Mine Safety and Health Administration",
+ "Agency Code": "12",
+ "Bureau Code": "19",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Bureau of Labor Statistics",
+ "Agency Code": "12",
+ "Bureau Code": "20",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of Labor",
+ "Bureau Name": "Departmental Management",
+ "Agency Code": "12",
+ "Bureau Code": "25",
+ "Treasury Code": "16",
+ "CGAC Code": "16"
+ },
+ {
+ "Agency Name": "Department of State",
+ "Bureau Name": "Department of State",
+ "Agency Code": "14",
+ "Bureau Code": "0",
+ "Treasury Code": "19",
+ "CGAC Code": "19"
+ },
+ {
+ "Agency Name": "Department of State",
+ "Bureau Name": "Administration of Foreign Affairs",
+ "Agency Code": "14",
+ "Bureau Code": "5",
+ "Treasury Code": "19",
+ "CGAC Code": "19"
+ },
+ {
+ "Agency Name": "Department of State",
+ "Bureau Name": "International Organizations and Conferences",
+ "Agency Code": "14",
+ "Bureau Code": "10",
+ "Treasury Code": "19",
+ "CGAC Code": "19"
+ },
+ {
+ "Agency Name": "Department of State",
+ "Bureau Name": "International Commissions",
+ "Agency Code": "14",
+ "Bureau Code": "15",
+ "Treasury Code": "19",
+ "CGAC Code": "19"
+ },
+ {
+ "Agency Name": "Department of State",
+ "Bureau Name": "Other",
+ "Agency Code": "14",
+ "Bureau Code": "25",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Department of Transportation",
+ "Agency Code": "21",
+ "Bureau Code": "0",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Office of the Secretary",
+ "Agency Code": "21",
+ "Bureau Code": "4",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Federal Aviation Administration",
+ "Agency Code": "21",
+ "Bureau Code": "12",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Federal Highway Administration",
+ "Agency Code": "21",
+ "Bureau Code": "15",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Federal Motor Carrier Safety Administration",
+ "Agency Code": "21",
+ "Bureau Code": "17",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "National Highway Traffic Safety Administration",
+ "Agency Code": "21",
+ "Bureau Code": "18",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Federal Railroad Administration",
+ "Agency Code": "21",
+ "Bureau Code": "27",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Federal Transit Administration",
+ "Agency Code": "21",
+ "Bureau Code": "36",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Saint Lawrence Seaway Development Corporation",
+ "Agency Code": "21",
+ "Bureau Code": "40",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Pipeline and Hazardous Materials Safety Administration",
+ "Agency Code": "21",
+ "Bureau Code": "50",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Office of Inspector General",
+ "Agency Code": "21",
+ "Bureau Code": "56",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Surface Transportation Board",
+ "Agency Code": "21",
+ "Bureau Code": "61",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of Transportation",
+ "Bureau Name": "Maritime Administration",
+ "Agency Code": "21",
+ "Bureau Code": "70",
+ "Treasury Code": "69",
+ "CGAC Code": "69"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Department of the Treasury",
+ "Agency Code": "15",
+ "Bureau Code": "0",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Departmental Offices",
+ "Agency Code": "15",
+ "Bureau Code": "5",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Financial Crimes Enforcement Network",
+ "Agency Code": "15",
+ "Bureau Code": "4",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Fiscal Service",
+ "Agency Code": "15",
+ "Bureau Code": "12",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Federal Financing Bank",
+ "Agency Code": "15",
+ "Bureau Code": "11",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Alcohol and Tobacco Tax and Trade Bureau",
+ "Agency Code": "15",
+ "Bureau Code": "13",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Bureau of Engraving and Printing",
+ "Agency Code": "15",
+ "Bureau Code": "20",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "United States Mint",
+ "Agency Code": "15",
+ "Bureau Code": "25",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Internal Revenue Service",
+ "Agency Code": "15",
+ "Bureau Code": "45",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Comptroller of the Currency",
+ "Agency Code": "15",
+ "Bureau Code": "57",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of the Treasury",
+ "Bureau Name": "Interest on the Public Debt",
+ "Agency Code": "15",
+ "Bureau Code": "60",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Department of Veterans Affairs",
+ "Bureau Name": "Department of Veterans Affairs",
+ "Agency Code": "29",
+ "Bureau Code": "0",
+ "Treasury Code": "36",
+ "CGAC Code": "36"
+ },
+ {
+ "Agency Name": "Department of Veterans Affairs",
+ "Bureau Name": "Veterans Health Administration",
+ "Agency Code": "29",
+ "Bureau Code": "15",
+ "Treasury Code": "36",
+ "CGAC Code": "36"
+ },
+ {
+ "Agency Name": "Department of Veterans Affairs",
+ "Bureau Name": "Benefits Programs",
+ "Agency Code": "29",
+ "Bureau Code": "25",
+ "Treasury Code": "36",
+ "CGAC Code": "36"
+ },
+ {
+ "Agency Name": "Department of Veterans Affairs",
+ "Bureau Name": "Departmental Administration",
+ "Agency Code": "29",
+ "Bureau Code": "40",
+ "Treasury Code": "36",
+ "CGAC Code": "36"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Other Defense Civil Programs",
+ "Agency Code": "200",
+ "Bureau Code": "0",
+ "Treasury Code": "84",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Military Retirement",
+ "Agency Code": "200",
+ "Bureau Code": "5",
+ "Treasury Code": "97",
+ "CGAC Code": "97"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Retiree Health Care",
+ "Agency Code": "200",
+ "Bureau Code": "7",
+ "Treasury Code": "97",
+ "CGAC Code": "97"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Educational Benefits",
+ "Agency Code": "200",
+ "Bureau Code": "10",
+ "Treasury Code": "97",
+ "CGAC Code": "97"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "American Battle Monuments Commission",
+ "Agency Code": "200",
+ "Bureau Code": "15",
+ "Treasury Code": "74",
+ "CGAC Code": "74"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Armed Forces Retirement Home",
+ "Agency Code": "200",
+ "Bureau Code": "20",
+ "Treasury Code": "84",
+ "CGAC Code": "84"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Cemeterial Expenses",
+ "Agency Code": "200",
+ "Bureau Code": "25",
+ "Treasury Code": "21",
+ "CGAC Code": "21"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Forest and Wildlife Conservation, Military Reservations",
+ "Agency Code": "200",
+ "Bureau Code": "30",
+ "Treasury Code": "97",
+ "CGAC Code": "17"
+ },
+ {
+ "Agency Name": "Other Defense Civil Programs",
+ "Bureau Name": "Selective Service System",
+ "Agency Code": "200",
+ "Bureau Code": "45",
+ "Treasury Code": "90",
+ "CGAC Code": "90"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "International Assistance Programs",
+ "Agency Code": "184",
+ "Bureau Code": "0",
+ "Treasury Code": "72",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Millennium Challenge Corporation",
+ "Agency Code": "184",
+ "Bureau Code": "3",
+ "Treasury Code": "95",
+ "CGAC Code": "524"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "International Security Assistance",
+ "Agency Code": "184",
+ "Bureau Code": "5",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Multilateral Assistance",
+ "Agency Code": "184",
+ "Bureau Code": "10",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Agency for International Development",
+ "Agency Code": "184",
+ "Bureau Code": "15",
+ "Treasury Code": "72",
+ "CGAC Code": "72"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Overseas Private Investment Corporation",
+ "Agency Code": "184",
+ "Bureau Code": "20",
+ "Treasury Code": "71",
+ "CGAC Code": "71"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Trade and Development Agency",
+ "Agency Code": "184",
+ "Bureau Code": "25",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Peace Corps",
+ "Agency Code": "184",
+ "Bureau Code": "35",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Inter-American Foundation",
+ "Agency Code": "184",
+ "Bureau Code": "40",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "African Development Foundation",
+ "Agency Code": "184",
+ "Bureau Code": "50",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "International Monetary Programs",
+ "Agency Code": "184",
+ "Bureau Code": "60",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Military Sales Program",
+ "Agency Code": "184",
+ "Bureau Code": "70",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Special Assistance Initiatives",
+ "Agency Code": "184",
+ "Bureau Code": "75",
+ "Treasury Code": "72",
+ "CGAC Code": "72"
+ },
+ {
+ "Agency Name": "International Assistance Programs",
+ "Bureau Name": "Foreign Assistance Program Allowances",
+ "Agency Code": "184",
+ "Bureau Code": "95",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Executive Office of the President",
+ "Agency Code": "100",
+ "Bureau Code": "0",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "The White House",
+ "Agency Code": "100",
+ "Bureau Code": "5",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Executive Residence at the White House",
+ "Agency Code": "100",
+ "Bureau Code": "10",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Special Assistance to the President and the Official Residence of the Vice President",
+ "Agency Code": "100",
+ "Bureau Code": "15",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Council of Economic Advisers",
+ "Agency Code": "100",
+ "Bureau Code": "20",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Council on Environmental Quality and Office of Environmental Quality",
+ "Agency Code": "100",
+ "Bureau Code": "25",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "National Security Council and Homeland Security Council",
+ "Agency Code": "100",
+ "Bureau Code": "35",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Office of Administration",
+ "Agency Code": "100",
+ "Bureau Code": "50",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Office of Management and Budget",
+ "Agency Code": "100",
+ "Bureau Code": "55",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Office of National Drug Control Policy",
+ "Agency Code": "100",
+ "Bureau Code": "60",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Office of Science and Technology Policy",
+ "Agency Code": "100",
+ "Bureau Code": "65",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Office of the United States Trade Representative",
+ "Agency Code": "100",
+ "Bureau Code": "70",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Executive Office of the President",
+ "Bureau Name": "Unanticipated Needs",
+ "Agency Code": "100",
+ "Bureau Code": "95",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Corps of Engineers - Civil Works",
+ "Bureau Name": "Corps of Engineers - Civil Works",
+ "Agency Code": "202",
+ "Bureau Code": "0",
+ "Treasury Code": "96",
+ "CGAC Code": "96"
+ },
+ {
+ "Agency Name": "Environmental Protection Agency",
+ "Bureau Name": "Environmental Protection Agency",
+ "Agency Code": "20",
+ "Bureau Code": "0",
+ "Treasury Code": "68",
+ "CGAC Code": "68"
+ },
+ {
+ "Agency Name": "General Services Administration",
+ "Bureau Name": "General Services Administration",
+ "Agency Code": "23",
+ "Bureau Code": "0",
+ "Treasury Code": "47",
+ "CGAC Code": "47"
+ },
+ {
+ "Agency Name": "General Services Administration",
+ "Bureau Name": "Real Property Activities",
+ "Agency Code": "23",
+ "Bureau Code": "5",
+ "Treasury Code": "47",
+ "CGAC Code": "47"
+ },
+ {
+ "Agency Name": "General Services Administration",
+ "Bureau Name": "Supply and Technology Activities",
+ "Agency Code": "23",
+ "Bureau Code": "10",
+ "Treasury Code": "47",
+ "CGAC Code": "47"
+ },
+ {
+ "Agency Name": "General Services Administration",
+ "Bureau Name": "General Activities",
+ "Agency Code": "23",
+ "Bureau Code": "30",
+ "Treasury Code": "47",
+ "CGAC Code": "47"
+ },
+ {
+ "Agency Name": "National Aeronautics and Space Administration",
+ "Bureau Name": "National Aeronautics and Space Administration",
+ "Agency Code": "26",
+ "Bureau Code": "0",
+ "Treasury Code": "80",
+ "CGAC Code": "80"
+ },
+ {
+ "Agency Name": "National Science Foundation",
+ "Bureau Name": "National Science Foundation",
+ "Agency Code": "422",
+ "Bureau Code": "0",
+ "Treasury Code": "49",
+ "CGAC Code": "49"
+ },
+ {
+ "Agency Name": "Office of Personnel Management",
+ "Bureau Name": "Office of Personnel Management",
+ "Agency Code": "27",
+ "Bureau Code": "0",
+ "Treasury Code": "24",
+ "CGAC Code": "24"
+ },
+ {
+ "Agency Name": "Small Business Administration",
+ "Bureau Name": "Small Business Administration",
+ "Agency Code": "28",
+ "Bureau Code": "0",
+ "Treasury Code": "73",
+ "CGAC Code": "73"
+ },
+ {
+ "Agency Name": "Social Security Administration",
+ "Bureau Name": "Social Security Administration",
+ "Agency Code": "16",
+ "Bureau Code": "0",
+ "Treasury Code": "28",
+ "CGAC Code": "28"
+ },
+ {
+ "Agency Name": "Access Board",
+ "Bureau Name": "Access Board",
+ "Agency Code": "310",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "310"
+ },
+ {
+ "Agency Name": "Administrative Conference of the United States",
+ "Bureau Name": "Administrative Conference of the United States",
+ "Agency Code": "302",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "302"
+ },
+ {
+ "Agency Name": "Advisory Council on Historic Preservation",
+ "Bureau Name": "Advisory Council on Historic Preservation",
+ "Agency Code": "306",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "306"
+ },
+ {
+ "Agency Name": "Affordable Housing Program",
+ "Bureau Name": "Affordable Housing Program",
+ "Agency Code": "530",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Appalachian Regional Commission",
+ "Bureau Name": "Appalachian Regional Commission",
+ "Agency Code": "309",
+ "Bureau Code": "0",
+ "Treasury Code": "46",
+ "CGAC Code": "309"
+ },
+ {
+ "Agency Name": "Barry Goldwater Scholarship and Excellence in Education Foundation",
+ "Bureau Name": "Barry Goldwater Scholarship and Excellence in Education Foundation",
+ "Agency Code": "313",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "313"
+ },
+ {
+ "Agency Name": "Broadcasting Board of Governors",
+ "Bureau Name": "Broadcasting Board of Governors",
+ "Agency Code": "514",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "514"
+ },
+ {
+ "Agency Name": "Bureau of Consumer Financial Protection",
+ "Bureau Name": "Bureau of Consumer Financial Protection",
+ "Agency Code": "581",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "581"
+ },
+ {
+ "Agency Name": "Central Intelligence Agency",
+ "Bureau Name": "Central Intelligence Agency",
+ "Agency Code": "316",
+ "Bureau Code": "0",
+ "Treasury Code": "56",
+ "CGAC Code": "56"
+ },
+ {
+ "Agency Name": "Chemical Safety and Hazard Investigation Board",
+ "Bureau Name": "Chemical Safety and Hazard Investigation Board",
+ "Agency Code": "510",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "510"
+ },
+ {
+ "Agency Name": "Christopher Columbus Fellowship Foundation",
+ "Bureau Name": "Christopher Columbus Fellowship Foundation",
+ "Agency Code": "465",
+ "Bureau Code": "0",
+ "Treasury Code": "76",
+ "CGAC Code": "465"
+ },
+ {
+ "Agency Name": "Civilian Property Realignment Board",
+ "Bureau Name": "Civilian Property Realignment Board",
+ "Agency Code": "582",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Commission of Fine Arts",
+ "Bureau Name": "Commission of Fine Arts",
+ "Agency Code": "323",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "323"
+ },
+ {
+ "Agency Name": "Commission on Civil Rights",
+ "Bureau Name": "Commission on Civil Rights",
+ "Agency Code": "326",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "326"
+ },
+ {
+ "Agency Name": "Committee for Purchase from People who are Blind or Severely Disabled, activities",
+ "Bureau Name": "Committee for Purchase from People who are Blind or Severely Disabled, activities",
+ "Agency Code": "338",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "338"
+ },
+ {
+ "Agency Name": "Commodity Futures Trading Commission",
+ "Bureau Name": "Commodity Futures Trading Commission",
+ "Agency Code": "339",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "339"
+ },
+ {
+ "Agency Name": "Consumer Product Safety Commission",
+ "Bureau Name": "Consumer Product Safety Commission",
+ "Agency Code": "343",
+ "Bureau Code": "0",
+ "Treasury Code": "61",
+ "CGAC Code": "61"
+ },
+ {
+ "Agency Name": "Corporation for National and Community Service",
+ "Bureau Name": "Corporation for National and Community Service",
+ "Agency Code": "485",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "485"
+ },
+ {
+ "Agency Name": "Corporation for Public Broadcasting",
+ "Bureau Name": "Corporation for Public Broadcasting",
+ "Agency Code": "344",
+ "Bureau Code": "0",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Corporation for Travel Promotion",
+ "Bureau Name": "Corporation for Travel Promotion",
+ "Agency Code": "580",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "580"
+ },
+ {
+ "Agency Name": "Council of the Inspectors General on Integrity and Efficiency",
+ "Bureau Name": "Council of the Inspectors General on Integrity and Efficiency",
+ "Agency Code": "542",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "542"
+ },
+ {
+ "Agency Name": "Court Services and Offender Supervision Agency for the District of Columbia",
+ "Bureau Name": "Court Services and Offender Supervision Agency for the District of Columbia",
+ "Agency Code": "511",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "511"
+ },
+ {
+ "Agency Name": "Defense Nuclear Facilities Safety Board",
+ "Bureau Name": "Defense Nuclear Facilities Safety Board",
+ "Agency Code": "347",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "347"
+ },
+ {
+ "Agency Name": "Delta Regional Authority",
+ "Bureau Name": "Delta Regional Authority",
+ "Agency Code": "517",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "517"
+ },
+ {
+ "Agency Name": "Denali Commission",
+ "Bureau Name": "Denali Commission",
+ "Agency Code": "513",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "513"
+ },
+ {
+ "Agency Name": "District of Columbia",
+ "Bureau Name": "District of Columbia Courts",
+ "Agency Code": "349",
+ "Bureau Code": "10",
+ "Treasury Code": "95",
+ "CGAC Code": "349"
+ },
+ {
+ "Agency Name": "District of Columbia",
+ "Bureau Name": "District of Columbia General and Special Payments",
+ "Agency Code": "349",
+ "Bureau Code": "30",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Election Assistance Commission",
+ "Bureau Name": "Election Assistance Commission",
+ "Agency Code": "525",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "525"
+ },
+ {
+ "Agency Name": "Electric Reliability Organization",
+ "Bureau Name": "Electric Reliability Organization",
+ "Agency Code": "531",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Equal Employment Opportunity Commission",
+ "Bureau Name": "Equal Employment Opportunity Commission",
+ "Agency Code": "350",
+ "Bureau Code": "0",
+ "Treasury Code": "45",
+ "CGAC Code": "45"
+ },
+ {
+ "Agency Name": "Export-Import Bank of the United States",
+ "Bureau Name": "Export-Import Bank of the United States",
+ "Agency Code": "351",
+ "Bureau Code": "0",
+ "Treasury Code": "83",
+ "CGAC Code": "83"
+ },
+ {
+ "Agency Name": "Farm Credit Administration",
+ "Bureau Name": "Farm Credit Administration",
+ "Agency Code": "352",
+ "Bureau Code": "0",
+ "Treasury Code": "78",
+ "CGAC Code": "352"
+ },
+ {
+ "Agency Name": "Farm Credit System Insurance Corporation",
+ "Bureau Name": "Farm Credit System Insurance Corporation",
+ "Agency Code": "355",
+ "Bureau Code": "0",
+ "Treasury Code": "78",
+ "CGAC Code": "352"
+ },
+ {
+ "Agency Name": "Federal Communications Commission",
+ "Bureau Name": "Federal Communications Commission",
+ "Agency Code": "356",
+ "Bureau Code": "0",
+ "Treasury Code": "27",
+ "CGAC Code": "27"
+ },
+ {
+ "Agency Name": "Federal Deposit Insurance Corporation",
+ "Bureau Name": "Deposit Insurance",
+ "Agency Code": "357",
+ "Bureau Code": "20",
+ "Treasury Code": "51",
+ "CGAC Code": "51"
+ },
+ {
+ "Agency Name": "Federal Deposit Insurance Corporation",
+ "Bureau Name": "FSLIC Resolution",
+ "Agency Code": "357",
+ "Bureau Code": "30",
+ "Treasury Code": "51",
+ "CGAC Code": "51"
+ },
+ {
+ "Agency Name": "Federal Deposit Insurance Corporation",
+ "Bureau Name": "Orderly Liquidation",
+ "Agency Code": "357",
+ "Bureau Code": "35",
+ "Treasury Code": "51",
+ "CGAC Code": "51"
+ },
+ {
+ "Agency Name": "Federal Deposit Insurance Corporation",
+ "Bureau Name": "FDIC - Office of Inspector General",
+ "Agency Code": "357",
+ "Bureau Code": "40",
+ "Treasury Code": "51",
+ "CGAC Code": "51"
+ },
+ {
+ "Agency Name": "Federal Drug Control Programs",
+ "Bureau Name": "Federal Drug Control Programs",
+ "Agency Code": "154",
+ "Bureau Code": "0",
+ "Treasury Code": "11",
+ "CGAC Code": "11"
+ },
+ {
+ "Agency Name": "Federal Election Commission",
+ "Bureau Name": "Federal Election Commission",
+ "Agency Code": "360",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "360"
+ },
+ {
+ "Agency Name": "Federal Financial Institutions Examination Council",
+ "Bureau Name": "Federal Financial Institutions Examination Council",
+ "Agency Code": "362",
+ "Bureau Code": "10",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Federal Financial Institutions Examination Council",
+ "Bureau Name": "Federal Financial Institutions Examination Council Appraisal Subcommittee",
+ "Agency Code": "362",
+ "Bureau Code": "20",
+ "Treasury Code": "95",
+ "CGAC Code": "362"
+ },
+ {
+ "Agency Name": "Federal Housing Finance Agency",
+ "Bureau Name": "Federal Housing Finance Agency",
+ "Agency Code": "537",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "537"
+ },
+ {
+ "Agency Name": "Federal Labor Relations Authority",
+ "Bureau Name": "Federal Labor Relations Authority",
+ "Agency Code": "365",
+ "Bureau Code": "0",
+ "Treasury Code": "54",
+ "CGAC Code": "54"
+ },
+ {
+ "Agency Name": "Federal Maritime Commission",
+ "Bureau Name": "Federal Maritime Commission",
+ "Agency Code": "366",
+ "Bureau Code": "0",
+ "Treasury Code": "65",
+ "CGAC Code": "65"
+ },
+ {
+ "Agency Name": "Federal Mediation and Conciliation Service",
+ "Bureau Name": "Federal Mediation and Conciliation Service",
+ "Agency Code": "367",
+ "Bureau Code": "0",
+ "Treasury Code": "93",
+ "CGAC Code": "93"
+ },
+ {
+ "Agency Name": "Federal Mine Safety and Health Review Commission",
+ "Bureau Name": "Federal Mine Safety and Health Review Commission",
+ "Agency Code": "368",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "368"
+ },
+ {
+ "Agency Name": "Federal Retirement Thrift Investment Board",
+ "Bureau Name": "Federal Retirement Thrift Investment Board",
+ "Agency Code": "369",
+ "Bureau Code": "0",
+ "Treasury Code": "26",
+ "CGAC Code": "26"
+ },
+ {
+ "Agency Name": "Federal Trade Commission",
+ "Bureau Name": "Federal Trade Commission",
+ "Agency Code": "370",
+ "Bureau Code": "0",
+ "Treasury Code": "29",
+ "CGAC Code": "29"
+ },
+ {
+ "Agency Name": "Gulf Coast Ecosystem Restoration Council",
+ "Bureau Name": "Gulf Coast Ecosystem Restoration Council",
+ "Agency Code": "586",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "471"
+ },
+ {
+ "Agency Name": "Harry S Truman Scholarship Foundation",
+ "Bureau Name": "Harry S Truman Scholarship Foundation",
+ "Agency Code": "372",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "372"
+ },
+ {
+ "Agency Name": "Independent Payment Advisory Board",
+ "Bureau Name": "Independent Payment Advisory Board",
+ "Agency Code": "578",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Indian Law and Order Commission",
+ "Bureau Name": "Indian Law and Order Commission",
+ "Agency Code": "584",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "584"
+ },
+ {
+ "Agency Name": "Institute of American Indian and Alaska Native Culture and Arts Development",
+ "Bureau Name": "Institute of American Indian and Alaska Native Culture and Arts Development",
+ "Agency Code": "373",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "373"
+ },
+ {
+ "Agency Name": "Institute of Museum and Library Services",
+ "Bureau Name": "Institute of Museum and Library Services",
+ "Agency Code": "474",
+ "Bureau Code": "0",
+ "Treasury Code": "59",
+ "CGAC Code": "417"
+ },
+ {
+ "Agency Name": "Intelligence Community Management Account",
+ "Bureau Name": "Intelligence Community Management Account",
+ "Agency Code": "467",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "467"
+ },
+ {
+ "Agency Name": "International Trade Commission",
+ "Bureau Name": "International Trade Commission",
+ "Agency Code": "378",
+ "Bureau Code": "0",
+ "Treasury Code": "34",
+ "CGAC Code": "34"
+ },
+ {
+ "Agency Name": "James Madison Memorial Fellowship Foundation",
+ "Bureau Name": "James Madison Memorial Fellowship Foundation",
+ "Agency Code": "381",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "381"
+ },
+ {
+ "Agency Name": "Japan-United States Friendship Commission",
+ "Bureau Name": "Japan-United States Friendship Commission",
+ "Agency Code": "382",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "382"
+ },
+ {
+ "Agency Name": "Legal Services Corporation",
+ "Bureau Name": "Legal Services Corporation",
+ "Agency Code": "385",
+ "Bureau Code": "0",
+ "Treasury Code": "20",
+ "CGAC Code": "20"
+ },
+ {
+ "Agency Name": "Marine Mammal Commission",
+ "Bureau Name": "Marine Mammal Commission",
+ "Agency Code": "387",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "387"
+ },
+ {
+ "Agency Name": "Merit Systems Protection Board",
+ "Bureau Name": "Merit Systems Protection Board",
+ "Agency Code": "389",
+ "Bureau Code": "0",
+ "Treasury Code": "41",
+ "CGAC Code": "389"
+ },
+ {
+ "Agency Name": "Military Compensation and Retirement Modernization Commission",
+ "Bureau Name": "Military Compensation and Retirement Modernization Commission",
+ "Agency Code": "479",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "479"
+ },
+ {
+ "Agency Name": "Morris K. Udall and Stewart L. Udall Foundation",
+ "Bureau Name": "Morris K. Udall and Stewart L. Udall Foundation",
+ "Agency Code": "487",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "487"
+ },
+ {
+ "Agency Name": "National Archives and Records Administration",
+ "Bureau Name": "National Archives and Records Administration",
+ "Agency Code": "393",
+ "Bureau Code": "0",
+ "Treasury Code": "88",
+ "CGAC Code": "88"
+ },
+ {
+ "Agency Name": "National Capital Planning Commission",
+ "Bureau Name": "National Capital Planning Commission",
+ "Agency Code": "394",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "394"
+ },
+ {
+ "Agency Name": "National Council on Disability",
+ "Bureau Name": "National Council on Disability",
+ "Agency Code": "413",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "413"
+ },
+ {
+ "Agency Name": "National Credit Union Administration",
+ "Bureau Name": "National Credit Union Administration",
+ "Agency Code": "415",
+ "Bureau Code": "0",
+ "Treasury Code": "25",
+ "CGAC Code": "25"
+ },
+ {
+ "Agency Name": "National Endowment for the Arts",
+ "Bureau Name": "National Endowment for the Arts",
+ "Agency Code": "417",
+ "Bureau Code": "0",
+ "Treasury Code": "59",
+ "CGAC Code": "417"
+ },
+ {
+ "Agency Name": "National Endowment for the Humanities",
+ "Bureau Name": "National Endowment for the Humanities",
+ "Agency Code": "418",
+ "Bureau Code": "0",
+ "Treasury Code": "59",
+ "CGAC Code": "417"
+ },
+ {
+ "Agency Name": "National Infrastructure Bank",
+ "Bureau Name": "National Infrastructure Bank",
+ "Agency Code": "538",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "National Labor Relations Board",
+ "Bureau Name": "National Labor Relations Board",
+ "Agency Code": "420",
+ "Bureau Code": "0",
+ "Treasury Code": "63",
+ "CGAC Code": "420"
+ },
+ {
+ "Agency Name": "National Mediation Board",
+ "Bureau Name": "National Mediation Board",
+ "Agency Code": "421",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "421"
+ },
+ {
+ "Agency Name": "National Railroad Passenger Corporation Office of Inspector General",
+ "Bureau Name": "National Railroad Passenger Corporation Office of Inspector General",
+ "Agency Code": "575",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "575"
+ },
+ {
+ "Agency Name": "National Transportation Safety Board",
+ "Bureau Name": "National Transportation Safety Board",
+ "Agency Code": "424",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "424"
+ },
+ {
+ "Agency Name": "Neighborhood Reinvestment Corporation",
+ "Bureau Name": "Neighborhood Reinvestment Corporation",
+ "Agency Code": "428",
+ "Bureau Code": "0",
+ "Treasury Code": "82",
+ "CGAC Code": "82"
+ },
+ {
+ "Agency Name": "Northern Border Regional Commission",
+ "Bureau Name": "Northern Border Regional Commission",
+ "Agency Code": "573",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "573"
+ },
+ {
+ "Agency Name": "Nuclear Regulatory Commission",
+ "Bureau Name": "Nuclear Regulatory Commission",
+ "Agency Code": "429",
+ "Bureau Code": "0",
+ "Treasury Code": "31",
+ "CGAC Code": "31"
+ },
+ {
+ "Agency Name": "Nuclear Waste Technical Review Board",
+ "Bureau Name": "Nuclear Waste Technical Review Board",
+ "Agency Code": "431",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "431"
+ },
+ {
+ "Agency Name": "Occupational Safety and Health Review Commission",
+ "Bureau Name": "Occupational Safety and Health Review Commission",
+ "Agency Code": "432",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "432"
+ },
+ {
+ "Agency Name": "Office of Government Ethics",
+ "Bureau Name": "Office of Government Ethics",
+ "Agency Code": "434",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "434"
+ },
+ {
+ "Agency Name": "Office of Navajo and Hopi Indian Relocation",
+ "Bureau Name": "Office of Navajo and Hopi Indian Relocation",
+ "Agency Code": "435",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "435"
+ },
+ {
+ "Agency Name": "Office of Special Counsel",
+ "Bureau Name": "Office of Special Counsel",
+ "Agency Code": "436",
+ "Bureau Code": "0",
+ "Treasury Code": "62",
+ "CGAC Code": "62"
+ },
+ {
+ "Agency Name": "Office of the Federal Coordinator for Alaska Natural Gas Transportation Projects",
+ "Bureau Name": "Office of the Federal Coordinator for Alaska Natural Gas Transportation Projects",
+ "Agency Code": "534",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "534"
+ },
+ {
+ "Agency Name": "Other Commissions and Boards",
+ "Bureau Name": "Other Commissions and Boards",
+ "Agency Code": "505",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "377"
+ },
+ {
+ "Agency Name": "Patient-Centered Outcomes Research Trust Fund",
+ "Bureau Name": "Patient-Centered Outcomes Research Trust Fund",
+ "Agency Code": "579",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "579"
+ },
+ {
+ "Agency Name": "Postal Service",
+ "Bureau Name": "Postal Service",
+ "Agency Code": "440",
+ "Bureau Code": "0",
+ "Treasury Code": "18",
+ "CGAC Code": "18"
+ },
+ {
+ "Agency Name": "Presidio Trust",
+ "Bureau Name": "Presidio Trust",
+ "Agency Code": "512",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "512"
+ },
+ {
+ "Agency Name": "Privacy and Civil Liberties Oversight Board",
+ "Bureau Name": "Privacy and Civil Liberties Oversight Board",
+ "Agency Code": "535",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "535"
+ },
+ {
+ "Agency Name": "Public Company Accounting Oversight Board",
+ "Bureau Name": "Public Company Accounting Oversight Board",
+ "Agency Code": "526",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Public Defender Service for the District of Columbia",
+ "Bureau Name": "Public Defender Service for the District of Columbia",
+ "Agency Code": "587",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "511"
+ },
+ {
+ "Agency Name": "Railroad Retirement Board",
+ "Bureau Name": "Railroad Retirement Board",
+ "Agency Code": "446",
+ "Bureau Code": "0",
+ "Treasury Code": "60",
+ "CGAC Code": "60"
+ },
+ {
+ "Agency Name": "Recovery Act Accountability and Transparency Board",
+ "Bureau Name": "Recovery Act Accountability and Transparency Board",
+ "Agency Code": "539",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "539"
+ },
+ {
+ "Agency Name": "Securities and Exchange Commission",
+ "Bureau Name": "Securities and Exchange Commission",
+ "Agency Code": "449",
+ "Bureau Code": "0",
+ "Treasury Code": "50",
+ "CGAC Code": "50"
+ },
+ {
+ "Agency Name": "Standard Setting Body",
+ "Bureau Name": "Standard Setting Body",
+ "Agency Code": "527",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Securities Investor Protection Corporation",
+ "Bureau Name": "Securities Investor Protection Corporation",
+ "Agency Code": "576",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "Smithsonian Institution",
+ "Bureau Name": "Smithsonian Institution",
+ "Agency Code": "452",
+ "Bureau Code": "0",
+ "Treasury Code": "33",
+ "CGAC Code": "33"
+ },
+ {
+ "Agency Name": "State Justice Institute",
+ "Bureau Name": "State Justice Institute",
+ "Agency Code": "453",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "453"
+ },
+ {
+ "Agency Name": "Tennessee Valley Authority",
+ "Bureau Name": "Tennessee Valley Authority",
+ "Agency Code": "455",
+ "Bureau Code": "0",
+ "Treasury Code": "64",
+ "CGAC Code": "455"
+ },
+ {
+ "Agency Name": "United Mine Workers of America Benefit Funds",
+ "Bureau Name": "United Mine Workers of America Benefit Funds",
+ "Agency Code": "476",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "n/a"
+ },
+ {
+ "Agency Name": "United States Court of Appeals for Veterans Claims",
+ "Bureau Name": "United States Court of Appeals for Veterans Claims",
+ "Agency Code": "345",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "345"
+ },
+ {
+ "Agency Name": "United States Enrichment Corporation Fund",
+ "Bureau Name": "United States Enrichment Corporation Fund",
+ "Agency Code": "486",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "486"
+ },
+ {
+ "Agency Name": "United States Holocaust Memorial Museum",
+ "Bureau Name": "United States Holocaust Memorial Museum",
+ "Agency Code": "456",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "456"
+ },
+ {
+ "Agency Name": "United States Institute of Peace",
+ "Bureau Name": "United States Institute of Peace",
+ "Agency Code": "458",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "458"
+ },
+ {
+ "Agency Name": "United States Interagency Council on Homelessness",
+ "Bureau Name": "United States Interagency Council on Homelessness",
+ "Agency Code": "376",
+ "Bureau Code": "0",
+ "Treasury Code": "48",
+ "CGAC Code": "376"
+ },
+ {
+ "Agency Name": "Vietnam Education Foundation",
+ "Bureau Name": "Vietnam Education Foundation",
+ "Agency Code": "519",
+ "Bureau Code": "0",
+ "Treasury Code": "95",
+ "CGAC Code": "519"
+ },
+ {
+ "Agency Name": "Federal National Mortgage Association",
+ "Bureau Name": "Federal National Mortgage Association",
+ "Agency Code": "915",
+ "Bureau Code": "0",
+ "Treasury Code": "39",
+ "CGAC Code": "915"
+ },
+ {
+ "Agency Name": "Federal Home Loan Mortgage Corporation",
+ "Bureau Name": "Federal Home Loan Mortgage Corporation",
+ "Agency Code": "914",
+ "Bureau Code": "0",
+ "Treasury Code": "39",
+ "CGAC Code": "914"
+ },
+ {
+ "Agency Name": "Federal Home Loan Bank System",
+ "Bureau Name": "Federal Home Loan Bank System",
+ "Agency Code": "913",
+ "Bureau Code": "0",
+ "Treasury Code": "39",
+ "CGAC Code": "913"
+ },
+ {
+ "Agency Name": "Farm Credit System",
+ "Bureau Name": "Farm Credit System",
+ "Agency Code": "912",
+ "Bureau Code": "0",
+ "Treasury Code": "39",
+ "CGAC Code": "912"
+ },
+ {
+ "Agency Name": "Financing Vehicles and the Board of Governors of the Federal Reserve",
+ "Bureau Name": "Financing Vehicles and the Board of Governors of the Federal Reserve",
+ "Agency Code": "920",
+ "Bureau Code": "0",
+ "Treasury Code": "39",
+ "CGAC Code": "920"
+ }
+]
+
+export default AGENCY_BUREAU_DATA;
diff --git a/src/data/agency_bureau_options.js b/src/data/agency_bureau_options.js
new file mode 100644
index 0000000..9cbff25
--- /dev/null
+++ b/src/data/agency_bureau_options.js
@@ -0,0 +1,11 @@
+import AGENCY_BUREAU_DATA from './agency_bureau_data';
+
+const OptionsByKey = (key) => {
+ return Object.keys(AGENCY_BUREAU_DATA.reduce((acc, obj) => {
+ acc[obj[key]] = null;
+ return acc
+ }, {})).sort().map(val => ({ label: val, value: val}));
+}
+
+export const AGENCY_OPTIONS = OptionsByKey("Agency Name");
+export const BUREAU_OPTIONS = OptionsByKey("Bureau Name");
diff --git a/src/data/api.js b/src/data/api.js
new file mode 100644
index 0000000..31cc3ab
--- /dev/null
+++ b/src/data/api.js
@@ -0,0 +1,5 @@
+export const API_DOMAIN = 'https://api.gsa.gov/technology/site-scanning';
+
+export const API_PATH = '/v1/websites';
+
+export const API_KEY = 'DEMO_KEY';
diff --git a/src/data/field-category-order.js b/src/data/field-category-order.js
new file mode 100644
index 0000000..75caeb1
--- /dev/null
+++ b/src/data/field-category-order.js
@@ -0,0 +1,10 @@
+const FIELD_CATEGORY_ORDER = {
+ "Website" : 1,
+ "USWDS" : 2,
+ "DAP" : 3,
+ "Search" : 4,
+ "Robots": 5,
+ "Sitemap": 6,
+}
+
+export default FIELD_CATEGORY_ORDER;
diff --git a/src/data/fields.js b/src/data/fields.js
new file mode 100644
index 0000000..265696a
--- /dev/null
+++ b/src/data/fields.js
@@ -0,0 +1,328 @@
+import * as OPTIONS from './agency_bureau_options';
+
+const FIELD_OPTIONS = {
+ // Website
+ target_url: {
+ live: true,
+ attribute: 'target_url',
+ title: 'Target Url',
+ order: 0,
+ category: 'Website',
+ query_type: 'equals',
+ input: 'text',
+
+ },
+ target_url_domain: {
+ live: true,
+ attribute: 'target_url_domain',
+ title: 'Target Url Domain',
+ order: 1,
+ category: 'Website',
+ query_type: 'equals',
+ input: 'text',
+ },
+ final_url_domain: {
+ live: true,
+ attribute: 'final_url_domain',
+ title: 'Final URL - Base Domain',
+ order: 2,
+ category: 'Website',
+ query_type: 'equals',
+ input: 'text',
+ },
+ final_url_MIMETYPE: {
+ attribute: 'final_url_MIMETYPE',
+ title: 'Final Url MIMEType',
+ order: 3,
+ category: 'Website',
+ },
+ final_url_live: {
+ live: true,
+ attribute: 'final_url_live',
+ title: 'Final Url is Live',
+ order: 4,
+ category: 'Website',
+ query_type: 'boolean',
+ input: 'select',
+ input_options: [
+ { label: 'true', value: 'true' },
+ { label: 'false', value: 'false' },
+ ],
+ },
+ target_url_redirects: {
+ live: true,
+ attribute: 'target_url_redirects',
+ title: 'Target URL - Redirects',
+ order: 5,
+ category: 'Website',
+ query_type: 'boolean',
+ input: 'select',
+ input_options: [
+ { label: 'true', value: 'true' },
+ { label: 'false', value: 'false' },
+ ],
+ },
+ final_url_same_domain: {
+ attribute: 'final_url_same_domain',
+ title: 'Final URL/Target URL - Same Base Domain',
+ order: 6,
+ category: 'Website',
+ },
+ final_url_same_website: {
+ attribute: 'final_url_same_website',
+ title: 'Final URL/Target URL - Same Website',
+ order: 7,
+ category: 'Website',
+
+ },
+ target_url_agency_owner: {
+ live: true,
+ attribute: 'target_url_agency_owner',
+ title: 'Target URL Base Domain - Agency Owner',
+ order: 8,
+ category: 'Website',
+ query_type: 'equals',
+ input: 'select',
+ input_options: OPTIONS.AGENCY_OPTIONS,
+ },
+ target_url_bureau_owner: {
+ attribute: 'target_url_bureau_owner',
+ title: 'Target URL Base Domain - Bureau Owner',
+ order: 9,
+ category: 'Website',
+ query_type: 'equals',
+ input: 'select',
+ input_options: OPTIONS.BUREAU_OPTIONS,
+ live: true,
+ },
+ final_url_status_code: {
+ attribute: 'final_url_status_code',
+ title: 'Final URL - Status Code',
+ order: 10,
+ category: 'Website',
+ },
+ target_url_404_test: {
+ attribute: 'target_url_404_test',
+ title: 'Target URL - 404 Test',
+ order: 11,
+ category: 'Website',
+ },
+ scan_status: {
+ live: true,
+ attribute: 'scan_status',
+ title: 'Scan Status',
+ order: 12,
+ category: 'Website',
+ query_type: 'equals',
+ input: 'select',
+ input_options: [
+ { label: 'Success', value: 'Success' },
+ { label: 'Timeout', value: 'Timeout' },
+ { label: 'Completed', value: 'Completed' },
+ { label: 'DNS resolution error', value: 'DNS resolution error' },
+ { label: 'General scanner error', value: 'General scanner error' },
+ ],
+ },
+ scan_date: {
+ attribute: 'scan_date',
+ title: 'Scan Date',
+ order: 13,
+ category: 'Website',
+ },
+
+ // USWDS
+ uswds_favicon_detected: {
+ attribute: 'uswds_favicon_detected',
+ title: 'Favicon',
+ category: 'USWDS',
+ order: 0,
+ },
+ uswds_favicon_in_css_detected: {
+ attribute: 'uswds_favicon_in_css_detected',
+ title: 'Favicon in CSS',
+ category: 'USWDS',
+ order: 1,
+ },
+ uswds_merriweather_font_detected: {
+ attribute: 'uswds_merriweather_font_detected',
+ title: 'Merriweather Font',
+ category: 'USWDS',
+ order: 2,
+ },
+ uswds_publicsans_font_detected: {
+ attribute: 'uswds_publicsans_font_detected',
+ title: 'Public Sans Font',
+ category: 'USWDS',
+ order: 3,
+ },
+ uswds_source_sans_font_detected: {
+ attribute: 'uswds_source_sans_font_detected',
+ title: 'Source Sans Font',
+ category: 'USWDS',
+ order: 3,
+ },
+ uswds_tables_detected: {
+ attribute: 'uswds_tables_detected',
+ title: 'Tables',
+ category: 'USWDS',
+ order: 4,
+ },
+ uswds_count: {
+ attribute: 'uswds_count',
+ title: 'Count',
+ category: 'USWDS',
+ order: 5,
+ },
+ uswds_usa_classes_detected: {
+ attribute: 'uswds_usa_classes_detected',
+ title: 'USA Classes',
+ category: 'USWDS',
+ order: 6,
+ },
+ uswds_usa_detected: {
+ attribute: 'uswds_usa_detected',
+ title: 'USA',
+ category: 'USWDS',
+ order: 7,
+ },
+ uswds_string_detected: {
+ attribute: 'uswds_string_detected',
+ title: 'String',
+ category: 'USWDS',
+ order: 8,
+ },
+ uswds_string_in_css_detected: {
+ attribute: 'uswds_string_in_css_detected',
+ title: 'String in CSS',
+ category: 'USWDS',
+ order: 9,
+ },
+
+ // DAP
+ dap_detected_final_url: {
+ live: true,
+ attribute: 'dap_detected_final_url',
+ title: 'Final URL - DAP Detected',
+ category: 'Website',
+ order: 0,
+ query_type: 'boolean',
+ input: 'select',
+ input_options: [
+ { label: 'True', value: 'True' },
+ { label: 'False', value: 'False' },
+ ],
+ },
+ dap_parameters_final_url: {
+ attribute: 'dap_parameters_final_url',
+ title: 'Parameters - Final URL',
+ category: 'DAP',
+ order: 1,
+ },
+
+ // Search
+ og_date_final_url: {
+ attribute: 'og_date_final_url',
+ title: 'SEO - og:date - Final URL',
+ category: 'Search',
+ order: 2,
+ },
+ og_title_final_url: {
+ attribute: 'og_title_final_url',
+ title: 'SEO - og:title - Final URL',
+ category: 'Search',
+ order: 3,
+ },
+ og_description_final_url: {
+ attribute: 'og_description_final_url',
+ title: 'SEO - og:description - Final URL',
+ category: 'Search',
+ order: 4,
+ },
+ main_element_final_url: {
+ attribute: 'main_element_final_url',
+ title: 'SEO - Main Element - Final URL',
+ category: 'Search',
+ order: 5,
+ },
+ robots_txt_final_url: {
+ attribute: 'robots_txt_final_url',
+ title: 'Robots.txt - Final URL',
+ category: 'Search',
+ order: 6,
+ },
+ robots_txt_final_url_live: {
+ attribute: 'robots_txt_final_url_live',
+ title: 'Robots.txt - Final URL - Live',
+ category: 'Search',
+ order: 7,
+ },
+ robots_txt_target_url_redirects: {
+ attribute: 'robots_txt_target_url_redirects',
+ title: 'Robots.txt - Target URL - Redirects',
+ category: 'Search',
+ order: 8,
+ },
+ robots_txt_final_url_filesize: {
+ attribute: 'robots_txt_final_url_filesize',
+ title: 'Robots.txt - Final URL - Filesize',
+ category: 'Search',
+ order: 9,
+ },
+ robots_txt_final_url_MIMEtype: {
+ attribute: 'robots_txt_final_url_MIMEtype',
+ title: 'Robots.txt - Final URL - MIMEtype',
+ category: 'Search',
+ order: 10,
+ },
+ robots_txt_crawl_delay: {
+ attribute: 'robots_txt_crawl_delay',
+ title: 'Robots.txt - Crawl Delay',
+ category: 'Search',
+ order: 11,
+ },
+ robots_txt_sitemap_locations: {
+ attribute: 'robots_txt_sitemap_locations',
+ title: 'Robots.txt - Sitemap Locations',
+ category: 'Search',
+ order: 12,
+ },
+ sitemap_xml_final_url_live: {
+ attribute: 'sitemap_xml_final_url_live',
+ title: 'Sitemap.xml - Final URL - Live',
+ category: 'Search',
+ order: 13,
+ },
+ sitemap_xml_target_url_redirects: {
+ attribute: 'sitemap_xml_target_url_redirects',
+ title: 'Sitemap.xml - Target URL - Redirects',
+ category: 'Search',
+ order: 14,
+ },
+ sitemap_xml_final_url_filesize: {
+ attribute: 'sitemap_xml_final_url_filesize',
+ title: 'Sitemap.xml - Final URL - Filesize',
+ category: 'Search',
+ order: 15,
+ },
+ sitemap_xml_final_url_MIMEtype: {
+ attribute: 'sitemap_xml_final_url_MIMEtype',
+ title: 'Sitemap.xml - MIMEtype',
+ category: 'Search',
+ order: 16,
+ },
+ sitemap_xml_count: {
+ attribute: 'sitemap_xml_count',
+ title: 'Sitemap.xml - Items Count',
+ category: 'Search',
+ order: 17,
+ },
+ sitemap_xml_pdf_count: {
+ attribute: 'sitemap_xml_pdf_count',
+ title: 'Sitemap.xml - PDF Count',
+ category: 'Search',
+ order: 18,
+ }
+
+}
+
+export default FIELD_OPTIONS
diff --git a/src/data/links.js b/src/data/links.js
new file mode 100644
index 0000000..2e7c525
--- /dev/null
+++ b/src/data/links.js
@@ -0,0 +1,5 @@
+export const GOOGLE_SHEETS_LINK = 'https://docs.google.com/spreadsheets/d/171q7B-1X_gDfv_9VKiPu8K4j5KYaAfh90u1Vl6JMzfU/copy';
+
+export const EXCEL_LINK = 'http://example.com/excel';
+
+export const TERMS_LINK = 'https://github.com/18F/site-scanning-documentation/blob/main/about/terms.md';
diff --git a/src/pages/404.js b/src/pages/404.js
index a152c70..603f9da 100644
--- a/src/pages/404.js
+++ b/src/pages/404.js
@@ -1,13 +1,16 @@
-import React from 'react';
-
-import Layout from '../components/layout';
-import SEO from '../components/seo';
+import React from 'react';
+import { Link } from 'gatsby';
+import Layout from '../components/layout';
+import SEO from '../components/seo';
const NotFoundPage = () => (
-
- NOT FOUND
- You just hit a route that doesn't exist... the sadness.
+
+
+
404
+
Your page cannot be found.
+
Return to Homepage
+
);
diff --git a/src/pages/about.js b/src/pages/about.js
deleted file mode 100644
index e1c7d68..0000000
--- a/src/pages/about.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import React from 'react';
-import { Link } from 'gatsby';
-
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-const About = () => (
-
-
- About Spotlight
-
- Spotlight is a set of report pages that present data about federal government websites. It is powered by the Site Scanning program and is
- available through the support of General Service
- Administration's 10x program .
-
-
- What is the Site Scanning program?
-
- The vision of this project is to create a way for TTS to offer a low-cost, automated scanning solution so federal stakeholders can determine the best practices government websites are following and identify ways to improve website performance for the public and public servants. We envision an on-demand service that not only reduces the legwork that scanning has historically entailed, but also enriches the data for analyses available. Performance would be measured on a variety of key dimensions like mobile-friendliness, load times, responsiveness. Not only could stakeholders access this critical data, they'd also be able to copy the scan engine and build off of it for their own, customized uses. Our primary focus is on an open, automated, inexpensive, and fast scanning solution. You can learn more about the scans, data, and how the it all works at the program's {' '}
-
- documentation repository on GitHub
-
- .
-
-
- Access the Data
-
- You can access all of the Site Scanning program's data via the{' '}
- API
-
-
-);
-export default About;
diff --git a/src/pages/accessibility.js b/src/pages/accessibility.js
deleted file mode 100644
index 1ad863c..0000000
--- a/src/pages/accessibility.js
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-import Report from '../components/report';
-import ReportQueryProvider from '../components/report-query-provider';
-
-const columns = [
- { title: `Domain`, accessor: obj => obj.domain },
- { title: `Agency`, accessor: obj => obj.agency },
- {
- title: `Text Size`,
- accessor: obj => obj.data[`font-size`]?.displayValue,
- },
- {
- title: `Tap Target Size`,
- accessor: obj => obj.data[`tap-targets`]?.displayValue,
- },
- {
- title: `Images Have Alt Text`,
- accessor: obj => obj.data[`image-alt`]?.score,
- },
- {
- title: `Text Has Sufficient Color Contrast`,
- accessor: obj => obj.data[`color-contrast`]?.score,
- },
-];
-
-export default () => (
-
-
- Accessibility scan results
-
-
-
-
-);
diff --git a/src/pages/analytics.js b/src/pages/analytics.js
deleted file mode 100644
index b552ebe..0000000
--- a/src/pages/analytics.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react';
-import { Link } from 'gatsby';
-
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-import Report from '../components/report';
-import ReportQueryProvider from '../components/report-query-provider';
-
-const columns = [
- { title: `Domain`, accessor: obj => obj.domain },
- { title: `Agency`, accessor: obj => obj.agency },
- { title: `DAP Detected`, accessor: obj => obj.data.dap_detected },
- { title: `DAP Parameters`, accessor: obj => obj.data.dap_parameters },
-];
-
-export default () => (
-
-
- Analytics scan results
- This report page displays data that has been gathered by the Digital Analytics Program scan .
-
-
-
-
-);
diff --git a/src/pages/api-data b/src/pages/api-data
deleted file mode 100644
index fdd47e6..0000000
--- a/src/pages/api-data
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-import { Link } from 'gatsby';
-
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-const api-data = () => (
-
-
- Spotlight API Data
-
- test
-
-);
-export default api-data;
diff --git a/src/pages/contact-us.js b/src/pages/contact-us.js
deleted file mode 100644
index 77c0f75..0000000
--- a/src/pages/contact-us.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react';
-
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-
-const ContactUs = () => (
-
-
- Contact us
-
-
- To learn more about the Site Scanning program and the methodology for each scan, the project's documentation hub is a good place to start.
-
-
- For general questions and comments about how the program works or its scan
- results, please email the team or file an issue in the project repository .
-
-
-
-
-);
-
-export default ContactUs;
diff --git a/src/pages/critical-components.js b/src/pages/critical-components.js
deleted file mode 100644
index ae393e6..0000000
--- a/src/pages/critical-components.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react';
-import { Link } from 'gatsby';
-
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-import Report from '../components/report';
-import ReportQueryProvider from '../components/report-query-provider';
-
-const columns = [
- { title: `Domain`, accessor: obj => obj.domain },
- { title: `Agency`, accessor: obj => obj.agency },
- { title: `External Domains`, accessor: obj => obj.data.external_domains },
-];
-
-export default () => (
-
-
- Third-Party Links
-
-
-
-
-);
diff --git a/src/pages/design.js b/src/pages/design.js
deleted file mode 100644
index 3ca1863..0000000
--- a/src/pages/design.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-import SEO from '../components/seo';
-import Report from '../components/report';
-import Layout from '../components/layout';
-import ReportQueryProvider from '../components/report-query-provider';
-
-const columns = [
- { title: `Domain`, isUrl: true, accessor: obj => obj.domain },
- { title: `Agency`, accessor: obj => obj.agency },
- {
- title: `USWDS Version`,
- accessor: obj => obj.data.uswdsversion,
- },
- {
- title: `Flag Detected`,
- accessor: obj => obj.data.flag_detected,
- },
- {
- title: `Flag in CSS`,
- accessor: obj => obj.data.flagincss_detected,
- },
- {
- title: `Merriweather Font Detected`,
- accessor: obj => obj.data.merriweatherfont_detected,
- },
- {
- title: `Public Sans Font Detected`,
- accessor: obj => obj.data.publicsansfont_detected,
- },
- {
- title: `Source Sans Font Detected`,
- accessor: obj => obj.data.sourcesansfont_detected,
- },
- {
- title: `Tables`,
- accessor: obj => obj.data.tables,
- },
- {
- title: `USA Classes Detected`,
- accessor: obj => obj.data.usa_classes_detected,
- },
- {
- title: `USA Detected`,
- accessor: obj => obj.data.usa_detected,
- },
- {
- title: `USWDS Detected`,
- accessor: obj => obj.data.uswds_detected,
- },
- {
- title: `USWDS In CSS Detected`,
- accessor: obj => obj.data.uswdsincss_detected,
- },
-];
-
-export default () => (
-
-
- Design
-
- This report displays scan results for whether USWDS is implemented on a
- domain and, if so, which version.
-
-
-
-
-
-
-);
diff --git a/src/pages/index.js b/src/pages/index.js
index 38339c8..2240802 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -1,58 +1,13 @@
-import React from 'react';
+import React, { Fragment } from 'react';
import Layout from '../components/layout';
import SEO from '../components/seo';
-
-const IndexPage = () => {
- const num_domains = 35952;
- return (
- }>
-
-
- Spotlight is a set of report pages that present data about federal government websites. That means that rather than going into the weeds into any
- one particular area, you can use this data to highlight the critical
- features that most reflect overall excellence on websites:
-
-
-
- Why Spotlight?
-
-
- Scans run automatically, allowing results whenever you want
-
- Daily scan results deliver the latest data to you
-
- Scan results are in the cloud, open to the public, and exportable for
- easy government-wide collaboration
-
-
- Using the API, feed scan results directly into your government system
-
-
- How Spotlight works
-
- Select a category OR enter a URL.
- Filter and sort your results as needed.
- Export or bookmark your results to share or reference later.
- Analyze and take action where it matters most.
-
-
- );
-};
-
-const Hero = () => (
-
-
- Spotlight highlights the features contributing to any federal website's
- success, for free.
-
-
-);
+import QueryBuilder from '../components/modules/query-builder';
+
+const IndexPage = () => (
+
+
+
+
+)
export default IndexPage;
diff --git a/src/pages/performance.js b/src/pages/performance.js
deleted file mode 100644
index 590a9d5..0000000
--- a/src/pages/performance.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React from 'react';
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-import Report from '../components/report';
-import ReportQueryProvider from '../components/report-query-provider';
-
-const columns = [
- { title: `Domain`, accessor: obj => obj.domain },
- { title: `Agency`, accessor: obj => obj.agency },
- {
- title: `Page Load Time (s)`,
- accessor: obj => obj.data['speed-index']?.displayValue,
- },
- {
- title: `Total Page Weight (bytes)`,
- accessor: obj => obj.data['total-byte-weight']?.numericValue,
- },
- {
- title: `Unminified JavaScript`,
- accessor: obj => obj.data['unminified-javascript']?.displayValue,
- },
- {
- title: `Unminified CSS`,
- accessor: obj => obj.data['unminified-css']?.displayValue,
- },
- {
- title: `Uncompressed Text`,
- accessor: obj => obj.data['uses-text-compression']?.displayValue,
- },
- {
- title: `Viewport Meta Tag Present`,
- accessor: obj => obj.data.viewport?.explanation,
- defaultValue: 'Present',
- },
-];
-
-export default () => (
-
-
- Performance scan results
-
-
-
-
-);
diff --git a/src/pages/security.js b/src/pages/security.js
deleted file mode 100644
index a25b9e6..0000000
--- a/src/pages/security.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import SEO from '../components/seo';
-import Report from '../components/report';
-import Layout from '../components/layout';
-import ReportQueryProvider from '../components/report-query-provider';
-
-const columns = [
- { title: `Domain`, accessor: obj => obj.domain },
- { title: `Agency`, accessor: obj => obj.agency },
- { title: `Supports HSTS`, accessor: obj => obj.data.HSTS },
- { title: `Supports HTTPS`, accessor: obj => obj.data['HTTPS Live'] },
- { title: `Headers`, accessor: obj => obj.data.endpoints.https.headers },
-];
-export default () => (
-
-
- Security
-
- This report contains scan results pertaining to CISA requirements and 21st
- Century IDEA act security requirements
-
-
-
-
-
-);
diff --git a/src/pages/url-results.js b/src/pages/url-results.js
deleted file mode 100644
index 49f253b..0000000
--- a/src/pages/url-results.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import React from 'react';
-import { Link } from 'gatsby';
-
-import Layout from '../components/layout';
-import SEO from '../components/seo';
-
-const SecondPage = () => (
-
-
- Site Scanner scan results for analytics
-
-);
-
-export default SecondPage;
diff --git a/src/prop-types.js b/src/prop-types.js
new file mode 100644
index 0000000..f8d9b02
--- /dev/null
+++ b/src/prop-types.js
@@ -0,0 +1,27 @@
+import PropTypes from 'prop-types';
+
+export const AvailableFieldPropTypes = PropTypes.shape({
+ category: PropTypes.string.isRequired,
+ attribute: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ order: PropTypes.number,
+ query_type: PropTypes.oneOf(['equals', 'boolean']),
+ input: PropTypes.oneOf(['text', 'select']),
+ input_options: PropTypes.arrayOf(PropTypes.shape({
+ label: PropTypes.string.isRequired,
+ value: PropTypes.any.isRequired,
+ })),
+ live: PropTypes.boolean,
+});
+
+export const AvailableFieldsPropTypes = PropTypes.arrayOf(AvailableFieldPropTypes);
+
+export const SelectedFieldPropTypes = PropTypes.shape({
+ category: PropTypes.string.isRequired,
+ attribute: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ value: PropTypes.any.isRequired,
+});
+
+export const SelectedFieldsPropTypes = PropTypes.objectOf(SelectedFieldPropTypes);
+
diff --git a/src/redux/ducks/selectedFields.js b/src/redux/ducks/selectedFields.js
new file mode 100644
index 0000000..e361afa
--- /dev/null
+++ b/src/redux/ducks/selectedFields.js
@@ -0,0 +1,50 @@
+import { omit } from 'lodash';
+import { parseFieldParams } from '../../utils';
+// Actions
+export const SELECT_FIELD = 'SELECT_FIELD';
+export const UNSELECT_FIELD = 'UNSELECT_FIELD';
+export const SET_FIELD_VALUE = 'SET_FIELD_VALUE';
+
+// Action Creators
+export const selectField = (payload) => ({
+ type: SELECT_FIELD,
+ payload,
+});
+
+export const unselectField = (payload) => ({
+ type: UNSELECT_FIELD,
+ payload,
+});
+
+export const setFieldValue = (payload) => ({
+ type: SET_FIELD_VALUE,
+ payload,
+});
+
+// Reducer
+export const emptyState = {}
+export const initialState = typeof window !== `undefined` && window.location.search.length ? parseFieldParams(window.location.search) : emptyState;
+
+export default (state = initialState, action) => {
+ switch (action.type) {
+ case SELECT_FIELD:
+ return {
+ [action.payload.attribute]: action.payload,
+ ...state,
+ }
+ case UNSELECT_FIELD:
+ return {
+ ...omit(state, [action.payload.attribute])
+ }
+ case SET_FIELD_VALUE:
+ return {
+ ...state,
+ [action.payload.attribute]: {
+ ...state[action.payload.attribute],
+ ...action.payload,
+ }
+ }
+ default:
+ return state
+ }
+};
diff --git a/src/redux/ducks/selectedFields.test.js b/src/redux/ducks/selectedFields.test.js
new file mode 100644
index 0000000..349286e
--- /dev/null
+++ b/src/redux/ducks/selectedFields.test.js
@@ -0,0 +1,48 @@
+import selectedFieldsReducer, * as ducks from './selectedFields';
+
+describe('selectedFields Reducer', () => {
+ test('adds a selected field to state', function() {
+ const payload = {
+ category: 'Website',
+ attribute: 'target_url',
+ title: 'Target URL'
+ };
+ const action = ducks.selectField(payload);
+ const result = selectedFieldsReducer(ducks.initialState, action);
+ expect(result.target_url).toBe(payload);
+ });
+ test('removes a selected field from state', function() {
+ const payload = {
+ category: 'Website',
+ attribute: 'target_url',
+ title: 'Target URL'
+ };
+ const action = ducks.unselectField(payload);
+ const state = {
+ ...ducks.initialState,
+ target_url: payload,
+ another_field: { foo: 'bar' }
+ }
+ const result = selectedFieldsReducer(state, action)
+ expect(result.target_url).toBe(undefined);
+ });
+ test('updates specific field value', function() {
+ const payload = {
+ attribute: 'target_url',
+ value: 'example.com',
+ }
+ const action = ducks.setFieldValue(payload);
+ const state = {
+ ...ducks.initialState,
+ target_url: {
+ category: 'Website',
+ }
+ }
+ const result = selectedFieldsReducer(state, action)
+ expect(result.target_url).toEqual({
+ category: 'Website',
+ attribute: 'target_url',
+ value: 'example.com',
+ });
+ })
+});
diff --git a/src/redux/index.js b/src/redux/index.js
new file mode 100644
index 0000000..7dd255a
--- /dev/null
+++ b/src/redux/index.js
@@ -0,0 +1,29 @@
+import {
+ createStore, combineReducers, applyMiddleware, compose,
+} from 'redux';
+import selectedFields from './ducks/selectedFields';
+import urlMiddleware from './middleware/url';
+
+// Reducers
+export const rootReducer = combineReducers({
+ selectedFields,
+});
+
+// Enhancers
+let composeEnhancers = compose;
+if (process.env.NODE_ENV === 'development' &&
+ typeof window !== 'undefined' &&
+ typeof window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ !== 'undefined') {
+ composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__;
+}
+
+// Middleware
+const middleware = [
+ urlMiddleware,
+]
+
+const store = createStore(rootReducer, composeEnhancers(
+ applyMiddleware(...middleware),
+));
+
+export default store;
diff --git a/src/redux/middleware/url.js b/src/redux/middleware/url.js
new file mode 100644
index 0000000..399b775
--- /dev/null
+++ b/src/redux/middleware/url.js
@@ -0,0 +1,23 @@
+import * as selectedFields from '../ducks/selectedFields';
+import * as utils from '../../utils';
+
+const urlMiddleware = store => next => action => {
+ let result = next(action);
+
+ switch (action.type) {
+ case selectedFields.SELECT_FIELD:
+ case selectedFields.SET_FIELD_VALUE:
+ case selectedFields.UNSELECT_FIELD:
+ const paramString = utils.buildQueryParams(
+ utils.deepPluck(store.getState().selectedFields, 'value')
+ );
+ typeof window !== 'undefined' &&
+ window.history.replaceState(null, "", `?${paramString}`);
+ break;
+ default:
+ break;
+ }
+ return result;
+}
+
+export default urlMiddleware;
diff --git a/src/test-utils.js b/src/test-utils.js
new file mode 100644
index 0000000..6549c10
--- /dev/null
+++ b/src/test-utils.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import { render as rtlRender } from '@testing-library/react';
+import { createStore } from 'redux';
+import { Provider } from 'react-redux';
+import { rootReducer } from './redux/index';
+
+/***
+Version of React Testing Library's render function that provides a Redux store
+If your component requires Redux for itself or any of its children, use this.
+***/
+function render(
+ ui,
+ {
+ initialState,
+ store = createStore(rootReducer, initialState),
+ ...renderOptions
+ } = {}
+) {
+ function Wrapper({ children }) {
+ return {children}
+ }
+ return rtlRender(ui, { wrapper: Wrapper, ...renderOptions })
+}
+
+// re-export everything
+export * from '@testing-library/react'
+// override render method
+export { render }
diff --git a/src/utils.js b/src/utils.js
index 309e617..d5e23ce 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,3 +1,7 @@
+import 'url-search-params-polyfill';
+import FIELD_OPTIONS from './data/fields';
+import * as API from './data/api';
+
export const flattenObject = (obj, prefix = '') =>
Object.keys(obj).reduce((acc, k) => {
const pre = prefix.length ? prefix + '.' : '';
@@ -14,3 +18,38 @@ export const addOptionAll = optionsArr => {
export const customFilterOptions = (filterList, filterName) =>
filterList.filter(el => Object.keys(el).includes(filterName))[0];
+
+export const deepPluck = (obj, keyName) => {
+ return Object.keys(obj).reduce((acc, key)=> {
+ acc[key] = obj[key][keyName];
+ return acc;
+ }, {});
+}
+
+export const buildQueryParams = (obj) => {
+ let query = new URLSearchParams();
+ Object.keys(obj).forEach(key => (
+ query.append(key, obj[key])
+ ));
+ return query.toString();
+}
+
+export const parseFieldParams = (string) => {
+ const obj = {};
+ const searchParams = new URLSearchParams(string);
+ for(var pair of searchParams.entries()) {
+ obj[pair[0]] = {
+ ...FIELD_OPTIONS[pair[0]] || {},
+ value: pair[1],
+ }
+ }
+ return obj;
+}
+
+export const buildApiUrl = (obj) => (
+ `${API.API_DOMAIN}${API.API_PATH}?api_key=${API.API_KEY}&${buildQueryParams(obj)}`
+)
+
+export const buildQueryUrl = (obj) => (
+ `${window.location.href.replace(window.location.search, "")}?${buildQueryParams(obj)}`
+)
diff --git a/src/utils.test.js b/src/utils.test.js
new file mode 100644
index 0000000..2df78f3
--- /dev/null
+++ b/src/utils.test.js
@@ -0,0 +1,68 @@
+import * as utils from './utils';
+import * as API from './data/api';
+
+describe('deepPluck', function() {
+ const obj = {
+ key_one: { foo: 'one', bar: 'two' },
+ key_two: { foo: 'three', bar: 'four' },
+ }
+ const result = utils.deepPluck(obj, 'bar');
+ it('returns a flat object keyed by argument', () => {
+ expect(result).toHaveProperty('key_one', 'two');
+ expect(result).toHaveProperty('key_two', 'four');
+ });
+});
+describe('buildQueryParams', function() {
+ const query = {
+ fields: ['foo', 'bar'],
+ filter_one: 'baz',
+ }
+ const result = utils.buildQueryParams(query);
+ it('builds param with array', () => {
+ expect(result).toMatch(/fields=foo%2Cbar/);
+ });
+ it('builds param with single selection', () => {
+ expect(result).toMatch(/filter_one=baz/);
+ });
+ it('builds complete query string', () => {
+ expect(result).toEqual('fields=foo%2Cbar&filter_one=baz');
+ });
+});
+describe('parseFieldParams', function() {
+ it('returns an object keyed by param value', () => {
+ const string = '?target_url=foo&uswds_favicon_detected=bar';
+ const result = utils.parseFieldParams(string);
+ expect(result.target_url).toBeDefined();
+ expect(result.uswds_favicon_detected).toBeDefined();
+ });
+ it('returns an object of objects, each with a value', () => {
+ const string = '?target_url=foo&uswds_favicon_detected=bar';
+ const result = utils.parseFieldParams(string);
+ expect(result.target_url.value).toEqual("foo");
+ expect(result.uswds_favicon_detected.value).toEqual("bar");
+ });
+ it('returns an empty object when no param values are present', () => {
+ const string = '';
+ const result = utils.parseFieldParams(string, 'fields');
+ expect(result).toEqual({});
+ })
+});
+describe('buildApiUrl', function() {
+ const filters = {
+ filter_one: 'foo',
+ filter_two: 'bar',
+ }
+ const result = utils.buildApiUrl(filters);
+ it('starts with api domain', () => {
+ expect(result).toMatch(new RegExp('^' + API.API_DOMAIN, 'i'));
+ });
+ it('includes api path', () => {
+ expect(result).toMatch(new RegExp(API.API_PATH));
+ });
+ it('includes api key', () => {
+ expect(result).toMatch(new RegExp(API.API_KEY));
+ });
+ it('ends with params', () => {
+ expect(result).toMatch(/\&filter_one=foo&filter_two=bar$/i,);
+ });
+});