diff --git a/frontend/package.json b/frontend/package.json
index 174f4cf0309c..e98e8378c3b1 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -57,6 +57,8 @@
"dependencies": {
"@patternfly/patternfly": "2.4.1",
"@patternfly/react-core": "3.2.4",
+ "@patternfly/react-table": "git+https://git@github.com/priley86/react-table.git",
+ "@patternfly/react-virtualized-extension": "git+https://git@github.com/priley86/react-virtualized-extension.git",
"brace": "0.11.x",
"classnames": "2.x",
"core-js": "2.x",
diff --git a/frontend/public/components/deployment.jsx b/frontend/public/components/deployment.jsx
index 80be6fa5d4eb..c8d9decb24ca 100644
--- a/frontend/public/components/deployment.jsx
+++ b/frontend/public/components/deployment.jsx
@@ -1,6 +1,10 @@
import * as React from 'react';
import * as _ from 'lodash-es';
-
+import {
+ headerCol,
+ sortable,
+} from '@patternfly/react-table';
+import { Link } from 'react-router-dom';
import { DeploymentModel } from '../models';
import { configureUpdateStrategyModal, errorModal } from './modals';
import { Conditions } from './conditions';
@@ -13,6 +17,7 @@ import {
ListPage,
WorkloadListHeader,
WorkloadListRow,
+ Table,
} from './factory';
import {
AsyncComponent,
@@ -26,6 +31,11 @@ import {
StatusIcon,
togglePaused,
WorkloadPausedAlert,
+ LabelList,
+ ResourceKebab,
+ ResourceLink,
+ resourcePath,
+ Selector,
} from './utils';
const {ModifyCount, AddStorage, EditEnvironment, common} = Kebab.factory;
@@ -117,7 +127,63 @@ const DeploymentsDetailsPage = props => ;
const Row = props => ;
-const DeploymentsList = props =>
;
+
+const kind = 'Deployment';
+
+const DeploymentTableRow = (o, index) => {
+ return {
+ id: index,
+ cells: [
+ {
+ title: ,
+ },
+ {
+ title: ,
+ },
+ {
+ title: ,
+ },
+ {
+ title:
+ {o.status.replicas || 0} of {o.spec.replicas} pods
+ ,
+ },
+ {
+ title: ,
+ },
+ {
+ title:
+
+
,
+ props: { className: 'pf-c-table__action'},
+ },
+ ]
+ };
+};
+
+const DeploymentTableRows = componentProps =>
+ _.map(componentProps.data, (obj, index) => obj && obj.metadata && DeploymentTableRow(obj, index));
+
+const DeploymentTableHeader = props => {
+ return [
+ { title: 'Name', sortField: 'metadata.name', transforms: [sortable], cellTransforms: [headerCol()], props},
+ { title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], props },
+ { title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], props},
+ { title: 'Status', sortField: 'metadata.labels', props: {...props, className: 'meta-status'},
+ { title: 'Pod Selector', sortField: 'spec.selector', transforms: [sortable], props },
+ // todo: add support for table actions api: https://github.com/patternfly/patternfly-react/pull/1441
+ // this is for the empty actions/kebab column header
+ { title: '' },
+ ];
+};
+
+const DeploymentsList = props =>
+
+ {/*
+
+
*/}
+;
+
const DeploymentsPage = props => ;
export {DeploymentsList, DeploymentsPage, DeploymentsDetailsPage};
diff --git a/frontend/public/components/factory/index.tsx b/frontend/public/components/factory/index.tsx
index acb1ae207a8b..e2ab2a67ccb8 100644
--- a/frontend/public/components/factory/index.tsx
+++ b/frontend/public/components/factory/index.tsx
@@ -2,3 +2,4 @@ export * from './details';
export * from './list-page';
export * from './list';
export * from './modal';
+export * from './table';
\ No newline at end of file
diff --git a/frontend/public/components/factory/table.tsx b/frontend/public/components/factory/table.tsx
new file mode 100644
index 000000000000..ddcaae32405c
--- /dev/null
+++ b/frontend/public/components/factory/table.tsx
@@ -0,0 +1,437 @@
+/* eslint-disable no-undef */
+import * as _ from 'lodash-es';
+import * as fuzzy from 'fuzzysearch';
+import * as PropTypes from 'prop-types';
+import * as React from 'react';
+import { connect } from 'react-redux';
+import { UIActions } from '../../ui/ui-actions';
+import { ingressValidHosts } from '../ingress';
+import { routeStatus } from '../routes';
+import { secretTypeFilterReducer } from '../secret';
+import { bindingType, roleType } from '../RBAC';
+import {
+ alertState,
+ alertStateOrder,
+ silenceState,
+ silenceStateOrder,
+} from '../../monitoring';
+import {
+ containerLinuxUpdateOperator,
+ EmptyBox
+} from '../utils';
+import {
+ getJobTypeAndCompletions,
+ nodeStatus,
+ planExternalName,
+ podPhase,
+ podPhaseFilterReducer,
+ podReadiness,
+ serviceCatalogStatus,
+ serviceClassDisplayName,
+ getClusterOperatorStatus,
+} from '../../module/k8s';
+
+import {
+ IRowData,
+ IExtraData,
+ Table as PfTable,
+ TableHeader,
+ TableBody,
+ SortByDirection,
+} from '@patternfly/react-table';
+
+import {
+ VirtualizedBody,
+ VirtualizedBodyWrapper,
+ VirtualizedRowWrapper,
+ WindowScroller
+} from '@patternfly/react-virtualized-extension';
+
+const fuzzyCaseInsensitive = (a, b) => fuzzy(_.toLower(a), _.toLower(b));
+
+// TODO: Having list filters here is undocumented, stringly-typed, and non-obvious. We can change that
+const listFilters = {
+ 'name': (filter, obj) => fuzzyCaseInsensitive(filter, obj.metadata.name),
+
+ 'alert-name': (filter, alert) => fuzzyCaseInsensitive(filter, _.get(alert, 'labels.alertname')),
+
+ 'alert-state': (filter, alert) => filter.selected.has(alertState(alert)),
+
+ 'silence-name': (filter, silence) => fuzzyCaseInsensitive(filter, silence.name),
+
+ 'silence-state': (filter, silence) => filter.selected.has(silenceState(silence)),
+
+ // Filter role by role kind
+ 'role-kind': (filter, role) => filter.selected.has(roleType(role)),
+
+ // Filter role bindings by role kind
+ 'role-binding-kind': (filter, binding) => filter.selected.has(bindingType(binding)),
+
+ // Filter role bindings by text match
+ 'role-binding': (str, {metadata, roleRef, subject}) => {
+ const isMatch = val => fuzzyCaseInsensitive(str, val);
+ return [metadata.name, roleRef.name, subject.kind, subject.name].some(isMatch);
+ },
+
+ // Filter role bindings by roleRef name
+ 'role-binding-roleRef': (roleRef, binding) => binding.roleRef.name === roleRef,
+
+ 'selector': (selector, obj) => {
+ if (!selector || !selector.values || !selector.values.size) {
+ return true;
+ }
+ return selector.values.has(_.get(obj, selector.field));
+ },
+
+ 'pod-status': (phases, pod) => {
+ if (!phases || !phases.selected || !phases.selected.size) {
+ return true;
+ }
+
+ const phase = podPhaseFilterReducer(pod);
+ return phases.selected.has(phase) || !_.includes(phases.all, phase);
+ },
+
+ 'node-status': (statuses, node) => {
+ if (!statuses || !statuses.selected || !statuses.selected.size) {
+ return true;
+ }
+
+ const status = nodeStatus(node);
+ return statuses.selected.has(status) || !_.includes(statuses.all, status);
+ },
+
+ 'clusterserviceversion-resource-kind': (filters, resource) => {
+ if (!filters || !filters.selected || !filters.selected.size) {
+ return true;
+ }
+ return filters.selected.has(resource.kind);
+ },
+ 'clusterserviceversion-status': (filters, csv) => {
+ if (!filters || !filters.selected || !filters.selected.size) {
+ return true;
+ }
+ return filters.selected.has(_.get(csv.status, 'reason')) || !_.includes(filters.all, _.get(csv.status, 'reason'));
+ },
+
+ 'build-status': (phases, build) => {
+ if (!phases || !phases.selected || !phases.selected.size) {
+ return true;
+ }
+
+ const phase = build.status.phase;
+ return phases.selected.has(phase) || !_.includes(phases.all, phase);
+ },
+
+ 'build-strategy': (strategies, buildConfig) => {
+ if (!strategies || !strategies.selected || !strategies.selected.size) {
+ return true;
+ }
+
+ const strategy = buildConfig.spec.strategy.type;
+ return strategies.selected.has(strategy) || !_.includes(strategies.all, strategy);
+ },
+
+ 'route-status': (statuses, route) => {
+ if (!statuses || !statuses.selected || !statuses.selected.size) {
+ return true;
+ }
+
+ const status = routeStatus(route);
+ return statuses.selected.has(status) || !_.includes(statuses.all, status);
+ },
+
+ 'catalog-status': (statuses, catalog) => {
+ if (!statuses || !statuses.selected || !statuses.selected.size) {
+ return true;
+ }
+
+ const status = serviceCatalogStatus(catalog);
+ return statuses.selected.has(status) || !_.includes(statuses.all, status);
+ },
+
+ 'secret-type': (types, secret) => {
+ if (!types || !types.selected || !types.selected.size) {
+ return true;
+ }
+ const type = secretTypeFilterReducer(secret);
+ return types.selected.has(type) || !_.includes(types.all, type);
+ },
+
+ 'pvc-status': (phases, pvc) => {
+ if (!phases || !phases.selected || !phases.selected.size) {
+ return true;
+ }
+
+ const phase = pvc.status.phase;
+ return phases.selected.has(phase) || !_.includes(phases.all, phase);
+ },
+
+ // Filter service classes by text match
+ 'service-class': (str, serviceClass) => {
+ const displayName = serviceClassDisplayName(serviceClass);
+ return fuzzyCaseInsensitive(str, displayName);
+ },
+
+ 'cluster-operator-status': (statuses, operator) => {
+ if (!statuses || !statuses.selected || !statuses.selected.size) {
+ return true;
+ }
+
+ const status = getClusterOperatorStatus(operator);
+ return statuses.selected.has(status) || !_.includes(statuses.all, status);
+ },
+};
+
+const getFilteredRows = (_filters, objects) => {
+ if (_.isEmpty(_filters)) {
+ return objects;
+ }
+
+ _.each(_filters, (value, name) => {
+ const filter = listFilters[name];
+ if (_.isFunction(filter)) {
+ objects = _.filter(objects, o => filter(value, o));
+ }
+ });
+
+ return objects;
+};
+
+const filterPropType = (props, propName, componentName) => {
+ if (!props) {
+ return;
+ }
+
+ for (const key of _.keys(props[propName])) {
+ if (key in listFilters || key === 'loadTest') {
+ continue;
+ }
+ return new Error(`Invalid prop '${propName}' in '${componentName}'. '${key}' is not a valid filter type!`);
+ }
+};
+
+const sorts = {
+ alertStateOrder,
+ daemonsetNumScheduled: daemonset => _.toInteger(_.get(daemonset, 'status.currentNumberScheduled')),
+ dataSize: resource => _.size(_.get(resource, 'data')),
+ ingressValidHosts,
+ serviceCatalogStatus,
+ jobCompletions: job => getJobTypeAndCompletions(job).completions,
+ jobType: job => getJobTypeAndCompletions(job).type,
+ nodeReadiness: node => {
+ let readiness = _.get(node, 'status.conditions');
+ readiness = _.find(readiness, {type: 'Ready'});
+ return _.get(readiness, 'status');
+ },
+ nodeUpdateStatus: node => _.get(containerLinuxUpdateOperator.getUpdateStatus(node), 'text'),
+ numReplicas: resource => _.toInteger(_.get(resource, 'status.replicas')),
+ planExternalName,
+ podPhase,
+ podReadiness,
+ serviceClassDisplayName,
+ silenceStateOrder,
+ string: val => JSON.stringify(val),
+ getClusterOperatorStatus,
+};
+
+const stateToProps = ({UI}, {data = [], defaultSortField = 'metadata.name', defaultSortFunc = undefined, filters = {}, loaded = false, reduxID = null, reduxIDs = null, staticFilters = [{}]}) => {
+ const allFilters = staticFilters ? Object.assign({}, filters, ...staticFilters) : filters;
+ let newData = getFilteredRows(allFilters, data);
+
+ const listId = reduxIDs ? reduxIDs.join(',') : reduxID;
+ // Only default to 'metadata.name' if no `defaultSortFunc`
+ const currentSortField = UI.getIn(['listSorts', listId, 'field'], defaultSortFunc ? undefined : defaultSortField);
+ const currentSortFunc = UI.getIn(['listSorts', listId, 'func'], defaultSortFunc);
+ const currentSortOrder = UI.getIn(['listSorts', listId, 'orderBy'], SortByDirection.asc);
+
+ if (loaded) {
+ let sortBy: string | Function = 'metadata.name';
+ if (currentSortField) {
+ // Sort resources by one of their fields as a string
+ sortBy = resource => sorts.string(_.get(resource, currentSortField, ''));
+ } else if (currentSortFunc && sorts[currentSortFunc]) {
+ // Sort resources by a function in the 'sorts' object
+ sortBy = sorts[currentSortFunc];
+ }
+
+ // Always set the secondary sort criteria to ascending by name
+ newData = _.orderBy(newData, [sortBy, 'metadata.name'], [currentSortOrder, SortByDirection.asc]);
+ }
+
+ return {
+ currentSortField,
+ currentSortFunc,
+ currentSortOrder,
+ data: newData,
+ listId,
+ };
+};
+
+ export const Table = connect(stateToProps, {sortList: UIActions.sortList})(
+ class TableInner extends React.Component {
+ static propTypes = {
+ data: PropTypes.array,
+ EmptyMsg: PropTypes.func,
+ expand: PropTypes.bool,
+ fieldSelector: PropTypes.string,
+ filters: filterPropType,
+ Header: PropTypes.func.isRequired,
+ Rows: PropTypes.func.isRequired,
+ loaded: PropTypes.bool,
+ loadError: PropTypes.oneOfType([PropTypes.object, PropTypes.string]),
+ mock: PropTypes.bool,
+ namespace: PropTypes.string,
+ reduxID: PropTypes.string,
+ reduxIDs: PropTypes.array,
+ selector: PropTypes.object,
+ staticFilters: PropTypes.array,
+ virtualize: PropTypes.bool,
+ currentSortField: PropTypes.string,
+ currentSortFunc: PropTypes.string,
+ currentSortOrder: PropTypes.any,
+ defaultSortField: PropTypes.string,
+ defaultSortFunc: PropTypes.string,
+ label: PropTypes.string,
+ listId: PropTypes.string,
+ sortList: PropTypes.func,
+ onSelect: PropTypes.func,
+ };
+ _columnShift: number;
+
+ constructor(props){
+ super(props);
+ const componentProps: any = _.pick(props, ['data', 'filters', 'selected', 'match', 'kindObj']);
+ const columns = props.Header(componentProps);
+ //sort by first column
+ this.state = {
+ columns: columns,
+ sortBy: {}
+ };
+ this._applySort = this._applySort.bind(this);
+ this._onSort = this._onSort.bind(this);
+ this._columnShift = props.onSelect ? 1 : 0; //shift indexes by 1 if select provided
+ }
+
+ componentDidMount(){
+ const {columns} = this.state;
+ const sp = new URLSearchParams(window.location.search);
+ const columnIndex = _.findIndex(columns, {title: sp.get('sortBy')});
+
+ if(columnIndex > -1){
+ const sortOrder = sp.get('orderBy') || SortByDirection.asc;
+ const column = columns[columnIndex];
+ this._applySort(column.sortField, sortOrder, column.title);
+ this.setState({
+ sortBy: {
+ index: columnIndex + this._columnShift,
+ direction: sortOrder
+ }
+ })
+ }
+ }
+
+ _applySort(sortField, direction, columnTitle){
+ const {sortList, listId, currentSortFunc} = this.props;
+ const applySort = _.partial(sortList, listId);
+ applySort(sortField, currentSortFunc, direction, columnTitle);
+ }
+
+ _onSort(_event, index, direction){
+ const sortColumn = this.state.columns[index - this._columnShift];
+
+ this._applySort(sortColumn.sortField, direction, sortColumn.title);
+
+ this.setState({
+ sortBy: {
+ index: index,
+ direction: direction
+ }
+ });
+ }
+
+ render() {
+ //todo: handle expand
+ const {expand, Rows, label, onSelect, selectedResourcesForKind, 'aria-label': ariaLabel, virtualize} = this.props;
+ const {sortBy, columns} = this.state;
+ const componentProps: any = _.pick(this.props, ['data', 'filters', 'selected', 'match', 'kindObj']);
+ const rows = Rows(componentProps, selectedResourcesForKind);
+
+ if(virtualize){
+ return rows ? (
+
+ {({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => (
+
+
+ document.querySelector('#content-scrollable')}
+ rowKey="id" />
+
+ )}
+
+ ) : ;
+ } else {
+ return rows ? (
+
+
+
+
+ ) : ;
+ }
+ }
+ });
+
+
+ export type TableInnerProps = {
+ 'aria-label': string;
+ currentSortField?: string;
+ currentSortFunc?: string;
+ currentSortOrder?: any;
+ data?: any[];
+ defaultSortField?: string;
+ defaultSortFunc?: string;
+ EmptyMsg?: React.ComponentType<{}>;
+ expand?: boolean;
+ fieldSelector?: string;
+ filters?: {[name: string]: any};
+ Header: React.ComponentType;
+ label?: string;
+ listId?: string;
+ loaded?: boolean;
+ loadError?: string | Object;
+ mock?: boolean;
+ namespace?: string;
+ reduxID?: string;
+ reduxIDs?: string[];
+ Row: React.ComponentType;
+ Rows: (...args)=> any;
+ selector?: Object;
+ sortList?: (listId: string, field: string, func: any, orderBy: string, column: string) => any;
+ selectedResourcesForKind?: string[];
+ onSelect?: (event: React.MouseEvent, isSelected: boolean, rowIndex: number, rowData: IRowData, extraData: IExtraData) => void;
+ staticFilters?: any[];
+ virtualize?: boolean;
+};
+
+export type TableInnerState = {
+ columns?: any[];
+ sortBy: object;
+}
diff --git a/frontend/public/components/utils/kebab.tsx b/frontend/public/components/utils/kebab.tsx
index e1f6336dac8e..322e22a7c917 100644
--- a/frontend/public/components/utils/kebab.tsx
+++ b/frontend/public/components/utils/kebab.tsx
@@ -4,8 +4,8 @@ import * as _ from 'lodash-es';
import * as React from 'react';
import { annotationsModal, configureReplicaCountModal, taintsModal, tolerationsModal, labelsModal, podSelectorModal, deleteModal } from '../modals';
-import { DropdownMixin } from './dropdown';
-import { history, resourceObjPath } from './index';
+import { resourceObjPath } from './index';
+import { OneOf, Dropdown, KebabToggle, DropdownItem, DropdownPosition } from '@patternfly/react-core';
import { referenceForModel, K8sResourceKind, K8sResourceKindReference, K8sKind } from '../../module/k8s';
import { connectToModel } from '../../kinds';
@@ -103,34 +103,69 @@ export const ResourceKebab = connectToModel((props: ResourceKebabProps) => {
/>;
});
-export class Kebab extends DropdownMixin {
+export class Kebab extends React.Component {
static factory: KebabFactory = kebabFactory;
- private onClick = this.onClick_.bind(this);
+ constructor(props) {
+ super(props);
+ this.state = {
+ isOpen: false
+ };
+ }
- onClick_(event, option) {
- event.preventDefault();
+ onToggle = isOpen => {
+ this.setState({
+ isOpen
+ });
+ };
- if (option.callback) {
- option.callback();
- }
+ onSelect = event => {
+ this.setState({
+ isOpen: !this.state.isOpen
+ });
+ };
- if (option.href) {
- history.push(option.href);
+ render() {
+ const { isOpen } = this.state;
+ const { options, isDisabled, position, id } = this.props;
+
+ const items = [];
+ if(options && options.length){
+ options.forEach((option) => {
+ items.push(
+
+ {option.label}
+
+ );
+ })
}
- this.hide();
+ return (
+ }
+ isOpen={isOpen}
+ isPlain
+ dropdownItems={items}
+ />
+ );
}
+}
- render() {
- const {options, isDisabled} = this.props;
-
- return
-
- {(!isDisabled && this.state.active) && }
-
;
- }
+export type KebabProps = {
+ id?: string;
+ isDisabled?: boolean;
+ options: KebabOption[];
+ position?: OneOf;
+}
+
+export type KebabState = {
+ isOpen: boolean;
}
export type KebabOption = {
diff --git a/frontend/public/style/_overrides.scss b/frontend/public/style/_overrides.scss
index ecd9714b7418..a650c92fb04a 100644
--- a/frontend/public/style/_overrides.scss
+++ b/frontend/public/style/_overrides.scss
@@ -470,3 +470,10 @@ tags-input .autocomplete .suggestion-item em {
display: none;
}
}
+
+// temporary td width's for demo only, responsive pf4 css to follow
+@media (min-width: $screen-md-min){
+ .pf-c-table .meta-status {
+ min-width: 100px;
+ }
+}
\ No newline at end of file
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 6cbb1eb53d55..06efd658d0f0 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -130,6 +130,11 @@
version "3.1.0"
resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.1.0.tgz#497c67a1cef50d1a2459ba60f315e448d2ad87fe"
+"@patternfly/patternfly@2.0.0":
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-2.0.0.tgz#8c701445c7a4fc89a128c1b4a99076d1c37dcbcb"
+ integrity sha512-Ezh5TDKEO0lFZ5DM+TrOqtsv82LjVXZX5kFLkpM2tZf4dIUCfrhWzGGcQg3Bb/DMU0wkicwuzCu7oqDw3ZoZxg==
+
"@patternfly/patternfly@2.4.1":
version "2.4.1"
resolved "https://registry.yarnpkg.com/@patternfly/patternfly/-/patternfly-2.4.1.tgz#2b82e946ec2cdf5a78535ba2b9d514151688dc2d"
@@ -148,10 +153,23 @@
exenv "^1.2.2"
focus-trap-react "^4.0.1"
-"@patternfly/react-icons@^3.7.4":
- version "3.7.4"
- resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-3.7.4.tgz#e4abe683de7208682dea257355768c4aefd1ff79"
- integrity sha512-YgKQt7qloOvGJEt3kRJLlRe6eJMwU0YbfuC2bsTzcmCReI5cSNv0ZyuV9rOIOpB5rRaNPmCm9RjKBCixFjcaYg==
+"@patternfly/react-core@^3.2.0":
+ version "3.2.6"
+ resolved "https://registry.yarnpkg.com/@patternfly/react-core/-/react-core-3.2.6.tgz#8cc2ca0d16084e4437a6c1b25d399a2db6129b2c"
+ integrity sha512-58GBM8SfhoggQldGArUR3LwvcMe+GaW/uTjB1/a3RTG6HXTfFfs33UXnQnN2IgWZmHOyxpjnWq2yEh5xwJs5HQ==
+ dependencies:
+ "@patternfly/react-icons" "^3.7.4"
+ "@patternfly/react-styles" "^3.0.2"
+ "@patternfly/react-tokens" "^2.3.3"
+ "@tippy.js/react" "^1.1.1"
+ emotion "^9.2.9"
+ exenv "^1.2.2"
+ focus-trap-react "^4.0.1"
+
+"@patternfly/react-icons@^3.7.3", "@patternfly/react-icons@^3.7.4":
+ version "3.7.5"
+ resolved "https://registry.yarnpkg.com/@patternfly/react-icons/-/react-icons-3.7.5.tgz#fc84079b8f9c1b283195d306841eec9a786e855a"
+ integrity sha512-Bejd6GAWfcDgA7YxvIcrohcBPVZUG34E3LWaJSHLUf8XADf33q7UvQ4YQ1eWk477o/GjZA3AX/71Y6op71WdDA==
"@patternfly/react-styles@^3.0.2":
version "3.0.2"
@@ -171,11 +189,32 @@
relative "^3.0.2"
resolve-from "^4.0.0"
+"@patternfly/react-table@git+https://git@github.com/priley86/react-table.git":
+ version "2.1.0"
+ resolved "git+https://git@github.com/priley86/react-table.git#5a8a70634afe5df4c73281060c92bbbd5db0118a"
+ dependencies:
+ "@patternfly/patternfly" "2.0.0"
+ "@patternfly/react-core" "^3.2.0"
+ "@patternfly/react-icons" "^3.7.3"
+ "@patternfly/react-styles" "^3.0.2"
+ exenv "^1.2.2"
+ reactabular-table "^8.14.0"
+
"@patternfly/react-tokens@^2.3.3":
version "2.3.3"
resolved "https://registry.yarnpkg.com/@patternfly/react-tokens/-/react-tokens-2.3.3.tgz#6fea36b284a36d4404b4bd75ddcc3fa5a833654f"
integrity sha512-+2SSGvOV1rZr1l6+p2QzORVJhpOKjrHqCBfkp10La7O93+mVLYU0vugTp1elhxeh32IuLCJAPDLrCJPBAfmYKw==
+"@patternfly/react-virtualized-extension@git+https://git@github.com/priley86/react-virtualized-extension.git":
+ version "2.1.0"
+ resolved "git+https://git@github.com/priley86/react-virtualized-extension.git#f7c06d5333f0f6a1ff3c6c5664afa7f5d1f28b35"
+ dependencies:
+ "@patternfly/patternfly" "2.4.1"
+ "@patternfly/react-icons" "^3.7.3"
+ "@patternfly/react-styles" "^3.0.2"
+ exenv "^1.2.2"
+ reactabular-table "^8.14.0"
+
"@plotly/d3-sankey@^0.5.1":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@plotly/d3-sankey/-/d3-sankey-0.5.1.tgz#c2862c71374aba4f097a95a3449c7274a15a22de"