We're so glad you're thinking about contributing to an 18F open source project! If you're unsure or afraid of anything, just ask or submit the issue or pull request anyways. The worst that can happen is that you'll be politely asked to change something. We appreciate any sort of contribution, and don't want a wall of rules to get in the way of that.
Disclaimer: Everything in this guide is not set in stone and subject to change based on changing project requirements and tools.
Before contributing, we encourage you to read our CONTRIBUTING policy (you are here), our LICENSE, and our README, all of which should be in this repository. If you have any questions, or want to read more about our underlying policies, you can consult the 18F Open Source Policy GitHub repository at https://github.com/18f/open-source-policy, or just shoot us an email/official government letterhead note to 18f@gsa.gov.
The cloud.gov dashboard is a unit-tested React single-page application. Its
previous version was an Angular app and is available at the deprecated
branch.
The work on the cloud.gov front-end fits into a higher-level roadmap for all of cloud.gov. For now, this is defined quarterly in a PI planning, documented in favro.
We track work and progress through Favro which is an agile board detailing the state of work for the dashboard and other customer facing projects.
- Stories or ideas for features can start in backlog or icebox.
The criteria for moving a card through the columns is in the main cloud.gov product repo: cloud.gov Delivery Process. Some aspects that differ or extend for that process as related to the cloud.gov front end:
- Have all new files and newly touched files linted (new files can skip linting if under tight deadline)
- Have all previous unit tests running without error.
- Non-JSX code covered in units tests.
- Is deployed on a demo, staging or live site so other team members can see/use it.
- Stakeholders see and approve the work as meeting acceptance criteria.
- If the work has a visual aspect, post a screenshot attached for later documentation/announcement/demo purposes.
A feature is a higher-level epic that will encompass multiple smaller units of work. An example would be "Route panel" or "Service panel on app page".
- Stakeholders see and approve the work as meeting acceptance criteria.
- Is deployed to production, dashboard.cloud.gov site.
- All appropriate shared global styling is in cg-style rather then cg-dashboard or other repos.
- All potential errors are handled correctly.
- If the feature uses new data from the Cloud Foundry API (new methods added to
cf_api.js
), ensure they are mocked in the testing server. - Open an issue or PR in cg-docs to document the new feature.
- A product owner or team member will check a story waiting acceptance and put it into "done" if it meets the criteria.
- The feature has been usability tested with at least two current or potential cloud.gov users.
For more information, see the high-level cloud.gov repository and delivery process.
- Languages
- ES6 Javascript (primer)
- CSS
- JS unit testing:
- View layer
- Data/model layer
- Build
- Front end router
- Add the "ready for review" label when the code is ready to be reviewed by
another team member.
- Work-in-progress PRs are allowed. Be sure to tag the review with "ready for review" when it's ready though.
- As another team member, review the code and ensure it conforms to the coding
standards and exit criteria
- PR's do not need to be assigned due to small team size
- When it is reviewed and ready to be merged, use Github's Code Review feature to approve the pull request.
- Any team member (code author or otherwise) can merge the code once it has an
approved review.
- Updates on PRs in the repo will be posted in the #cloud-gov-nav-news Slack channel
- It's fine to merge code that isn't "feature complete." The
demo
branch is not currently in use, so it is fine to force push the branch for "in progress work", e.g.git push -f origin HEAD:demo
. - We're currently not focusing on acceptance tests right now due to the tests not being easily repeatable and having a clean data state. If a change breaks an acceptance test, spend 10 minutes trying to fix it before disabling the test. Do not write new acceptance tests.
- Squashing commits is allowed but discouraged, except in rare instances.
- The team prefers rebasing over merging, though we use GitHub to close out pull
requests. This means that PRs will be merged, but if you're refreshing a local
branch make sure to use rebase. For example, if you want to update your
new-feature
branch to reflect the most recent changes on GitHub usegit pull --rebase origin new-feature
.
- Open branches off main repo due to Circle CI env var problem. For now,
remember to branch off of the
master
branch. - Name your branch with your initials first. For example,
ms-feature
. - Include a short description of the feature that's being developed after your initial.
In general, commit messages can be written in whatever way the author decides, but here are some guidelines:
- Focus on the "why" rather then the "what".
- Have the first line be the "what".
- Discuss the "why" in more detail on subsequent lines.
- Airbnb styleguide for JS
- Re-add deprecation warnings on linter when upgrade react.
- 18F styleguide for CSS, through stylelint.
Prettier is used to automatically format
a range of files (particularly .css
, .js
, .jsx
, .json
and .md
). See
prettier's docs for how to enable automatic formatting on save for your editor.
If it's necessary to manually run prettier, you can use the package script:
npm run format
Note: it's highly recommended to format on save, so the package script should generally not need to be used.
The code base includes linting configurations and tools, but is currently not fully linted. This means that there's an "opt-in" policy to linting: you decide when to add a file to linting.
- Theres a lint ignore file,
.eslintignore
with all non-linted files. - If you touch another file that isn't linted yet, you should generally fix it
and remove it from the lint ignore.
- Unless under strict time constraints.
- No new files should be added to the lint ignore. Consequently, all new files should be linted.
- Linting will be run before tests run, so will fail the tests if files are not linted.
- Additionally, linting should always
- fail on CI
- should not stop watching commands
- should not fail builds
- should fail test runs
Documentation is not a focus right now.
All pull requests are tested with CircleCI and CodeClimate. To run the CodeClimate tests locally, you can use the CodeClimate CLI with Docker by invoking: codeclimate analyze
. Only the nodesecurity
tests will block acceptance, which you can isolate by running: codeclimate analyze -e nodesecurity
.
The CirleCI tests are described in the circle.yml
stanza for test:
, but in short
- FrontEnd tests: If you followed the Manual Setup, tests are invoked with
npm run test
. If you're using the recommended Docker setup, then you'd invoke:The Docker imagedocker-compose up -d frontend docker-compose exec frontend bash -c "npm run test"
consol/ubuntu-xfce-vnc
may encounter a ChromeDriver bug that results in errors like the one below:Failures related to these errors can be ignored, as they're not present in the CircleCI tests.Error: An unknown server-side error occurred while processing the command. at cookie("DELETE", "testing_user_role") - deleteCookie.js
- Backend Tests: The Go server test suites are invoked with
./codecheck.sh
.
- The only language level dependencies should be
nodejs
andgo
. - Front end dependencies (as in
package.json
) should be pinned to minor versions and below under most circumstances. - Front end dependencies should be shrink-wrapped with
npm
. - Every month, front end dependencies will be upgraded to most recent patch or minor version as stipulated in package.json.
- React, Babel, Webpack, ImmutableJS, and other major libraries should upgraded to major versions when there's time to test the changes.
To upgrade all dependencies to version stipulated in package.json
- Ensure cloudgov-style or other dependencies are unlinked by running
npm unlink cloudgov-style
. - Dependencies you unlink may need to be manually replaced with, say,
npm install --save cloudgov-style
- Remove
npm-shrinkwrap.json
. - Use
npm update --save --saveDev
to update all dependencies. - Make sure it works (run linting, tests, and acceptance tests).
- Add back the shrinkwrap with
npm shrinkwrap --dev
and commit it.
To update a single package to a specific version
- Ensure cloudgov-style or other dependencies are unlinked by running
npm unlink cloudgov-style
. - Run
npm install --save[Dev] {package name}@{version}
where package name is the name of the package to update and version is the version you want to update to. - If there are errors related to peer dependencies, continue installing them in the same fashion.
- If there are no errors, both package.json and npm-shrinkwrap.json should have changes that can be committed.
- When doing code reviews, the reviewer should pull down the code and test on
their local computer. This is because the staging site is not often used,
meaning bugs could be present for long amounts of time.
- If a code change is very simple, or is not a code change (but maybe a document change) it doesn't need to be pulled down.
- We can give other members of 18F the public URL to the staging site.
- Browser support is all major browsers and Internet Explorer 10 and up.
- Pages and views should work on different device screen-sizes, and should work
as specified in design mockups.
- If making a feature work on mobile is time-consuming, and there isn't an official design for the mobile view of the feature yet, the mobile view can be held off until a design becomes available.
- Support for browsers not running javascript should be attempted if it's easy,
but not a major focus
- Eventually the react code base will allow server-side, isomorphic rendering for both performance and support reasons. This means code should attempt to ensure things work without it when it's not detrimental to project timeline to do so.
- CSS in component files should opt to use CSS Modules first, and will use normal CSS class architecture after.
The dashboard uses the Flux architecture and renders HTML with React. The Flux implementation is mostly vanilla JS with a Flux dispatcher, as opposed to using a tool such as Redux.
Action creators are simple functions which always dispatch an action of a certain type with certain data through the central dispatcher and perform a API request if the action requires it. These actions are listened to by just the stores, which will usually modify their data when an certain action happens.
Here are some basic rules to work with action creators successfully:
-
An action creator should usually have a matching store, i.e.
UserStore
->UserActions
. -
An action creator should have a corresponding constant for the action.
-
Action creator methods should generally be named as
{verb}{noun}
and use the appropriate tense based on whether the action is going to happen vs it has happened// Good fetchSpaces(); receivedSpaces(); addUser(); addedUser(); // Bad spacesFetch();
-
Each action creator (each method in
*_actions.js
) should dispatch only a single action.// Bad addedUser(user) { AppDispatcher.handleServerAction({ type, user}); AppDispatcher.handleServerAction({ type2, user}); }
-
Each action creator should return a
Promise
.// Good receivedSpaces(spaces) { return Promise.resolve(spaces); }
-
There should not be more than one action with the same data.
-
Any XHR calls should happen within the fetch action creators which should return a promise of the request.
-
Any
fetch
action should call the appropriatereceive
/success
/error
action on the resolve of the fetch's promise.// Good fetchUser(guid) { return cfApi.fetchUser(guid).then((user) => userActions.receivedUser(user)); }
-
Each async action should have a fetch, success, and error sub-action (e.g.
TOGGLE
,TOGGLE_SUCCESS
, andTOGGLE_ERROR
).
Stores are meant to hold all the data of the application such as the Cloud Foundry entities such as organizations, spaces, apps, etc as well as UI data such as loading states, current pages, and current user info. They use Immutable.js as their main data structure to limit accidental manipulation of the data. They bind to certain actions and change their data. When they change their data, they emit a changed which is listened to by the components that require their data, which in turn render.
Here are some basic rules to work with stores successfully:
- Stores should not call action creators.
- The data in stores should be an Immutable.List.
// Good
export class OrgStore extends BaseStore {
constructor() {
super();
this.currentOrgGUID = null;
this.isFetchingOrg = false;
}
get loading() {
return this.isFetchingOrg || this.isFetchingAll;
}
}
-
Stores should generally not access their
storeData
directly but should attempt to use the data manipulation functions in the BaseStore, such aspush
,merge
,mergeAll
, etc.// Good case orgActions.ORGS.RECEIVED: { this.mergeAll('guid', action.orgs); } // Bad case orgActions.ORGS.RECEIVED: { this.storeData = new Immutable.List(this.storeData, action.orgs); }
-
Show preference for emitting a change as the performance hit is worth reducing bugs due to changes not being reflected in the UI because the change wasn't emitted. This should always be the case when something like loading state changed.
// Good this.isFetchingAll = false; this.mergeMany("guid", updates, () => {}); this.emitChange(); // Bad this.isFetchingAll = false; this.mergeMany("guid", updates, changed => { if (changed) this.emitChange(); });
-
Stores should generally avoid doing async work, including making any calls to the API, and
setTimeout
. The only case where it's easier for the store to make API calls is when a store's loading state depends on more then one data request, in which case, theBaseStore.load
method should be used. See anti-patterns for more information
Creating a new store should generally be avoided but there are a few circumstances where it makes sense:
- There isn't already a store for a certain Cloud Foundry Entity, such as an org, app, space (these already exist).
- There's a need for data to be shared across the whole application, such as global notifications and errors.
Components are React JSX components that render the HTML UI of the application. Components subscribe to stores that have data they care about or have properties that are passed down from the component that rendered them. Components call action creators when things happen in the UI, such as a user clicks a button to add an entity.
Here are some basic rules to work with components successfully:
-
Always set the component's state with the
setState
method (unless in the constructor). Never set state in therender()
method.// Good this.setState({ ...data }); // Bad this.state = { ...data };
-
Due to the flux architecture, components should rarely set their own state, but should receive all their state from their stores. Rather then setting state when something happens, components should call action creators which will change the store data.
// Good onHandleClick() { userActions.addUser(); // Eventually state will be set from stores. } // Bad onHandleClick() { this.setState({ loading: true }); }
-
Break up long render methods by putting some of the HTML UI in other methods.
// Good getErrorMessage() { return <ErrorMessage></ErrorMessage> } render() { const error = this.getErrorMessage(); }
-
Be sure to bind methods to
this
that are called from click or DOM events in the constructor.// Good this.handleOverviewClick = this.handleOverviewClick.bind(this);
-
For UI that is only rendered conditionally, set the UI's variable with an empty
let
statement, as React will not renderundefined items
, but will also not crash.// Good let errorMessage; if (this.state.error) { errorMessage = <Error>{ this.state.error }</Error> } ... return (<div> { errorMessage} { otherStuff } </div>); // Bad if (condition) { conditionalContent = <div></div>; } else { conditionalContent = ''; // Not required }
-
Keep the
propTypes
anddefaultProps
asconst
s at the top of the file directly before the component class definition, and set the consts at the bottom.// Good const propTypes = { things: PropTypes.array }; const defaultProps = { things: [] }; class Things extends React.Component { ... } Things.propTypes = propTypes; Things.defaultProps = defaultProps;
-
If UI displaying is dependent on a certain amount of time, this should be set in the component rather then the store, see Loading component as an example.
-
If a component's prop types is technically an enum, use
oneOf
rather then the underlying data structure.// Good const propTypes = { status: PropTypes.oneOf(['red', 'green', 'yellow']); // Bad const propTypes = { status: PropTypes.string };
-
Components should usually have either
state
orprops
, but usually not both. See Container component.- The only time it should have both when there's a prop for a configurable part of the UI.
When a UI is supposed to do something based on something a user did, such as clicking a a dropdown to display what's within, these actions should go through the dispatcher as an action.
The general pattern for different UIs on the app is to fetch data for Cloud
Foundry entities, and then render React components with the data. To organize
around this, container components should get and keep the state of the data and
pass down this data through various UI based components through props. This
means UIs will have one container (which is usually but not always linked to a
page, like org
, space
, etc) and all other components will just have props
and will have all data passed down through them.
There's no perfect guidance on what should be a container component vs a props component, besides that almost all base pages are a container component. A good way to determine is to see what data various UIs require and then separate containers and prop containers based on this.
Besides pure UI components, no components should set state within themselves without the use of an action. This is due to keeping both UI actions the users take, and server actions when data comes in from the server to move through the app in the same way, through the dispatcher.
Instead, use ui actions.
This pattern is slowly being phased out of multiple stores, so will be seen many places. For new stores, all async work should be done in the action creator functions. For example, for a fetch action, the fetch action should be dispatched and then the API call should be made.
When async code is in stores, it should be migrated to action creators.
Both client-side routed page views and all client-side events are logged in google analytics.
- Different client-side urls, such as
/org/{org guid}/spaces/
are logged as page views. This is done through the router. - Any action called is logged as a Google Analytics event. This is done from the App Dispatcher.
- There's a utility file for all analytics related functionality, util/analytics.js
There's no distinction from the dashboard and any of the other cloud.gov site's in the GA dashboard. To help with this, there's a shared GA segment representing just the dashboard production traffic. If you have access to the cloud.gov GA, it's available with a shared link. There's also a dashboard GA dashboard, which doesn't have a shareable link but should be in the "Customization" -> "Dashboard" section.
Performance, as in speed of the site as perceived to users, has been briefly researched and tracked. The main web performance metrics tracked are Speed Index, input latency, time to interactive, total page weight, and number of DOM nodes. These metrics were tracked for five comparison sites for cloud.gov and documented in a google sheet, GSA only. These comparison stats were used to determine performance goals (ideal numbers to reach over time) and budgets (limits placed on the team to stop performance from degrading).
Performance is tracked as part of the CI build process, and the build fails if performance goes over the budgets. Any library added that's total file size is above 25kb should be evaluated for performance affect. Update 07/05: Currently the performance is not tracked as part of the CI build due to memory and resource limits on CircleCI, but should be added back later when these issues are resolved.
Complete the onboarding tasks for the whole cloud.gov team and for the Customer theme.
This project is in the public domain within the United States, and copyright and related rights in the work worldwide are waived through the CC0 1.0 Universal public domain dedication.
All contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest.