From d9ee06e70fb9f4a5244170b096054fbec6709a41 Mon Sep 17 00:00:00 2001 From: Alec Merdler Date: Mon, 14 May 2018 11:15:33 -0400 Subject: [PATCH] add 'firehoseFor' factory function and switched uses in some components --- .../cloud-services/catalog-source.spec.tsx | 61 +++++----- .../clusterserviceversion-resource.spec.tsx | 1 + .../cloud-services/create-crd-yaml.spec.tsx | 24 ++-- .../components/utils/firehose.spec.tsx | 43 +++++++ .../cloud-services/catalog-source.tsx | 51 +++----- .../cloud-services/create-crd-yaml.tsx | 25 ++-- frontend/public/components/create-yaml.tsx | 13 +- .../utils/{firehose.jsx => firehose.tsx} | 111 +++++++++++++++--- 8 files changed, 224 insertions(+), 105 deletions(-) create mode 100644 frontend/__tests__/components/utils/firehose.spec.tsx rename frontend/public/components/utils/{firehose.jsx => firehose.tsx} (65%) diff --git a/frontend/__tests__/components/cloud-services/catalog-source.spec.tsx b/frontend/__tests__/components/cloud-services/catalog-source.spec.tsx index a93245c7c76..9f304079924 100644 --- a/frontend/__tests__/components/cloud-services/catalog-source.spec.tsx +++ b/frontend/__tests__/components/cloud-services/catalog-source.spec.tsx @@ -8,10 +8,9 @@ import { Link } from 'react-router-dom'; import { safeLoad, safeDump } from 'js-yaml'; import Spy = jasmine.Spy; -import { CatalogSourceDetails, CatalogSourceDetailsProps, CatalogSourceDetailsPage, CatalogSourceDetailsPageProps, PackageHeader, PackageHeaderProps, PackageRow, PackageRowProps, PackageList, PackageListProps, CreateSubscriptionYAML, CreateSubscriptionYAMLProps } from '../../../public/components/cloud-services/catalog-source'; +import { CatalogSourceDetails, CatalogSourceFirehose, CatalogSourceDetailsProps, CatalogSourceDetailsPage, CatalogSourceDetailsPageProps, PackageHeader, PackageHeaderProps, PackageRow, PackageRowProps, PackageList, PackageListProps, CreateSubscriptionYAML, CreateSubscriptionYAMLProps, CreateSubscriptionYAMLFirehose } from '../../../public/components/cloud-services/catalog-source'; import { ClusterServiceVersionLogo, SubscriptionKind } from '../../../public/components/cloud-services'; -import { referenceForModel } from '../../../public/module/k8s'; -import { SubscriptionModel, CatalogSourceModel } from '../../../public/models'; +import { SubscriptionModel } from '../../../public/models'; import { ListHeader, ColHead, List } from '../../../public/components/factory'; import { Firehose, NavTitle, LoadingBox, ErrorBoundary } from '../../../public/components/utils'; import { CreateYAML } from '../../../public/components/create-yaml'; @@ -159,27 +158,18 @@ describe(CatalogSourceDetailsPage.displayName, () => { expect(wrapper.find(NavTitle).props().title).toEqual('Open Cloud Services'); }); - it('creates a `Firehose` component for the necessary resources', () => { - expect(wrapper.find(Firehose).props().resources).toEqual([{ - kind: referenceForModel(CatalogSourceModel), - name: 'tectonic-ocs', - namespace: 'tectonic-system', - isList: false, - prop: 'catalogSource', - }, { - kind: referenceForModel(SubscriptionModel), - isList: true, - prop: 'subscription', - }, { - kind: 'ConfigMap', - isList: false, - name: 'tectonic-ocs', - namespace: 'tectonic-system', - prop: 'configMap'}]); - }); - - it('renders `CatalogSourceDetails` component', () => { - expect(wrapper.find(Firehose).find(CatalogSourceDetails).exists()).toBe(true); + it('renders `CatalogSourceDetails` component wrapped in a custom `Firehose`', () => { + const configMap = {loaded: false, loadError: null, data: null}; + const subscription = {loaded: false, loadError: null, data: null}; + const catalogSource = {loaded: false, loadError: null, data: null}; + + const render = wrapper.find(CatalogSourceFirehose).props().render; + wrapper = shallow(
{render({configMap, subscription, catalogSource})}
); + + expect(wrapper.find(CatalogSourceDetails).props().ns).toEqual('default'); + expect(wrapper.find(CatalogSourceDetails).props().configMap).toEqual(configMap); + expect(wrapper.find(CatalogSourceDetails).props().subscription).toEqual(subscription); + expect(wrapper.find(CatalogSourceDetails).props().catalogSource).toEqual(catalogSource); }); }); @@ -193,23 +183,25 @@ describe(CreateSubscriptionYAML.displayName, () => { wrapper = shallow(); }); - it('renders a `Firehose` for the catalog ConfigMap', () => { - expect(wrapper.find(Firehose).props().resources).toEqual([ + it('renders a custom `Firehose` for the catalog ConfigMap', () => { + expect(wrapper.find(CreateSubscriptionYAMLFirehose).shallow().find(Firehose).props().resources).toEqual([ {kind: 'ConfigMap', name: 'tectonic-ocs', namespace: 'tectonic-system', isList: false, prop: 'ConfigMap'} ]); }); it('renders YAML editor component wrapped by an error boundary component', () => { - wrapper = wrapper.setProps({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}} as any); + const render = wrapper.find(CreateSubscriptionYAMLFirehose).props().render; + wrapper = shallow(
{render({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}})}
); - expect(wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).exists()).toBe(true); - expect(wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(CreateYAML).exists()).toBe(true); + expect(wrapper.childAt(0).dive().find(ErrorBoundary).exists()).toBe(true); + expect(wrapper.childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(CreateYAML).exists()).toBe(true); }); it('registers example YAML templates using the package default channel', () => { - wrapper = wrapper.setProps({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}} as any); + const render = wrapper.find(CreateSubscriptionYAMLFirehose).props().render; + wrapper = shallow(
{render({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}})}
); - wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).childAt(0).dive(); + wrapper.childAt(0).dive().find(ErrorBoundary).childAt(0).dive(); const subTemplate: SubscriptionKind = safeLoad(registerTemplateSpy.calls.argsFor(0)[1]); expect(registerTemplateSpy.calls.count()).toEqual(1); @@ -221,9 +213,10 @@ describe(CreateSubscriptionYAML.displayName, () => { }); it('does not render YAML editor component if ConfigMap has not loaded yet', () => { - wrapper = wrapper.setProps({ConfigMap: {loaded: false}} as any); + const render = wrapper.find(CreateSubscriptionYAMLFirehose).props().render; + wrapper = shallow(
{render({ConfigMap: {loaded: false}})}
); - expect(wrapper.find(Firehose).childAt(0).dive().find(CreateYAML).exists()).toBe(false); - expect(wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(LoadingBox).exists()).toBe(true); + expect(wrapper.childAt(0).dive().find(CreateYAML).exists()).toBe(false); + expect(wrapper.childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(LoadingBox).exists()).toBe(true); }); }); diff --git a/frontend/__tests__/components/cloud-services/clusterserviceversion-resource.spec.tsx b/frontend/__tests__/components/cloud-services/clusterserviceversion-resource.spec.tsx index 538922166ec..7e56cf3cc85 100644 --- a/frontend/__tests__/components/cloud-services/clusterserviceversion-resource.spec.tsx +++ b/frontend/__tests__/components/cloud-services/clusterserviceversion-resource.spec.tsx @@ -302,6 +302,7 @@ describe(ClusterServiceVersionPrometheusGraph.displayName, () => { }); describe('ResourcesList', () => { + it('uses the resources defined in the CSV', () => { const kindObj: K8sKind = { abbr: '', diff --git a/frontend/__tests__/components/cloud-services/create-crd-yaml.spec.tsx b/frontend/__tests__/components/cloud-services/create-crd-yaml.spec.tsx index 8c7420b0172..6de9446214a 100644 --- a/frontend/__tests__/components/cloud-services/create-crd-yaml.spec.tsx +++ b/frontend/__tests__/components/cloud-services/create-crd-yaml.spec.tsx @@ -25,41 +25,45 @@ describe(CreateCRDYAML.displayName, () => { }); it('renders a `Firehose` for the ClusterServiceVersion', () => { - expect(wrapper.find(Firehose).props().resources).toEqual([ + expect(wrapper.shallow().find(Firehose).props().resources).toEqual([ {kind: referenceForModel(ClusterServiceVersionModel), name: 'example', namespace: 'default', isList: false, prop: 'ClusterServiceVersion'} ]); }); it('renders YAML editor component', () => { - wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}} as any); + const render = wrapper.find('CreateCRDYAMLFirehose').prop('render'); + wrapper = shallow(
{render({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}})}
); - expect(wrapper.find(Firehose).childAt(0).dive().find(CreateYAML).exists()).toBe(true); + expect(wrapper.childAt(0).shallow().find(CreateYAML).exists()).toBe(true); }); it('registers example YAML templates using annotations on the ClusterServiceVersion', () => { let data = _.cloneDeep(testClusterServiceVersion); data.metadata.annotations = {'alm-examples': JSON.stringify([testResourceInstance])}; - wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: true, data}} as any); + const render = wrapper.find('CreateCRDYAMLFirehose').prop('render'); + wrapper = shallow(
{render({ClusterServiceVersion: {loaded: true, data}})}
); - wrapper.find(Firehose).childAt(0).dive(); + wrapper.childAt(0).dive(); expect(registerTemplateSpy.calls.count()).toEqual(1); expect(registerTemplateSpy.calls.argsFor(0)[1]).toEqual(safeDump(testResourceInstance)); }); it('registers fallback example YAML template if annotations not present on ClusterServiceVersion', () => { - wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}} as any); + const render = wrapper.find('CreateCRDYAMLFirehose').prop('render'); + wrapper = shallow(
{render({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}})}
); - wrapper.find(Firehose).childAt(0).dive(); + wrapper.childAt(0).dive(); expect(registerTemplateSpy.calls.count()).toEqual(1); expect(registerTemplateSpy.calls.argsFor(0)[1]).not.toEqual(safeDump(testResourceInstance)); }); it('does not render YAML editor component if ClusterServiceVersion has not loaded yet', () => { - wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: false}} as any); + const render = wrapper.find('CreateCRDYAMLFirehose').prop('render'); + wrapper = shallow(
{render({ClusterServiceVersion: {loaded: false}})}
); - expect(wrapper.find(Firehose).childAt(0).dive().find(CreateYAML).exists()).toBe(false); - expect(wrapper.find(Firehose).childAt(0).dive().find(LoadingBox).exists()).toBe(true); + expect(wrapper.childAt(0).dive().find(CreateYAML).exists()).toBe(false); + expect(wrapper.childAt(0).dive().find(LoadingBox).exists()).toBe(true); }); }); diff --git a/frontend/__tests__/components/utils/firehose.spec.tsx b/frontend/__tests__/components/utils/firehose.spec.tsx new file mode 100644 index 00000000000..77ff082a6df --- /dev/null +++ b/frontend/__tests__/components/utils/firehose.spec.tsx @@ -0,0 +1,43 @@ +/* eslint-disable no-unused-vars, no-undef */ + +import * as React from 'react'; +import { shallow } from 'enzyme'; +import * as _ from 'lodash-es'; + +import { Firehose, FirehoseResource, firehoseFor } from '../../../public/components/utils'; + +describe('firehoseFor', () => { + type ComponentProps = {pods: any, namespace: string}; + let resources: {pods: FirehoseResource}; + const Component: React.SFC = (props) => {props.pods.loaded}; + + beforeEach(() => { + resources = { + pods: {kind: 'Pod', isList: true}, + }; + }); + + it('returns a component which accepts a render callback', (done) => { + const FirehosedComponent = firehoseFor(resources); + const render = () => { + done(); + return
; + }; + + shallow().childAt(0).dive(); + }); + + it('wraps render callback component in a `Firehose`', () => { + const FirehosedComponent = firehoseFor(resources); + const wrapper = shallow( } />); + + expect(wrapper.find(Firehose).childAt(0).shallow().find(Component).exists()).toBe(true); + }); + + it('passes `resources` to `Firehose` component', () => { + const FirehosedComponent = firehoseFor(resources); + const wrapper = shallow( } />); + + expect(wrapper.find(Firehose).props().resources).toEqual(_.map(resources, (res, prop) => ({...res, prop}))); + }); +}); diff --git a/frontend/public/components/cloud-services/catalog-source.tsx b/frontend/public/components/cloud-services/catalog-source.tsx index 1fdb5c61c8e..cb94fa226cd 100644 --- a/frontend/public/components/cloud-services/catalog-source.tsx +++ b/frontend/public/components/cloud-services/catalog-source.tsx @@ -6,7 +6,7 @@ import { match, Link } from 'react-router-dom'; import { Helmet } from 'react-helmet'; import { safeLoad } from 'js-yaml'; -import { NavTitle, Firehose, MsgBox, LoadingBox, withFallback } from '../utils'; +import { NavTitle, MsgBox, LoadingBox, withFallback, firehoseFor } from '../utils'; import { CreateYAML } from '../create-yaml'; import { ClusterServiceVersionLogo, SubscriptionKind, CatalogSourceKind, ClusterServiceVersionKind, Package } from './index'; import { SubscriptionModel, CatalogSourceModel } from '../../models'; @@ -97,34 +97,26 @@ export const CatalogSourceDetails: React.SFC = ({cata :
; }; +export const CatalogSourceFirehose = firehoseFor({ + catalogSource: {kind: referenceForModel(CatalogSourceModel), name: 'tectonic-ocs', namespace: 'tectonic-system', isList: false}, + subscription: {kind: referenceForModel(SubscriptionModel), isList: true}, + configMap: {kind: 'ConfigMap', isList: false, name: 'tectonic-ocs', namespace: 'tectonic-system'}, +}); + export const CatalogSourceDetailsPage: React.SFC = (props) =>
Open Cloud Services - - {/* FIXME(alecmerdler): Hack because `Firehose` injects props without TypeScript knowing about it */} - - + + + } />
; +export const CreateSubscriptionYAMLFirehose = firehoseFor({ + ConfigMap: {kind: 'ConfigMap', isList: false, name: 'tectonic-ocs', namespace: 'tectonic-system'} +}); + export const CreateSubscriptionYAML: React.SFC = (props) => { type CreateProps = {ConfigMap: {loaded: boolean, data: K8sResourceKind}}; const Create = withFallback((createProps) => { @@ -150,16 +142,9 @@ export const CreateSubscriptionYAML: React.SFC = (p return ; }, () => ); - return - {/* FIXME(alecmerdler): Hack because `Firehose` injects props without TypeScript knowing about it */} - - ; + return + + } />; }; export type PackageHeaderProps = { @@ -203,3 +188,5 @@ PackageList.displayName = 'PackageList'; CatalogSourceDetails.displayName = 'CatalogSourceDetails'; CatalogSourceDetailsPage.displayName = 'CatalogSourceDetailPage'; CreateSubscriptionYAML.displayName = 'CreateSubscriptionYAML'; +CatalogSourceFirehose.displayName = 'CatalogSourceFirehose'; +CreateSubscriptionYAMLFirehose.displayName = 'CreateSubscriptionYAMLFirehose'; diff --git a/frontend/public/components/cloud-services/create-crd-yaml.tsx b/frontend/public/components/cloud-services/create-crd-yaml.tsx index 2f974e4a526..1385b6ca783 100644 --- a/frontend/public/components/cloud-services/create-crd-yaml.tsx +++ b/frontend/public/components/cloud-services/create-crd-yaml.tsx @@ -5,7 +5,7 @@ import { safeDump } from 'js-yaml'; import { match } from 'react-router-dom'; import * as _ from 'lodash-es'; -import { Firehose, LoadingBox } from '../utils'; +import { firehoseFor, LoadingBox } from '../utils'; import { CreateYAML } from '../create-yaml'; import { referenceForModel, K8sResourceKind, K8sResourceKindReference, kindForReference, apiVersionForReference } from '../../module/k8s'; import { ClusterServiceVersionModel } from '../../models'; @@ -19,6 +19,17 @@ import { ocsTemplates } from './ocs-templates'; export const CreateCRDYAML: React.SFC = (props) => { const annotationKey = 'alm-examples'; + const CreateCRDYAMLFirehose = firehoseFor({ + ClusterServiceVersion: { + kind: referenceForModel(ClusterServiceVersionModel), + name: props.match.params.appName, + namespace: props.match.params.ns, + isList: false, + prop: 'ClusterServiceVersion' + } + }); + CreateCRDYAMLFirehose.displayName = 'CreateCRDYAMLFirehose'; + const Create = (createProps: {ClusterServiceVersion: {loaded: boolean, data: ClusterServiceVersionKind}}) => { if (createProps.ClusterServiceVersion.loaded && createProps.ClusterServiceVersion.data) { try { @@ -34,15 +45,9 @@ export const CreateCRDYAML: React.SFC = (props) => { return ; }; - return - - ; + return + + } />; }; export type CreateCRDYAMLProps = { diff --git a/frontend/public/components/create-yaml.tsx b/frontend/public/components/create-yaml.tsx index 811ff7f0192..96a29e564c0 100644 --- a/frontend/public/components/create-yaml.tsx +++ b/frontend/public/components/create-yaml.tsx @@ -6,7 +6,7 @@ import { safeLoad } from 'js-yaml'; import { TEMPLATES } from '../yaml-templates'; import { connectToPlural } from '../kinds'; import { AsyncComponent } from './utils/async'; -import { Firehose, LoadingBox } from './utils'; +import { LoadingBox, firehoseFor } from './utils'; import { K8sKind, apiVersionForModel } from '../module/k8s'; import { ErrorPage404 } from './error'; import { ClusterServiceVersionModel } from '../models'; @@ -51,9 +51,14 @@ export const CreateYAML = connectToPlural((props: CreateYAMLProps) => { export const EditYAMLPage: React.SFC = (props) => { const Wrapper = (wrapperProps) => import('./edit-yaml').then(c => c.EditYAML)} create={false} showHeader={true} />; - return - - ; + const EditYAMLPageFirehose = firehoseFor({ + obj: {kind: props.kind, name: props.match.params.name, namespace: props.match.params.ns, isList: false}, + }); + EditYAMLPageFirehose.displayName = 'EditYAMLPageFirehose'; + + return + + } />; }; /* eslint-disable no-undef */ diff --git a/frontend/public/components/utils/firehose.jsx b/frontend/public/components/utils/firehose.tsx similarity index 65% rename from frontend/public/components/utils/firehose.jsx rename to frontend/public/components/utils/firehose.tsx index ebd674865ee..825a5d39766 100644 --- a/frontend/public/components/utils/firehose.jsx +++ b/frontend/public/components/utils/firehose.tsx @@ -1,3 +1,5 @@ +/* eslint-disable no-undef, no-unused-vars */ + import * as _ from 'lodash-es'; import * as React from 'react'; import * as PropTypes from 'prop-types'; @@ -5,20 +7,20 @@ import { connect } from 'react-redux'; import { Map as ImmutableMap } from 'immutable'; import { inject } from './index'; +import { K8sKind, K8sResourceKindReference } from '../../module/k8s'; import actions from '../../module/k8s/k8s-actions'; -export const makeReduxID = (k8sKind = {}, query) => { +export const makeReduxID = (k8sKind: K8sKind, query: Query) => { let qs = ''; if (!_.isEmpty(query)) { qs = `---${JSON.stringify(query)}`; } - return `${k8sKind.plural}${qs}`; + return `${_.get(k8sKind, 'plural')}${qs}`; }; -/** @type {(namespace: string, labelSelector?: any, fieldSelector?: any, name?: string) => {[key: string]: string}} */ -export const makeQuery = (namespace, labelSelector, fieldSelector, name, limit) => { - const query = {}; +export const makeQuery = (namespace: string, labelSelector?: LabelSelector, fieldSelector?, name?: string, limit?: number): Query => { + let query = {} as any; if (!_.isEmpty(labelSelector)) { query.labelSelector = labelSelector; @@ -92,10 +94,8 @@ const worstError = errors => { return worst; }; -// A wrapper Component that takes data out of redux for a list or object at some reduxID ... -// passing it to children -const ConnectToState = connect(({k8s}, {reduxes}) => { - const resources = {}; +const stateToProps = ({k8s}, {reduxes}) => { + const resources = {} as any; reduxes.forEach(redux => { resources[redux.prop] = processReduxId({k8s}, redux); @@ -112,23 +112,28 @@ const ConnectToState = connect(({k8s}, {reduxes}) => { reduxIDs: _.map(reduxes, 'reduxID'), resources, }); -})(props =>
+}; + +// A wrapper Component that takes data out of redux for a list or object at some reduxID ... +// passing it to children +const ConnectToState = connect(stateToProps)((props: ConnectToStateProps) =>
{inject(props.children, _.omit(props, ['children', 'className', 'reduxes']))}
); -const stateToProps = ({k8s}, {resources}) => ({ +const firehoseStateToProps = ({k8s}, {resources}) => ({ k8sModels: resources.reduce((models, {kind}) => models.set(kind, k8s.getIn(['RESOURCES', 'models', kind])), ImmutableMap()), }); export const Firehose = connect( - stateToProps, { + firehoseStateToProps, { stopK8sWatch: actions.stopK8sWatch, watchK8sObject: actions.watchK8sObject, watchK8sList: actions.watchK8sList, })( - /** @augments {React.Component<{k8sModels?: Map} */ - class Firehose extends React.Component { - componentWillMount (props=this.props) { + class Firehose extends React.Component { + private firehoses = []; + + componentWillMount (props = this.props) { const { watchK8sList, watchK8sObject, resources, k8sModels } = props; this.firehoses = resources.map(resource => { @@ -187,6 +192,7 @@ export const Firehose = connect( } } ); + Firehose.WrappedComponent.contextTypes = { router: PropTypes.object, }; @@ -209,3 +215,78 @@ Firehose.propTypes = { optional: PropTypes.bool, })).isRequired, }; + +export type FirehoseFor = (resources: {[K in ResourceKeys]: FirehoseResource}) => + React.ComponentType<{render: (props: {[K in ResourceKeys]: FirehoseResponse}) => JSX.Element}>; + +/** + * Factory function to produce a component that renders a `Firehose` with render props matching `props.resources`. + */ +export const firehoseFor: FirehoseFor = (resources: {[K in ResourceKeys]: FirehoseResource}) => { + type FirehoseRCProps = {render: (props: {[K in ResourceKeys]: {}}) => JSX.Element}; + + // Needed because `Firehose` doesn't support React.Fragment + const Render = ({render, ...rest}) => render(rest); + + const FirehoseRC: React.SFC = (props) => ({...resource, prop}))}> + {/* FIXME: Replace this once `Firehose` itself uses render callback */} + + ; + + return FirehoseRC; +}; + +export type FirehoseResource = { + kind: K8sResourceKindReference; + name?: string; + namespace?: string; + selector?: {[key: string]: any}; + fieldSelector?: string; + isList: boolean; + optional?: boolean; + prop?: string; +}; + +export type FirehoseProps = { + k8sModels: Map; + watchK8sList: any; + watchK8sObject: any; + stopK8sWatch: any; + resources: FirehoseResource[]; + expand?: boolean; + children: React.ReactNode; +}; + +export type ConnectToStateProps = { + resources: { + kind: K8sResourceKindReference; + name: string; + namespace: string; + selector: {[key: string]: any}; + fieldSelector: string; + isList: boolean; + optional: boolean; + }[]; + loaded: boolean; + loadError: string; + reduxIDs: string[]; + filters: any; +} & React.HTMLProps; + +export type FirehoseResponse = { + loaded: boolean; + loadError: string | Object; + // FIXME(alecmerdler) + data: any; +}; + +export type LabelSelector = { + +}; + +export type Query = { + labelSelector: LabelSelector; + ns: string; + name: string; + fieldSelector: string; +};