Skip to content

Commit

Permalink
Add the first How Tos (#1112)
Browse files Browse the repository at this point in the history
* Add the first How Tos.

* Uncomment imports in example.
  • Loading branch information
jim committed Oct 12, 2018
1 parent 6e48631 commit 7a445ed
Show file tree
Hide file tree
Showing 8 changed files with 408 additions and 214 deletions.
5 changes: 3 additions & 2 deletions .markdownlintrc
Expand Up @@ -3,5 +3,6 @@
"line_length": false,
"MD024": {
"siblings_only": true
}
}
},
"MD014": false
}
2 changes: 2 additions & 0 deletions .spelling
Expand Up @@ -319,6 +319,8 @@ dynamike
fillable
symlinked
dp3-engineering
ripgrep
entr
github.com
namsral
float64
Expand Down
176 changes: 176 additions & 0 deletions docs/how-to/access-swagger-endpoints-from-react.md
@@ -0,0 +1,176 @@
# How To Call Swagger Endpoints from React

## 1. Verify the Schema is Defined

For each model type returned by the backend, there needs to be an `Entity` defined and exported in `src/shared/Entities/schema.js`.

Here is the definition for `Shipment`:

```javascript
export const shipment = new schema.Entity('shipments');

// add any embedded objects that should be extracted during normalization
shipment.define({
pickup_address: address,
secondary_pickup_address: address,
delivery_address: address,
partial_sit_delivery_address: address,
});

export const shipments = new schema.Array(shipment);
```

## 2. Call the Swagger Operation

Add a function to `src/shared/Entities/modules/$MODEL.js` that calls the `operationId` defined in the
swagger YAML. Action creator functions should take a `label` argument, which will be used to allow the calling component to determine the status of any requests with that label.

`swaggerRequest` returns a promise, so it is possible to chain behavior onto its result, for example to perform a few requests in sequence.

```javascript
import { swaggerRequest } from 'shared/Swagger/request';
import { getClient } from 'shared/Swagger/api';

export function getShipment(label, shipmentId, moveId) {
return swaggerRequest(
getClient, // function returning a promise that will resolve to a Swagger client instance
'shipments.getShipment', // what operation to perform, including tag namespace
{ moveId, shipmentId }, // parameters to pass to the operation
{ label }, // optional params for swaggerRequest, such as label
);
}
```

By directing all Swagger Client calls through the `swaggerRequest` function, we can have a centralized place to manage how to track
the lifecycle of the request. This allows us to dispatch actions to Redux that represent these events, currently `@@swagger/${operation}/START`, `@@swagger/${operation}/SUCCESS` and `@@swagger/${operation}/FAILURE`. These actions will appear in the Redux debugger along with any other state changes.

## 3. Dispatch an Action when Component Mounts

The following pattern, using `onDidMount`, allows the data fetching to be defined outside the component.

```javascript
export class ShipmentDisplay extends Component {

componentDidMount() {
this.props.onDidMount && this.props.onDidMount();
}

render {
const { shipment } = this.props;

return (
<div>
<p>You are moving on { shipment.requested_move_date }.</p>
</div>
);
}
}

ShipmentDisplay.propTypes = {
shipmentID: PropTypes.string.isRequired,

onDidMount: PropTypes.function,
shipment: PropTypes.object,
};

const requestLabel = 'ShipmentDisplay.getShipment';

function mapDispatchToProps(dispatch, ownProps) {
return {
onDidMount: function() {
dispatch(getShipment(requestLabel, ownProps.shipmentID)); }
};
}

function mapStateToProps(state, ownProps) {
return {
shipment: selectShipment(ownProps.shipmentID),
};
}

export default connect(mapStateToProps, mapDispatchToProps)(ShipmentDisplay);
```

If you need to load data based on a value that isn't passed in as a `prop`, it's best to embed another component and pass that value into it as a `prop`. This can be thought of as an extension of the container pattern.

## 4. Use a Selector to Access the Data

All data access should be done through selectors and not by directly accessing the global Redux state.

Add a function to `src/shared/Entities/modules/$MODEL.js` that returns the value from Redux. This example uses `denormalize`:

```javascript
// Return a shipment identified by its ID
export function selectShipment(state, id) {
if (!id) {
return null;
}
return denormalize([id], shipments, state.entities)[0];
}
```

This one returns a value that doesn't need `denormalize`:

```javascript
// Return a shipment identified by its ID
export function selectShipment(state, id) {
if (!id) {
return null;
}
return get(state, `entities.shipments.${id}`);
}
```

## 5. Handle Fetch Errors

The `lastError` selector provides access to the last error for a specified request label.

```javascript
import { lastError } from 'shared/Swagger/selectors';

export class ShipmentDisplay extends Component {

componentDidMount() {
this.props.onDidMount && this.props.onDidMount();
}

render {
const { shipment, error } = this.props;

return (
{ error && <p>An error has occurred.</p> }

<div>
<p>You are moving on { shipment.requested_move_date }.</p>
</div>
);
}
}

ShipmentDisplay.propTypes = {
shipmentID: PropTypes.string.isRequired,

onDidMount: PropTypes.function,
shipment: PropTypes.object,
error: PropTypes.object,
};

const requestLabel = 'ShipmentDisplay.getShipment';

function mapDispatchToProps(dispatch, ownProps) {
return {
onDidMount: function() {
dispatch(getShipment(requestLabel, ownProps.shipmentID));
}
};
}

function mapStateToProps(state, ownProps) {
return {
shipment: selectShipment(ownProps.shipmentID),
error: lastError(state, requestLabel),
};
}

export default connect(mapStateToProps, mapDispatchToProps)(ShipmentDisplay);
```
45 changes: 45 additions & 0 deletions docs/how-to/run-go-tests.md
@@ -0,0 +1,45 @@
# How To Run Go Tests

## Run All Go Tests

```console
$ make server_test
```

## Run Specific Tests

All of the commands in this section assume that `test_db` is setup properly. This can be done using:

```console
$ make db_test_reset
```

### Run All Tests in a Single Package

```console
$ go test ./pkg/handlers/internalapi/
```

### Run Tests with Names Matching a String

The following will run any Testify tests that have a name matching `Test_Name` in the `handlers/internalapi` package:

```console
$ go test ./pkg/handlers/internalapi/ -testify.m Test_Name
```

## Run Tests when a File Changes

You'll need to install [ripgrep](https://github.com/BurntSushi/ripgrep) and [entr](http://www.entrproject.org/) (`brew install ripgrep entr`):

```console
$ rg -t go --files | entr -c $YOUR_TEST_COMMAND
```

Here is an example that will run all model tests when any Go file in the project is changed:

```console
$ rg -t go --files | entr -c go test ./pkg/models
```

There is generally no need to be any more specific than `rg -t go`, as watching all `.go` files is plenty fast enough.
33 changes: 33 additions & 0 deletions docs/how-to/store-data-in-redux.md
@@ -0,0 +1,33 @@
# How To Store Data in Redux

The specific layout of data within the Redux store should generally be considered an implementation detail and we should strive to avoid coupling any Components to this structure directly. Selectors provide the best way to decouple component data access from store layout.

The current layout of data in Redux, however, is the following:

```javascript
{
entities: {
shipments: {
'123e4567-e89b-12d3-a456-426655440000': { /* shipment properties */ },
},
addresses: {
'123e4567-e89b-12d3-a456-426655440000': { /* address properties */ },
}
},
requests: {
byID: {
'req_0': { /* request properties */},
'req_1': { /* request properties */},
},
errored: {
'req_1': { /* request properties */},
},
lastErrorByLabel: {
'ShipmentForm.loadShipments': { /* error properties */ },
}
},
ui: {
'currentShipmentID': '123e4567-e89b-12d3-a456-426655440000',
},
}
```
64 changes: 64 additions & 0 deletions docs/how-to/store-ui-state-in-redux.md
@@ -0,0 +1,64 @@
# How To Store UI State in Redux

State that is specific to the UI should be set by dispatching an action and accessed using a selector. Here is an
example of how this might work for managing which of a list of Shipments is currently selected in the UI:

```javascript
import { get } from 'lodash';
import PropTypes from 'prop-types';
import React, { Component} from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import { lastError } from 'shared/Swagger/ducks';
import { allShipments, selectShipment } from 'shared/Entities/modules/shipments';
import { setCurrentShipmentID, currentShipmentID } from 'shared/UI/ducks';

const requestLabel = 'ShipmentForm.loadShipments';

export class ShipmentList extends Component {
componentDidMount() {
const id = get(this.props, 'shipmentID');
if (!id) return;

this.props.listShipments(requestLabel);
}

shipmentClicked = (id) => {
this.props.setCurrentShipmentID(id);
}

render {
const { shipments, selectedShipment, error } = this.props;

return (
<div>
{ error && <p>An error has occurred.</p> }

<ul>
{ shipments.map(shipment => (<li>
<button onClick={this.shipmentClicked.bind(shipment.id)}> { shipment.id } </button>
</li>))}
</ul>

<p>The selected shipment is { selectedShipment.id }.</p>
</div>
);
}
}

function mapDispatchToProps(dispatch) {
return bindActionCreators({ request, setCurrentShipmentID }, dispatch);
}

function mapStateToProps(state) {
return {
shipments: allShipments(state),
selectedShipment: selectShipment(state, currentShipmentID(state)),
error: lastError(state, requestLabel),
};
}
```

Note that the above use of defining an inline event handler for `onClick` is not considered a
best practice. This technique is used above only for its brevity.

0 comments on commit 7a445ed

Please sign in to comment.