An npm
-installable package of PCIC-developed React components for use across
projects.
Note: This README duplicates information in code comments, and may be out of sync with them. It should be replaced by documentation generated from comments, but that is a future project.
This package is not published to a Node package registry. Instead, it is installed directly from GitHub, as follows:
Using HTTPS protocol (preferred):
npm install git+https://[email protected]/pacificclimate/pcic-react-components.git#<version>
Using SSH protocol (requires troublesome ssh setup):
npm install git+ssh://[email protected]/pacificclimate/pcic-react-components.git#<version>
From there, pcic-react-components
is like any other installed package,
and components are imported in JS code the usual way:
import { ExampleComponent } from 'pcic-react-components';
These components provide selectors specialized to the purposes of Climate Explorer, Plan2Adapt, and other apps.
Although these selectors were developed to support specific PCIC apps, they are quite general and provide plug-in points (via props) so that they can easily be specialized. Other selectors in this package are just such specializations of these selectors.
These selectors wrap (render) React Select v2.
All React Select v2 properties are passed through to the rendered
React Select v2 selector, enabling the user to further customize each component
using the tools provided by React Select v2.
(As a tiny example, one can provide a value for
placeholder
, which is rendered when no option is selected.)
All selectors are controlled components, and communicate values in and out
of the component in the standard way, using properties with the standard names
value
and onChange
.
Renders a React Select v2 selector whose options are constructed from a list of basis items.
Options are constructed using a user-supplied function that maps each basis item to an representative value (an arbitrary JS object). The mapping is permitted to map many basis items to a single (deep) equal representative; indeed that is much of the point of this component.
All basis items that map to the same representative are grouped together and
into a single option. (Hence the name GroupingSelector
.)
Component properties allow the user to pass in functions to
- compute the option label (the string presented in the UI)
- compute the enabled/disabled status of each option
- sort, group, and otherwise arrange options for presentation
- replace an invalid value (a value which does not correspond to any enabled option) with a valid value
For more details, see code comments documenting GroupingSelector
.
Renders a GroupingSelector
, injecting an enabled/disabled function
that enables an option if and only if the supplied constraint value
(a JS object) matches one of the basis items that mapped to the option's
representative value.
This particular pattern is used in most of the CE selectors (see below) and makes it particularly easy to cascade selectors so that each successive selector in a sequence enables only those options that match the selections in preceding selectors. See the MEV demonstration for an example of this usage.
For more details, see code comments documenting SimpleConstraintGroupingSelector
.
These selectors wrap SimpleConstraintGroupingSelector
.
These selectors handle as bases the Climate Explorer backend metadata
(output of the /multimeta
endpoint) as option bases (see GroupingSelector
).
Selector options are the (unique) models found in the input metadata.
Item representative is an object containing the model_id
property
(from CE metadata):
{
model_id: String,
}
Selector options are the (unique) emissions scenarios found the input metadata.
Emissions scenarios are presented in more human-readable format (e.g., encoding "historical, rcp45" is presented as "Historical, then RCP 4.5").
Item representative is an object containing the experiment
property
(from CE metadata):
{
experiment: String,
}
Selector options are the (unique) variables found the input metadata.
Each option representative is a unique set of values for the following subset of CE metadata values:
{
variable_id: String,
variable_name: String,
multi_year_mean: Boolean,
}
Note that distinct variables can have the same variable_id
and multi_year_mean
,
but differ on variable_name
. This is arguably an incorrect way of naming variables,
but it's what we've got and this selector handles that. The distinction between
variables that differ only on variable_name
is usually the period over which
they apply (e.g., monthly vs. annual), and so far only occurs for Climdex variables.
It can be similarly argued that a variable with
multi_year_mean === false
vs one with multi_year_mean === true
is actually a different variable (e.g., pr
vs. pr_mym
), but this is also
what we've got, hence this trio of values that encode the full identity of
a variable.
Options are grouped by multi_year_mean
.
Each option bears an icon denoting the value of multi_year_mean
.
Selector options are the (unique) "dataspecs" found the input metadata.
Each option representative is a unique set of values for the following subset of CE metadata values:
{
start_date: String,
end_date: String,
ensemble_member: String,
}
Unique values of ensemble_member
(which are run codes such as "r1i1p1")
are mapped to the more human-friendly form "Run 1", "Run 2", etc.
Selector options are the (unique) time periods found in the input metadata.
Each option representative is a unique set of values for the following subset of CE metadata values:
{
start_date: String,
end_date: String,
}
We put the term "publishing" in quotes because we don't publish to a
Node registry, we just push package contents to GitHub and npm install
the package directly from GitHub. (See Installation above.)
Important:
-
Only content under the
src/lib
subtree is included in the package that is built. -
Content outside the
src/lib
subtree is excluded from the package, but is allowed and can be extremely useful; for example, to create demonstrations of package content (seesrc/demo
subtree). -
Each item exported by the package must be exported in the file
src/lib/index.js
.
When you modify this package (i.e., when you modify the contents of the
src/lib
subtree), follow this procedure:
-
Make sure you export any new or renamed components in
src/lib/index.js
. -
When all modifications have been completed, merge the branch or PR.
-
On the command line,
npm run build:library
.A successful build will output something like the following:
> [email protected] build /home/rglover/code/pcic-react-components > rimraf dist && NODE_ENV=production babel src/lib --out-dir dist --copy-files --ignore __tests__,spec.js,test.js,__snapshots__ Successfully compiled 17 files with Babel.
A successful build on changed
src/lib
content will cause files in thedist/
subtree to be modified. -
Commit the changes in
dist/
. (Make sure you add any newly built items to the repo.) -
Increment
version
inpackage.json
. -
Summarize the changes from the last version in
NEWS.md
. -
Commit these changes, then tag the release, and push all to GitHub, including tag:
git add package.json NEWS.md git commit -m "Bump to version x.x.x" git tag -a -m "x.x.x" x.x.x git push --follow-tags
This package is developed using Create React App as the basis. To do this takes a little effort, but is worth it for the following reasons:
-
All the out-of-box language and dev support benefits of Create React App:
-
React, JSX, ES6, TypeScript and Flow syntax support.
-
Language extras beyond ES6 like the object spread operator.
-
Autoprefixed CSS, so you don’t need -webkit- or other prefixes.
-
A fast interactive unit test runner with built-in support for coverage reporting.
-
A live development server that warns about common mistakes.
-
A build script to bundle JS, CSS, and images for production, with hashes and sourcemaps.
-
An offline-first service worker and a web app manifest, meeting all the Progressive Web App criteria. (Note: Using the service worker is opt-in as of [email protected] and higher)
-
-
Hassle-free updates for the above tools with a single dependency.
-
Easy to create demonstration apps for the components under development.
-
Familiarity.
-
Exact correspondence to our production environment (not that free-standing React components can or should depend much on the dev vs. prod environments, but ...).
A CRA project is not an npm package, alas. Some adjustment is required. Fortunately it is not too onerous.
After reviewing a variety of ways
to adapt a Create React App project to package development,
we chose what appears to be the simplest, implemented through
create-component-lib
.
With a small amount of tweaking, this worked out.
We need demonstrate this package to non-PCIC people.
We therefore created Docker infrastructure for running it (see docker/
).
The Docker image is built manually on whatever server will host the demo.
The commands for building and running the image are wrapped up in a Makefile;
the Makefile uses docker-compose
to handle Docker operations.
The Makefile defines two variables that configure the demo:
port
: External port to which container maps demo app.public_url
: URL from which demo will ultimately be accessed.
These two variables are given default values; update them in the Makefile according to the deployment you wish to make. No need to commit these changes to the repo.
- Pick a server (e.g., docker-devNN) and port on the server to run the demo on.
- Pick an appropriate public URL
(e.g.,
https://services.pacificclimate.org/dev/pcic-react-components/
) for the demo. - Modify proxy config to forward a request from public URL to server and port.
Have it strip the base path (e.g.,
/dev/pcic-react-components/
) and forward the remainder of the path to the server. - Clone this repo into a suitable directory on the server.
- Update Makefile variables with chosen port number and public URL.
- Run
make image
. - An image named
pcic/pcic-react-components-demo
(tagged:latest
) is created.
- Update the Makefile configuration variables.
- Run
make up
.
Run make down
.