Monorepo of Red Hat Cloud services Components for applications in a React.js environment.
- utils - library that has utilities functions
- charts - small group of general charts
- components - series of common components
- notifications - common component to display toast notifications
- remediations - hot loaded component that shows remediations buton and wizard
- inventory - hot loaded inventory component (table and detail)
- inventory-general-info - directly imported component with redux store to show system information
- inventory-vulnerabilities - directly imported component with redux store to show vulnerability data
- inventory-compliance - This component was moved to the compliance repository
- inventory-insights - directly imported component to show insights data
- sources - Sources Wizard component was moved to Sources UI repository (it's also stored here in
sources_backup
branch)
First you have to generate MD files from /packages/components
folder (it takes every JS file and generated MD files from JSDOCs) by running npm run generate:components:docs
.
To run docs server locally simply run npm run docs
in root directory.
Add new JS file to /packages/docs/examples/<component name>/<example-name>.js
. The component name must be the same as the component MD file.
Patternfly packages require some ehancements to be done in order to properly treeshake your bundles. You can either use direct imports or plugin that does that for you, there are actually 2 plugins to do this
- babel-plugin-import - easy setup, however not that extensible
- babel-plugin-transform-imports - harder to setup, but allows custom rules
Since Patternfly requires a bit of custom settings you should use babel-plugin-transform-imports
. Change your babel to be JS file babel.config.js
and add these changes to it
// This is required because of how PF is using their css modules.
// This `extensions` will be removed in future, we'll have to come up with some other clever way of doing this
require.extensions['.css'] = () => undefined;
const path = require('path');
const glob = require('glob');
// list of custom named exports, this is just a few, and you should probably update it to fit your project
const mapper = {
TextVariants: 'Text',
DropdownPosition: 'dropdownConstants',
EmptyStateVariant: 'EmptyState',
TextListItemVariants: 'TextListItem',
TextListVariants: 'TextList'
};
module.exports = {
presets: [
// list of your presets goes here
],
plugins: [
// your other plugins
[
'transform-imports',
{
'@patternfly/react-core': {
transform: (importName) => {
const files = glob.sync(
path.resolve(
__dirname,
// you can use `js` or `esm`
`./node_modules/@patternfly/react-core/dist/js/**/${mapper[
importName
] || importName}.js`
)
);
if (files.length > 0) {
return files[0].replace(/.*(?=@patternfly)/, '');
} else {
throw `File with importName ${importName} does not exist`;
}
},
preventFullImport: false,
skipDefaultConversion: true
}
},
'react-core'
],
[
'transform-imports',
{
'@patternfly/react-icons': {
transform: (importName) =>
// you can use `js` or `esm`
`@patternfly/react-icons/dist/js/icons/${importName
.split(/(?=[A-Z])/)
.join('-')
.toLowerCase()}`,
preventFullImport: true
}
},
'react-icons'
]
}
If you see Jest errors after applying transform-imports plugin you should add to your Jest config
"transformIgnorePatterns": [ "/node_modules/(?!@redhat-cloud-services)" ],
Since this is monorepo repository it has some special requirements how to run tasks. This repository is using lerna, so if you have newer version of npm you can run npx lerna $TASK
where $TASK is one of lerna commands.
These tasks are preconfigured
npm start
- will perform start in all packages, you can change the scope by callingnpm start -- --scope=pckg_name
to run start inpckg_name
npm run build
- will perform build in all packages, you can change the scope by callingnpm start -- --scope=pckg_name
to run start inpckg_name
npm run clean
- to remove all node modules in root and in packages foldernpm run bootstrap
- to install packages correctly (will link local dependencies as well)npm run test
- to run tests locallynpm run watch
- similiar to start, but will emit files to disk (good for local links)npm run playground
- to launch local demo on port 8080
There are two ways to test changes from packages in this repository in other applications: Using npm link
or yalc
.
- Run
npm install
in the root of thefrontend-components
working copy - Remove
react
andreact-dom
fromnode_modules
rm -rf node_modules/react; rm -rf node_modules/react-dom
This is because we want to use hooks and different reacts are not playing nicely with hooks facebook/react/issues/15315
- Link
react
andreact-dom
from your application. Running from folder that contains your application and frontend components. Runningls
in this folder would yield<application-folder> insights-proxy frontend-components
ln -s $PWD/<application-folder>/node_modules/react frontend-components/node_modules/react
ln -s $PWD/<application-folder>/node_modules/react-dom frontend-components/node_modules/react-dom
- Change into the directory of the package you are working on, for example
cd packages/components
and runnpm link
* - Change into the directory of the application you'd like to include the package and run
npm link @redhat-cloud-services/frontend-components
*
After these steps the package you want to test should be linked and the last npm link
command should have returned the paths it linked the package from.
When linked successfully you can build the package(s) by running either npm start -- --scope=@redhat-cloud-services/frontend-components
or npm run build -- --scope=@redhat-cloud-services/frontend-components
in the frontend-components
working copy.
Both will build the @redhat-cloud-services/frontend-components
package, to build all packages run these commands without -- --scope=@redhat-cloud-services/frontend-components
.*
Once the packages are built the application the package is linked in should also be able to build and include any changes made locally in the frontend-components
packages.
* Depending on what package you are working on this arguments need to change accordingly.
yalc acts as very simple local repository for your locally developed packages that you want to share across your local environment.
- Install yalc globally. e.g.
npm install -g yalc
. - Run
npm install
in the root of thefrontend-components
working copy. - Change into the directory of the package you are working on, for example
cd packages/components
and runyalc publish
* - Change into the directory of the application you'd like to include the package and run
yalc add @redhat-cloud-services/frontend-components
*
After these steps the package you want to test should be linked and the yalc add
command should have returned the paths it linked the package from.
When added successfully you can build the package(s) by running npm run build -- --scope=@redhat-cloud-services/frontend-components
in the frontend-components
working copy and pushing by going into the directory of the package and running yalc push
.
yalc
does not watch the files, but if you would like to do this automatically you can build the package(s) by running: npm start -- --scope=@redhat-cloud-services/frontend-components
and having a separate terminal that does the local publishing of the packages by running: watch -n 0.5 yalc publish --push --changed
. This will publish the package only when there are changes.
To build all packages run these commands without -- --scope=@redhat-cloud-services/frontend-components
.*
Once the packages are built the application the package is linked in should also be able to build and include any changes made locally in the frontend-components
packages.
To remove the package info from package.json and yalc.lock, run yalc remove @redhat-cloud-services/frontend-components
to remove a single package; or yalc remove --all
to remove all packages from a project.
* Depending on what package you are working on this arguments need to change accordingly.
If none package suits scope of new changes, we need to create new package by creating folder inside packages
and running npm init
in it.
Webhooks are enabled to trigger releases on travis from comment on merged PR. If you are member of group responsible for releases you can add new commnent to merged PR Release minor
, Release bugfix
or Release
in order to trigger new release.
You can also draft a release by adding label release
or release minor
and once this PR is merged new release will be triggered.
Typescript build has been finally introduced to the repository. All modules will be eventually refactored into typescript. When refactoring, or creating new modules, please follow these instructions when creating types:
- All component props must be an interface and must be exported. This is required for consuming apps to use features like
omit
orextends
when developing with typescript. A nice example is generating JSX from array structure, but for some reason, extension/exclustion of the interface is required.
import React from 'react';
import { AlertProps, AlertVariant } from '@patternfly/react-core';
/**
* Custom component wrapper from FEC
* It has a description prop which is mapped to the PF Alert children prop
* */
interface AlertWrapper extends AlertProps {
description?: string
}
/**
* Local type which builds on FEC defined type
*/
interface LocalAlertType extends AlertWrapper {
id: string
}
const CustomComponent: React.ComponentType = ({ data }) => {
const alertsStructure: LocalAlertType[] = data.map(({ uuid, status, title, message }) => ({
id: uuid,
variant: status === 500 ? AlertVariant.danger : AlertVariant.info,
title,
description: message
}));
return alertsStructure.map(({ id, ...props }) => <AlertWrapper key={id} {...props} />);
};
- When creating a wrapper around PF components, always extend original components props. This will ensure that all original props are accepted by your component wrapper. If you have to exclude some original prop, use
Omit
generic. Exclusion might be required because the wrapper is setting a prop by default.
import React from 'react';
import { Alert, AlertProps, AlertVariant } from '@patternfly/react-core';
export interface AlertWrapperProps extends Omit<AlertProps, 'variant'> {
specificRequiredProp: React.ReactNode
}
const AlertWrapper: React.FunctionComponent<AlertWrapperProps> = ({ specificRequiredProp, ...props }) => (
<div>
<span>{specificRequiredProp}</span>
<Alert {...props} variant={AlertVariant.danger} />
</div>
);
export default AlertWrapper;
- Always export nested types. It can be required by a consumer app when constructing component props.
import React from 'react';
import { Alert, AlertProps, AlertVariant } from '@patternfly/react-core';
export type CustomType = 'A' | 'B' | 2 | 'something'
export interface CustomInterface {
a: string,
b?: number
}
export interface AlertWrapperProps extends Omit<AlertProps, 'variant'> {
nestedNonPrimitiveType: CustomType
nestedNonPrimitiveInterface: CustomInterface
nestedNonPrimitiveArray: CustomType[]
}
const AlertWrapper: React.FunctionComponent<AlertWrapperProps> = ({ specificRequiredProp, ...props }) => (
<div>
<span>{specificRequiredProp}</span>
<Alert {...props} variant={AlertVariant.danger} />
</div>
);
export default AlertWrapper;
- Use PascalCase for type names.
- Do not use I as a prefix for interface names.
- Use PascalCase for enum values.
- Use camelCase for function names.
- Use camelCase for property names and local variables.
- Do not use _ as a prefix for private properties.
- Use whole words in names when possible.
- Always add a short description to exported types. The description is used for generating documentation
- Use JSDoc style comments for functions, interfaces, enums, and classes.
export interface AlertWrapperProps extends Omit<AlertProps, 'variant'> {
/**
* Alert id
*/
id: string,
/**
* A content displayed as Patternfly Alert children
*/
description: React.ReactNode
}
- Do not export types/functions unless you need to share it across multiple components.
- Do not introduce new types/values to the global namespace.
- Shared types should be defined in types.ts.
- Within a file, type definitions should come first.
- Use undefined. Do not use null.
For a variety of reasons, we avoid certain constructs, and use some of our own. Among them:
- Do not use for..in statements; instead, use ts.forEach, ts.forEachKey and ts.forEachValue. Be aware of their slightly different semantics.
- Try to use ts.forEach, ts.map, and ts.filter instead of loops when it is not strongly inconvenient.
- Use arrow functions over anonymous function expressions.
- Open curly braces always go on the same line as whatever necessitates them.
- Parenthesized constructs should have no surrounding whitespace.
- A single space follows commas, colons, and semicolons in those constructs. For example:
for (var i = 0, n = str.length; i < 10; i++) { }
if (x < 10) { }
function f(x: number, y: string): void { }
- Use a single declaration per variable statement
(i.e. use var x = 1; var y = 2; over var x = 1, y = 2;).
- Pick a TO DO card from: https://github.com/RedHatInsights/frontend-components/projects/1 and move it to in-progress column. Only modules from the components package should be migrated at the moment.
- Make sure that all local module dependencies and already migrated to TS. If some of the local dependencies have not been migrated, migrate them first and move the related cards to the in-progress column
import A from './a' // <- not migrated to TS, has to be migrated first
- Make sure that the
index
file in the appropriate folder is migrated to TS. If not, do it.
- index.js
+ index.ts
- Follow typescript guidelines when refactoring
- Create PR and link it to the project card