Skip to content
This repository was archived by the owner on Apr 9, 2021. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
42a023d
Add basic UI interactivity for field selection
echappen Nov 3, 2020
7d5e7cc
Fix: only use redux dev tools when in dev mode
echappen Nov 3, 2020
eaea56a
Fix: get CI build to pass
echappen Nov 3, 2020
79f136d
Merge pull request #7 from 18F/788-basic-ui-interactivity
echappen Nov 4, 2020
c702b10
Add utility for URL search param generation
echappen Nov 4, 2020
3af78ea
Hydrate redux state with fields specified in url params
echappen Nov 4, 2020
fb11139
FIX: check for window presence to satisfy server builds
echappen Nov 4, 2020
a107d2c
FIX: better check for window presence
echappen Nov 4, 2020
6596a36
Merge pull request #9 from 18F/792-set-fields-based-on-url-params
echappen Nov 4, 2020
cad83e1
WIP: initial filtering of selected fields
echappen Nov 4, 2020
38d769b
Fix broken test and handle empty text input values
echappen Nov 5, 2020
6849d1d
Add visual indicator for filterable vs non-filterable fields
echappen Nov 5, 2020
ff02f79
Add aria-labels to inputs without visual labels
echappen Nov 5, 2020
4336a0e
Merge pull request #10 from 18F/795-implement-ui-for-initial-filters
echappen Nov 5, 2020
8f5f017
Add UI for build actions
echappen Nov 5, 2020
308c718
FIX: fix test by adding needed dev dependency
echappen Nov 5, 2020
bfb23e9
include filters in url params
echappen Nov 6, 2020
72e7536
Select/Unselect All fields per group
echappen Nov 6, 2020
9b66eda
Add Agency and Bureau Names to dropdowns
echappen Nov 6, 2020
5f6b3de
Fix: specs
echappen Nov 6, 2020
6ffc6df
Updates based on review:
echappen Nov 12, 2020
f9ffd46
Fix: build failure
echappen Nov 12, 2020
9735505
Name changes to links
echappen Nov 12, 2020
2365979
Remove checkboxes / select field functionality and add copy url funct…
echappen Nov 16, 2020
630f62b
Fix: build error
echappen Nov 16, 2020
582ca62
Updates from review:
echappen Nov 16, 2020
85d440b
Add API demo key and basic mobile styling
echappen Nov 20, 2020
0d76054
Add Glossary of Terms page, link to it from builder
echappen Nov 20, 2020
938a950
Add variables to spreadsheet links so theyre easier to change
echappen Nov 20, 2020
e358519
Rename to QueryBuilder and set as homepage
echappen Nov 20, 2020
6c023f7
Remove residual 2.0 code from codebase; update README
echappen Nov 23, 2020
8e97c91
README cleanup
echappen Nov 23, 2020
466c63d
more README cleanup
echappen Nov 23, 2020
68a81e2
even more README cleanup
echappen Nov 23, 2020
4e14d62
Add unit testing for components; remove more residual code
echappen Nov 23, 2020
e322ee4
Add initial google sheets link
echappen Nov 24, 2020
6e6ed86
More removal of residual code; slight text changes
echappen Nov 24, 2020
61f3294
Fix README link
echappen Nov 24, 2020
7f282a1
e2e tests for homepage; unit tests for available fields
echappen Nov 24, 2020
3428dfd
add initial API domain and endpoint
echappen Nov 25, 2020
52ecfd6
Fix: typo
echappen Nov 25, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 70 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<Report />` in a `<ReportQueryProvider />`. 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 `<Report />` 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 |

`<ReportFilters />` 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

`<Pagination />` is pretty straightforward, and sends page navigation requests back up to the `<Report />` (via the `<ReportQueryProvider />`).
[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 [`<QueryBuilder />`](./src/components/modules/query-builder.js).

The QueryBuilder component contains the major modules of the app:
- `<AvailableFields />` generates the list of API fields able to be filtered by the user
- `<SelectedFields />` displays the filters the user has selected
- `<BuilderActions />` contains any user affordances, like copying the API link
- `<Instructions />` 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 `<QueryBuilder />`.

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.
76 changes: 76 additions & 0 deletions cypress/e2e/homepage.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
44 changes: 0 additions & 44 deletions cypress/e2e/navigation.test.js

This file was deleted.

48 changes: 45 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
},
Expand All @@ -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",
Expand Down
48 changes: 0 additions & 48 deletions src/components/__tests__/pagination.spec.js

This file was deleted.

Loading