diff --git a/frontend/__tests__/components/factory/details.spec.tsx b/frontend/__tests__/components/factory/details.spec.tsx index 88e559114b0..eb8fb1f76a1 100644 --- a/frontend/__tests__/components/factory/details.spec.tsx +++ b/frontend/__tests__/components/factory/details.spec.tsx @@ -1,18 +1,21 @@ import * as React from 'react'; -import { shallow, ShallowWrapper } from 'enzyme'; +import { Provider } from 'react-redux'; +import { mount, ReactWrapper } from 'enzyme'; -import { DetailsPage, DetailsPageProps } from '../../../public/components/factory/details'; -import { PodModel, ConfigMapModel } from '../../../public/models'; -import { referenceForModel } from '../../../public/module/k8s'; -import { Firehose } from '../../../public/components/utils'; +import store from '@console/internal/redux'; +import { DetailsPage, DetailsPageProps } from '@console/internal/components/factory/details'; +import { PodModel, ConfigMapModel } from '@console/internal/models'; +import { referenceForModel } from '@console/internal/module/k8s'; +import { Firehose } from '@console/internal/components/utils'; describe(DetailsPage.displayName, () => { - let wrapper: ShallowWrapper; + let wrapper: ReactWrapper; beforeEach(() => { const match = { params: { ns: 'default' }, isExact: true, path: '', url: '' }; - wrapper = shallow( + // Need full mount with redux store since this is a redux-connected component + wrapper = mount( { kind={referenceForModel(PodModel)} pages={[]} />, - ) - .childAt(0) - .shallow(); + { + wrappingComponent: ({ children }) => {children}, + }, + ); }); it('renders a `Firehose` using the given props', () => { diff --git a/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx b/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx index c082398cd16..ee4a1656ae3 100644 --- a/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx +++ b/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx @@ -41,13 +41,16 @@ describe('', () => { // Need full mount with redux store since this is a redux-connected component const getWrapper = (query: string) => { const wrapper = mount( - - - -

- - - , + +

+ , + { + wrappingComponent: ({ children }) => ( + + {children} + + ), + }, ); expect(wrapper.find('p.test-class').exists()).toBe(true); return wrapper; diff --git a/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx b/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx index 5c4ac28de07..2bf99183920 100644 --- a/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx +++ b/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx @@ -17,7 +17,9 @@ describe(CopyToClipboard.displayName, () => { .find(CTC) .props() .onCopy(); - wrapper.update(); + + // re-render component created via React.memo + wrapper.setProps({ value: 'FuzzBizz' }); expect(wrapper.find(Tooltip).props().content[0].props.children).toEqual('Copied'); }); @@ -28,7 +30,6 @@ describe(CopyToClipboard.displayName, () => { .find(CTC) .props() .onCopy(); - wrapper.update(); wrapper.find('.co-copy-to-clipboard__btn').simulate('mouseenter'); diff --git a/frontend/package.json b/frontend/package.json index 342638d7994..4429bd03add 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -139,7 +139,7 @@ }, "devDependencies": { "@types/classnames": "^2.2.7", - "@types/enzyme": "3.x", + "@types/enzyme": "3.10.x", "@types/glob": "7.x", "@types/immutable": "3.x", "@types/jasmine": "2.8.x", @@ -164,8 +164,8 @@ "chromedriver": "77.x", "circular-dependency-plugin": "5.0.2", "css-loader": "0.28.x", - "enzyme": "^3.9.0", - "enzyme-adapter-react-16": "1.12.1", + "enzyme": "3.10.x", + "enzyme-adapter-react-16": "1.15.2", "file-loader": "1.x", "find-up": "4.x", "fork-ts-checker-webpack-plugin": "0.x", diff --git a/frontend/packages/ceph-storage-plugin/src/components/modals/restore-pvc-modal/_restore-pvc-modal.scss b/frontend/packages/ceph-storage-plugin/src/components/modals/restore-pvc-modal/_restore-pvc-modal.scss new file mode 100644 index 00000000000..e3675c67f29 --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/components/modals/restore-pvc-modal/_restore-pvc-modal.scss @@ -0,0 +1,13 @@ +.ceph-restore-pvc-modal__details { + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.ceph-restore-pvc-modal__details-section { + margin-top: var(--pf-global--spacer--xl); +} + +.ceph-restore-pvc-modal__pvc-details { + font-weight: 600; +} diff --git a/frontend/packages/ceph-storage-plugin/src/components/modals/restore-pvc-modal/restore-pvc-modal.tsx b/frontend/packages/ceph-storage-plugin/src/components/modals/restore-pvc-modal/restore-pvc-modal.tsx new file mode 100644 index 00000000000..5b65116c6cd --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/components/modals/restore-pvc-modal/restore-pvc-modal.tsx @@ -0,0 +1,142 @@ +import './_restore-pvc-modal.scss'; + +import * as React from 'react'; + +import { Form, FormGroup, Grid, GridItem, TextInput } from '@patternfly/react-core'; +import { + HandlePromiseProps, + ResourceIcon, + withHandlePromise, +} from '@console/internal/components/utils/index'; +import { K8sResourceKind, k8sCreate, k8sGet } from '@console/internal/module/k8s'; +import { + ModalBody, + ModalComponentProps, + ModalSubmitFooter, + ModalTitle, + createModalLauncher, +} from '@console/internal/components/factory'; +import { NamespaceModel, PersistentVolumeClaimModel } from '@console/internal/models'; +import { getName, getNamespace } from '@console/shared'; + +import { VolumeSnapshotModel } from '../../../models'; + +export const RestorePVCModal = withHandlePromise((props: RestorePVCModalProps) => { + const { close, cancel, resource, errorMessage, inProgress, handlePromise } = props; + const [pvcResource, setResource] = React.useState(null); + const [restorePVCName, setPVCName] = React.useState(`${getName(resource) || 'pvc'}-restore`); + + React.useEffect(() => { + k8sGet(PersistentVolumeClaimModel, resource?.spec?.source?.name, getNamespace(resource)) + .then(setResource) + .catch((error) => { + setResource(null); + throw error; + }); + }, [resource]); + + const submit = (event: React.FormEvent) => { + event.preventDefault(); + const snapshotName = getName(resource); + const pvcSize = resource?.status?.restoreSize; + const accessModes = pvcResource?.spec?.accessModes; + const pvcStorageClass = pvcResource?.spec?.storageClassName; + const namespace = getNamespace(resource); + const restorePVCTemplate: K8sResourceKind = { + apiVersion: PersistentVolumeClaimModel.apiVersion, + kind: PersistentVolumeClaimModel.kind, + metadata: { + name: restorePVCName, + }, + spec: { + storageClassName: pvcStorageClass, + dataSource: { + name: snapshotName, + kind: VolumeSnapshotModel.kind, + apiGroup: 'snapshot.storage.k8s.io', + }, + accessModes: [...accessModes], + resources: { + requests: { + storage: pvcSize, + }, + }, + }, + }; + handlePromise(k8sCreate(PersistentVolumeClaimModel, restorePVCTemplate, { ns: namespace })) + .then(close) + .catch((error) => { + throw error; + }); + }; + + return ( +

+ Restore + +

After restore action is finished, a new PVC will be created.

+
+ + + +
+ + +
+

Date

+

{resource?.metadata?.creationTimestamp}

+
+
+

Status

+

{resource?.status?.readyToUse ? 'Ready' : 'Not Ready'}

+
+
+

Size

+

{resource?.status?.restoreSize || 'No Data'}

+
+
+ +
+

Namespace

+

+ + {getNamespace(resource)} +

+
+
+

API Version

+

{resource?.apiVersion}

+
+
+

Persistent Volume

+ + {resource?.spec?.source?.name} +
+
+
+
+
+
+ +
+ ); +}); + +export type RestorePVCModalProps = { + resource: K8sResourceKind; +} & HandlePromiseProps & + ModalComponentProps; + +export default createModalLauncher(RestorePVCModal); diff --git a/frontend/packages/ceph-storage-plugin/src/components/modals/volume-snapshot-modal/volume-snapshot-modal.tsx b/frontend/packages/ceph-storage-plugin/src/components/modals/volume-snapshot-modal/volume-snapshot-modal.tsx new file mode 100644 index 00000000000..1547da86767 --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/components/modals/volume-snapshot-modal/volume-snapshot-modal.tsx @@ -0,0 +1,123 @@ +import * as React from 'react'; + +import { + Dropdown, + Firehose, + FirehoseResourcesResult, + HandlePromiseProps, + withHandlePromise, +} from '@console/internal/components/utils'; +import { Form, FormGroup, TextInput } from '@patternfly/react-core'; +import { K8sResourceKind, K8sResourceKindReference, k8sCreate } from '@console/internal/module/k8s'; +import { + ModalBody, + ModalComponentProps, + ModalSubmitFooter, + ModalTitle, + createModalLauncher, +} from '@console/internal/components/factory'; +import { getName, getNamespace } from '@console/shared'; + +import { PersistentVolumeClaimModel } from '@console/internal/models'; +import { VolumeSnapshotModel } from '../../../models'; + +export type VolumeSnapshotModalProps = { + pvcData?: FirehoseResourcesResult; +} & HandlePromiseProps & + ModalComponentProps; + +export const VolumeSnapshotModal = withHandlePromise((props: VolumeSnapshotModalProps) => { + const { close, cancel, pvcData, errorMessage, inProgress, handlePromise } = props; + const resource = pvcData.data as K8sResourceKind; + const [snapshotName, setSnapshotName] = React.useState( + `${getName(resource) || 'pvc'}-snapshot-1`, + ); + const snapshotTypes = { + single: 'Single Snapshot', + }; + + const submit = (event: React.FormEvent) => { + event.preventDefault(); + const ns = getNamespace(resource); + const pvcName = getName(resource); + const snapshotTemplate: K8sResourceKind = { + apiVersion: VolumeSnapshotModel.apiVersion, + kind: VolumeSnapshotModel.kind, + metadata: { + name: snapshotName, + namespace: ns, + }, + spec: { + source: { + name: pvcName, + kind: PersistentVolumeClaimModel.kind, + }, + }, + }; + handlePromise(k8sCreate(VolumeSnapshotModel, snapshotTemplate)) + .then(close) + .catch((error) => { + throw error; + }); + }; + + return ( +
+
+ Create Snapshot + +

Creating snapshot for {getName(resource)}

+ + + + + + +
+ +
+
+ ); +}); + +type VolumeSnapshotModalWithFireHoseProps = { + name: string; + namespace: string; + kind: K8sResourceKindReference; + pvcData?: FirehoseResourcesResult; + resource?: K8sResourceKind; +} & ModalComponentProps; + +const VolumeSnapshotModalWithFireHose: React.FC = (props) => ( + + + +); + +export const volumeSnapshotModal = createModalLauncher(VolumeSnapshotModalWithFireHose); diff --git a/frontend/packages/ceph-storage-plugin/src/components/volume-snapshot/_volume-snapshot.scss b/frontend/packages/ceph-storage-plugin/src/components/volume-snapshot/_volume-snapshot.scss new file mode 100644 index 00000000000..1b223a7acf8 --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/components/volume-snapshot/_volume-snapshot.scss @@ -0,0 +1,9 @@ +.ceph-volume-snapshot__header { + border-bottom: 1px solid #ddd; + padding: 10px 30px; +} + +.ceph-volume-snapshot__header-buttons { + align-items: baseline; + display: flex; +} diff --git a/frontend/packages/ceph-storage-plugin/src/components/volume-snapshot/volume-snapshot.tsx b/frontend/packages/ceph-storage-plugin/src/components/volume-snapshot/volume-snapshot.tsx new file mode 100644 index 00000000000..aeeeb272fc7 --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/components/volume-snapshot/volume-snapshot.tsx @@ -0,0 +1,197 @@ +import './_volume-snapshot.scss'; + +import * as React from 'react'; +import * as classNames from 'classnames'; + +import { + DetailsPage, + ListPage, + Table, + TableData, + TableProps, + TableRow, +} from '@console/internal/components/factory'; +import { K8sResourceKind, referenceFor } from '@console/internal/module/k8s'; +import { + Kebab, + ResourceKebab, + ResourceLink, + ResourceSummary, + SectionHeading, + navFactory, +} from '@console/internal/components/utils'; +import { getName, getNamespace } from '@console/shared'; + +import { PersistentVolumeClaimModel } from '@console/internal/models'; +import { VolumeSnapshotModel } from '../../models'; +import { getKebabActionsForKind } from '../../utils/kebab-actions'; +import { sortable } from '@patternfly/react-table'; +import { volumeSnapshotModal } from '../modals/volume-snapshot-modal/volume-snapshot-modal'; + +const snapshotMenuActions = [...getKebabActionsForKind(VolumeSnapshotModel)]; + +const snapshotTableColumnClasses = [ + '', + classNames('pf-m-hidden', 'pf-m-visible-on-sm', 'pf-u-w-16-on-lg'), + classNames('pf-m-hidden', 'pf-m-visible-on-lg'), + classNames('pf-m-hidden', 'pf-m-visible-on-xl'), + classNames('pf-m-hidden', 'pf-m-visible-on-2xl'), + Kebab.columnClass, +]; + +const VolumeSnapshotTableHeader = () => { + return [ + { + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], + props: { className: snapshotTableColumnClasses[0] }, + }, + { + title: 'Date', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], + props: { className: snapshotTableColumnClasses[1] }, + }, + { + title: 'Status', + sortField: 'status.phase', + transforms: [sortable], + props: { className: snapshotTableColumnClasses[2] }, + }, + { + title: 'Size', + sortField: 'status.restoreSize', + transforms: [sortable], + props: { className: snapshotTableColumnClasses[3] }, + }, + { + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], + props: { className: snapshotTableColumnClasses[4] }, + }, + { + title: '', + props: { className: snapshotTableColumnClasses[5] }, + }, + ]; +}; +VolumeSnapshotTableHeader.displayName = 'SnapshotTableHeader'; + +const volumeSnapshotKind = referenceFor(VolumeSnapshotModel); + +const { details } = navFactory; + +const breadcrumbsForSnapshotDetailsPage = (match: any) => () => [ + { + name: PersistentVolumeClaimModel.labelPlural, + path: `/k8s/ns/${match.params.ns}/persistentvolumeclaims`, + }, + { + name: 'Snapshot Details', + path: `${match.url}`, + }, +]; + +const DetailsComponent = ({ obj: volumeSnapshot }) => ( + <> +
+ +
+
+ +
Status
+
{volumeSnapshot.status.readyToUse ? 'Ready' : 'Not Ready'}
+
Size
+
{volumeSnapshot.status.restoreSize || 'No Data'}
+
+
+
+
Persistent Volume Claim
+
+ +
+
+
+
+ +); + +export const VolumeSnapshotDetails = (props) => ( + +); + +type VolumeSnapshotTableRowProps = { + obj: K8sResourceKind; + index: number; + key?: string; + style: object; +}; + +const VolumeSnapshotTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => ( + + + + + + {obj?.metadata?.creationTimestamp} + + + {obj?.status?.readyToUse ? 'Ready' : 'Not Ready'} + + + {obj?.status?.restoreSize || 'No Data'} + + None + + + + +); + +VolumeSnapshotTableRow.displayName = 'SnapshotTableRow'; + +export const VolumeSnapshotList: React.FC = (props) => ( + +); + +export const VolumeSnapshotPage = (props) => ( + volumeSnapshotModal({ ...props })} + /> +); diff --git a/frontend/packages/ceph-storage-plugin/src/models.ts b/frontend/packages/ceph-storage-plugin/src/models.ts index 4612aa4788c..1ab04203df5 100644 --- a/frontend/packages/ceph-storage-plugin/src/models.ts +++ b/frontend/packages/ceph-storage-plugin/src/models.ts @@ -25,3 +25,16 @@ export const OCSServiceModel: K8sKind = { id: 'ocscluster', crd: true, }; + +export const VolumeSnapshotModel: K8sKind = { + label: 'Volume Snapshot', + apiVersion: 'snapshot.storage.k8s.io/v1alpha1', + apiGroup: '', + plural: 'volumesnapshots', + abbr: 'VS', + namespaced: true, + kind: 'VolumeSnapshot', + id: 'volumesnapshot', + labelPlural: 'Volume Snapshots', + crd: true, +}; diff --git a/frontend/packages/ceph-storage-plugin/src/plugin.ts b/frontend/packages/ceph-storage-plugin/src/plugin.ts index 65353239810..bb66766d62b 100644 --- a/frontend/packages/ceph-storage-plugin/src/plugin.ts +++ b/frontend/packages/ceph-storage-plugin/src/plugin.ts @@ -1,32 +1,35 @@ import * as _ from 'lodash'; import * as models from './models'; - import { - ActionFeatureFlag, + CAPACITY_USAGE_QUERIES, + STORAGE_HEALTH_QUERIES, + StorageDashboardQuery, +} from './constants/queries'; +import { ClusterServiceVersionAction, DashboardsCard, DashboardsOverviewHealthPrometheusSubsystem, DashboardsOverviewUtilizationItem, DashboardsTab, + FeatureFlag, KebabActions, ModelDefinition, ModelFeatureFlag, Plugin, + ResourceTabPage, RoutePage, + ResourceDetailsPage, + ActionFeatureFlag, } from '@console/plugin-sdk'; -import { - CAPACITY_USAGE_QUERIES, - STORAGE_HEALTH_QUERIES, - StorageDashboardQuery, -} from './constants/queries'; import { OCS_INDEPENDENT_FLAG, detectIndependentMode } from './features'; import { ClusterServiceVersionModel } from '@console/operator-lifecycle-manager/src/models'; import { GridPosition } from '@console/shared/src/components/dashboard/DashboardGrid'; import { OverviewQuery } from '@console/internal/components/dashboard/dashboards-page/cluster-dashboard/queries'; -import { getCephHealthState } from './components/dashboard-page/storage-dashboard/status-card/utils'; +import { referenceForModel, referenceFor } from '@console/internal/module/k8s'; +import { PersistentVolumeClaimModel } from '@console/internal/models'; import { getKebabActionsForKind } from './utils/kebab-actions'; -import { referenceForModel } from '@console/internal/module/k8s'; +import { getCephHealthState } from './components/dashboard-page/storage-dashboard/status-card/utils'; type ConsumedExtensions = | ModelFeatureFlag @@ -38,7 +41,12 @@ type ConsumedExtensions = | RoutePage | ActionFeatureFlag | ClusterServiceVersionAction - | KebabActions; + | KebabActions + | ResourceDetailsPage + | ResourceTabPage + | ClusterServiceVersionAction + | KebabActions + | FeatureFlag; const CEPH_FLAG = 'CEPH'; @@ -58,6 +66,18 @@ const plugin: Plugin = [ flag: CEPH_FLAG, }, }, + { + type: 'Page/Resource/Tab', + properties: { + href: 'volumesnapshots', + model: PersistentVolumeClaimModel, + name: 'Volume Snapshots', + loader: () => + import('./components/volume-snapshot/volume-snapshot').then( + (m) => m.VolumeSnapshotPage, + ) /* webpackChunkName: "ceph-storage-volume-snapshot" */, + }, + }, { type: 'Dashboards/Tab', properties: { @@ -281,6 +301,17 @@ const plugin: Plugin = [ required: OCS_INDEPENDENT_FLAG, }, }, + { + type: 'Page/Resource/Details', + properties: { + model: models.VolumeSnapshotModel, + loader: async () => + import( + './components/volume-snapshot/volume-snapshot' /* webpackChunkName: "ceph-storage-volume-snapshot-details" */ + ).then((m) => m.VolumeSnapshotDetails), + modelParser: referenceFor, + }, + }, { type: 'KebabActions', properties: { diff --git a/frontend/packages/ceph-storage-plugin/src/utils/delete-snapshot-workflow.ts b/frontend/packages/ceph-storage-plugin/src/utils/delete-snapshot-workflow.ts new file mode 100644 index 00000000000..e5c9724b6e0 --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/utils/delete-snapshot-workflow.ts @@ -0,0 +1,30 @@ +import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; + +import { KebabOption } from '@console/internal/components/utils'; + +export const DeleteSnapshot = (kind: K8sKind, resource: K8sResourceKind): KebabOption => { + return { + label: 'Delete Snapshot', + callback: () => { + const clusterObject = { resource }; + import( + '@console/internal/components/modals/delete-modal' /* webpackChunkName: "ceph-storage-delete-snapshot-modal" */ + ) + .then((m) => + m.deleteModal({ + kind, + resource: clusterObject.resource, + }), + ) + // eslint-disable-next-line no-console + .catch((e) => console.error(e)); + }, + accessReview: { + group: kind.apiGroup, + resource: kind.plural, + namespace: resource.metadata.namespace, + name: resource.metadata.name, + verb: 'delete', + }, + }; +}; diff --git a/frontend/packages/ceph-storage-plugin/src/utils/kebab-actions.ts b/frontend/packages/ceph-storage-plugin/src/utils/kebab-actions.ts index 4eb5b7e2049..da18dad0453 100644 --- a/frontend/packages/ceph-storage-plugin/src/utils/kebab-actions.ts +++ b/frontend/packages/ceph-storage-plugin/src/utils/kebab-actions.ts @@ -1,12 +1,18 @@ +import { ClonePVC } from './clone-workflow'; +import { DeleteSnapshot } from './delete-snapshot-workflow'; import { K8sKind } from '@console/internal/module/k8s'; import { KebabAction } from '@console/internal/components/utils'; import { PersistentVolumeClaimModel } from '@console/internal/models/index'; -import { ClonePVC } from './clone-workflow'; +import { RestorePVC } from './restore-pvc-workflow'; +import { SnapshotPVC } from './snapshot-workflow'; +import { VolumeSnapshotModel } from '../models'; export const getKebabActionsForKind = (resourceKind: K8sKind): KebabAction[] => { - const menuActions: KebabAction[] = []; if (resourceKind?.kind === PersistentVolumeClaimModel.kind) { - menuActions.push(ClonePVC); + return [SnapshotPVC, ClonePVC]; + } + if (resourceKind?.kind === VolumeSnapshotModel.kind) { + return [RestorePVC, DeleteSnapshot]; } - return menuActions; + return []; }; diff --git a/frontend/packages/ceph-storage-plugin/src/utils/restore-pvc-workflow.ts b/frontend/packages/ceph-storage-plugin/src/utils/restore-pvc-workflow.ts new file mode 100644 index 00000000000..1f841a204ad --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/utils/restore-pvc-workflow.ts @@ -0,0 +1,25 @@ +import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; + +import { KebabOption } from '@console/internal/components/utils'; + +export const RestorePVC = (kind: K8sKind, resource: K8sResourceKind): KebabOption => { + return { + label: 'Restore', + callback: () => { + const clusterObject = { resource }; + import( + '../components/modals/restore-pvc-modal/restore-pvc-modal' /* webpackChunkName: "ceph-storage-restore-pvc-modal" */ + ) + .then((m) => m.default(clusterObject)) + // eslint-disable-next-line no-console + .catch((e) => console.error(e)); + }, + accessReview: { + group: kind.apiGroup, + resource: kind.plural, + namespace: resource.metadata.namespace, + name: resource.metadata.name, + verb: 'create', + }, + }; +}; diff --git a/frontend/packages/ceph-storage-plugin/src/utils/snapshot-workflow.ts b/frontend/packages/ceph-storage-plugin/src/utils/snapshot-workflow.ts new file mode 100644 index 00000000000..089d1cbcba4 --- /dev/null +++ b/frontend/packages/ceph-storage-plugin/src/utils/snapshot-workflow.ts @@ -0,0 +1,29 @@ +import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s'; + +import { KebabOption } from '@console/internal/components/utils'; + +export const SnapshotPVC = (kind: K8sKind, resource: K8sResourceKind): KebabOption => { + return { + label: 'Create Snapshot', + callback: () => { + const clusterObject = { + name: resource.metadata.name, + namespace: resource.metadata.namespace, + kind: kind.kind, + }; + import( + '../components/modals/volume-snapshot-modal/volume-snapshot-modal' /* webpackChunkName: "ceph-storage-volume-snapshot-modal" */ + ) + .then((m) => m.volumeSnapshotModal(clusterObject)) + // eslint-disable-next-line no-console + .catch((e) => console.error(e)); + }, + accessReview: { + group: kind.apiGroup, + resource: kind.plural, + namespace: resource.metadata.namespace, + name: resource.metadata.name, + verb: 'create', + }, + }; +}; diff --git a/frontend/packages/console-plugin-sdk/src/registry.ts b/frontend/packages/console-plugin-sdk/src/registry.ts index 326629baed9..7648d48f9d0 100644 --- a/frontend/packages/console-plugin-sdk/src/registry.ts +++ b/frontend/packages/console-plugin-sdk/src/registry.ts @@ -1,36 +1,39 @@ import * as _ from 'lodash'; -import { FlagsObject } from '@console/internal/reducers/features'; + import { + ActivePlugin, Extension, ExtensionTypeGuard, - ActivePlugin, - isModelDefinition, - isFeatureFlag, - isResourceListPage, - isResourceDetailsPage, - isPerspective, - isYAMLTemplate, - isRoutePage, - isDashboardsOverviewHealthSubsystem, + isClusterServiceVersionAction, isDashboardsCard, - isDashboardsTab, - isDashboardsOverviewInventoryItem, isDashboardsInventoryItemGroup, + isDashboardsOverviewHealthSubsystem, + isDashboardsOverviewInventoryItem, + isDashboardsOverviewInventoryItemReplacement, + isDashboardsOverviewPrometheusActivity, + isDashboardsOverviewResourceActivity, isDashboardsOverviewUtilizationItem, - isOverviewResourceTab, - isOverviewCRD, - isOverviewTabSection, + isDashboardsTab, + isDevCatalogModel, + isFeatureFlag, isGlobalConfig, - isClusterServiceVersionAction, isKebabActions, - isDevCatalogModel, - isDashboardsOverviewResourceActivity, - isDashboardsOverviewPrometheusActivity, + isModelDefinition, + isOverviewCRD, + isOverviewResourceTab, + isOverviewTabSection, + isPerspective, isProjectDashboardInventoryItem, isReduxReducer, - isDashboardsOverviewInventoryItemReplacement, + isResourceDetailsPage, + isResourceListPage, + isResourceTabPage, + isRoutePage, + isYAMLTemplate, } from './typings'; +import { FlagsObject } from '@console/internal/reducers/features'; + // TODO(vojtech): legacy, remove /** @@ -84,6 +87,10 @@ export class ExtensionRegistry { return this.extensions.filter(isRoutePage); } + public getResourceTabPages() { + return this.extensions.filter(isResourceTabPage); + } + public getPerspectives() { return this.extensions.filter(isPerspective); } diff --git a/frontend/packages/console-plugin-sdk/src/typings/pages.ts b/frontend/packages/console-plugin-sdk/src/typings/pages.ts index e37dfeee448..678b917f052 100644 --- a/frontend/packages/console-plugin-sdk/src/typings/pages.ts +++ b/frontend/packages/console-plugin-sdk/src/typings/pages.ts @@ -1,5 +1,5 @@ -import { RouteProps, RouteComponentProps } from 'react-router-dom'; -import { K8sKind, K8sResourceKindReference } from '@console/internal/module/k8s'; +import { RouteComponentProps, RouteProps } from 'react-router-dom'; +import { K8sKind, K8sResourceKindReference, K8sResourceKind } from '@console/internal/module/k8s'; import { Extension, LazyLoader } from './base'; namespace ExtensionProperties { @@ -10,6 +10,23 @@ namespace ExtensionProperties { loader: LazyLoader; } + /** To add an additonal page to public components(ex: PVs, PVCs) via plugins */ + export type ResourceTabPage = ResourcePage<{ + /** See https://reacttraining.com/react-router/web/api/match */ + match: RouteComponentProps['match']; + /** The resource kind scope. */ + kind: K8sResourceKindReference; + /** The namespace scope. */ + namespace: string; + /** Name of the resource tab inside detailsPage */ + name: string; + }> & { + /** The href for the resource page */ + href: string; + /** Name of the resource tab inside detailsPage */ + name: string; + }; + export type ResourceListPage = ResourcePage<{ /** See https://reacttraining.com/react-router/web/api/match */ match: RouteComponentProps['match']; @@ -21,7 +38,10 @@ namespace ExtensionProperties { mock: boolean; /** The namespace scope. */ namespace: string; - }>; + }> & { + /** Some Resources require ReferenceFor instead of ReferenceForModel */ + modelParser?: (obj: K8sResourceKind) => string; + }; export type ResourceDetailsPage = ResourcePage<{ /** See https://reacttraining.com/react-router/web/api/match */ @@ -32,7 +52,10 @@ namespace ExtensionProperties { namespace: string; /** The page name. */ name: string; - }>; + }> & { + /** Some Resources require ReferenceFor instead of ReferenceForModel */ + modelParser?: (obj: K8sResourceKind) => string; + }; // Maps to react-router#https://reacttraining.com/react-router/web/api/Route // See https://reacttraining.com/react-router/web/api/Route @@ -60,6 +83,10 @@ export interface RoutePage extends Extension { type: 'Page/Route'; } +export interface ResourceTabPage extends Extension { + type: 'Page/Resource/Tab'; +} + export type ResourcePage = ResourceListPage | ResourceDetailsPage; export const isResourceListPage = (e: Extension): e is ResourceListPage => { @@ -70,6 +97,10 @@ export const isResourceDetailsPage = (e: Extension): e is ResourceDetailsPage => return e.type === 'Page/Resource/Details'; }; +export const isResourceTabPage = (e: Extension): e is ResourceTabPage => { + return e.type === 'Page/Resource/Tab'; +}; + export const isRoutePage = (e: Extension): e is RoutePage => { return e.type === 'Page/Route'; }; diff --git a/frontend/packages/kubevirt-plugin/src/components/vm-templates/__tests__/__snapshots__/vm-template-source.spec.tsx.snap b/frontend/packages/kubevirt-plugin/src/components/vm-templates/__tests__/__snapshots__/vm-template-source.spec.tsx.snap index 9e70dfbfcc0..b764921272e 100644 --- a/frontend/packages/kubevirt-plugin/src/components/vm-templates/__tests__/__snapshots__/vm-template-source.spec.tsx.snap +++ b/frontend/packages/kubevirt-plugin/src/components/vm-templates/__tests__/__snapshots__/vm-template-source.spec.tsx.snap @@ -158,7 +158,9 @@ ShallowWrapper { }, }, }, + Symbol(enzyme.__providerValues__): undefined, }, + Symbol(enzyme.__providerValues__): Map {}, } `; @@ -431,7 +433,9 @@ ShallowWrapper { }, }, }, + Symbol(enzyme.__providerValues__): undefined, }, + Symbol(enzyme.__providerValues__): Map {}, } `; @@ -595,7 +599,9 @@ ShallowWrapper { }, }, }, + Symbol(enzyme.__providerValues__): undefined, }, + Symbol(enzyme.__providerValues__): Map {}, } `; @@ -870,7 +876,9 @@ ShallowWrapper { }, }, }, + Symbol(enzyme.__providerValues__): undefined, }, + Symbol(enzyme.__providerValues__): Map {}, } `; @@ -1025,7 +1033,9 @@ ShallowWrapper { }, }, }, + Symbol(enzyme.__providerValues__): undefined, }, + Symbol(enzyme.__providerValues__): Map {}, } `; @@ -1253,6 +1263,8 @@ ShallowWrapper { }, }, }, + Symbol(enzyme.__providerValues__): undefined, }, + Symbol(enzyme.__providerValues__): Map {}, } `; diff --git a/frontend/public/components/factory/details.tsx b/frontend/public/components/factory/details.tsx index 41c76d32192..5d5744dfbf3 100644 --- a/frontend/public/components/factory/details.tsx +++ b/frontend/public/components/factory/details.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { match } from 'react-router-dom'; import * as _ from 'lodash-es'; import { getBadgeFromType } from '@console/shared'; +import { useExtensions, ResourceTabPage, isResourceTabPage } from '@console/plugin-sdk'; import { Firehose, HorizontalNav, @@ -9,15 +10,45 @@ import { FirehoseResource, KebabOptionsCreator, Page, + AsyncComponent, } from '../utils'; -import { K8sResourceKindReference, K8sResourceKind, K8sKind } from '../../module/k8s'; +import { + K8sResourceKindReference, + K8sResourceKind, + K8sKind, + referenceForModel, + referenceFor, +} from '../../module/k8s'; import { withFallback } from '../utils/error-boundary'; import { ErrorBoundaryFallback } from '../error'; import { breadcrumbsForDetailsPage } from '../utils/breadcrumbs'; -export const DetailsPage = withFallback((props) => { +export const DetailsPage = withFallback(({ pages = [], ...props }) => { const resourceKeys = _.map(props.resources, 'prop'); - + const resourcePageExtensions = useExtensions(isResourceTabPage); + let allPages = [ + ...pages, + ...resourcePageExtensions + .filter( + (p) => + referenceForModel(p.properties.model) === + (props.kindObj ? referenceFor(props.kindObj) : props.kind), + ) + .map((p) => ({ + href: p.properties.href, + name: p.properties.name, + component: () => ( + + ), + })), + ]; + allPages = allPages.length ? allPages : null; return ( ((props) => { {props.children} , kind: string, helpText?: any, namespace?: string, filterLabel?: string, textFilter?: string, title?: string, hideTextFilter?: boolean, showTitle?: boolean, rowFilters?: any[], selector?: any, fieldSelector?: string, canCreate?: boolean, createButtonText?: string, createProps?: any, mock?: boolean, badge?: React.ReactNode} >} */ +/** @type {React.SFC<{ListComponent: React.ComponentType, kind: string, helpText?: any, namespace?: string, filterLabel?: string, textFilter?: string, title?: string, showTitle?: boolean, rowFilters?: any[], selector?: any, fieldSelector?: string, canCreate?: boolean, createButtonText?: string, createProps?: any, mock?: boolean, badge?: React.ReactNode, createHandler?: any} >} */ export const ListPage = withFallback((props) => { const { autoFocus, diff --git a/frontend/public/components/resource-pages.ts b/frontend/public/components/resource-pages.ts index e78f6c54271..05b4b56f932 100644 --- a/frontend/public/components/resource-pages.ts +++ b/frontend/public/components/resource-pages.ts @@ -67,7 +67,9 @@ const addResourcePage = ( map: ImmutableMap, page: plugins.ResourcePage, ) => { - const key = referenceForModel(page.properties.model); + const key = page.properties?.modelParser + ? page.properties?.modelParser(page.properties.model) + : referenceForModel(page.properties.model); if (!map.has(key)) { map.set(key, page.properties.loader); } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 63e3f605d51..f6ba36ff96b 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1997,6 +1997,14 @@ resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.42.tgz#2b0cd7678d5fc273df7816a88b8b34016e3a5d85" integrity sha512-knVdi1Ul8xYgJ0wdhQ+/2YGJFKJFa/5srcPII9zvOs4KhsHfpnFrSTQXATYmjslglxRMif3Lg+wEZ0beag+94A== +"@types/enzyme@3.10.x": + version "3.10.4" + resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.10.4.tgz#dd4961042381a7c0f6637ce25fec3f773ce489dd" + integrity sha512-P5XpxcIt9KK8QUH4al4ttfJfIHg6xmN9ZjyUzRSzAsmDYwRXLI05ng/flZOPXrEXmp8ZYiN8/tEXYK5KSOQk3w== + dependencies: + "@types/cheerio" "*" + "@types/react" "*" + "@types/enzyme@3.x": version "3.1.9" resolved "https://registry.yarnpkg.com/@types/enzyme/-/enzyme-3.1.9.tgz#fbd97f3beb7cad76fc9c6f04c97d77f4834522ef" @@ -2632,13 +2640,13 @@ agent-base@4, agent-base@^4.3.0: string.prototype.padstart "^3.0.0" symbol.prototype.description "^1.0.0" -airbnb-prop-types@^2.13.2: - version "2.13.2" - resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.13.2.tgz#43147a5062dd2a4a5600e748a47b64004cc5f7fc" - integrity sha512-2FN6DlHr6JCSxPPi25EnqGaXC4OC3/B3k1lCd6MMYrZ51/Gf/1qDfaR+JElzWa+Tl7cY2aYOlsYJGFeQyVHIeQ== +airbnb-prop-types@^2.15.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/airbnb-prop-types/-/airbnb-prop-types-2.15.0.tgz#5287820043af1eb469f5b0af0d6f70da6c52aaef" + integrity sha512-jUh2/hfKsRjNFC4XONQrxo/n/3GG4Tn6Hl0WlFQN5PY9OMC9loSCoAYKnZsWaP8wEfd5xcrPloK0Zg6iS1xwVA== dependencies: - array.prototype.find "^2.0.4" - function.prototype.name "^1.1.0" + array.prototype.find "^2.1.0" + function.prototype.name "^1.1.1" has "^1.0.3" is-regex "^1.0.4" object-is "^1.0.1" @@ -2646,7 +2654,7 @@ airbnb-prop-types@^2.13.2: object.entries "^1.1.0" prop-types "^15.7.2" prop-types-exact "^1.2.0" - react-is "^16.8.6" + react-is "^16.9.0" ajv-errors@^1.0.0: version "1.0.1" @@ -2977,13 +2985,13 @@ array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" -array.prototype.find@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.0.4.tgz#556a5c5362c08648323ddaeb9de9d14bc1864c90" - integrity sha1-VWpcU2LAhkgyPdrrnenRS8GGTJA= +array.prototype.find@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.0.tgz#630f2eaf70a39e608ac3573e45cf8ccd0ede9ad7" + integrity sha512-Wn41+K1yuO5p7wRZDl7890c3xvv5UBrfVXTVIe28rSQb6LS0fZMDrQB6PAcxQFRFy6vJTLDc3A2+3CjQdzVKRg== dependencies: - define-properties "^1.1.2" - es-abstract "^1.7.0" + define-properties "^1.1.3" + es-abstract "^1.13.0" array.prototype.flat@^1.2.1: version "1.2.1" @@ -4674,11 +4682,12 @@ check-types@^8.0.3: integrity sha512-YpeKZngUmG65rLudJ4taU7VLkOCTMhNl/u4ctNC56LQS/zJTyNH0Lrtwm1tfTsbLlwvlfsA2d1c8vCf/Kh2KwQ== cheerio@^1.0.0-rc.2: - version "1.0.0-rc.2" - resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.2.tgz#4b9f53a81b27e4d5dac31c0ffd0cfa03cc6830db" + version "1.0.0-rc.3" + resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6" + integrity sha512-0td5ijfUPuubwLUu0OBoe98gZj8C/AA+RW3v67GPlGOrvxWjZmBXiBCRU+I8VEiNyJzjth40POfHiz2RB3gImA== dependencies: css-select "~1.2.0" - dom-serializer "~0.1.0" + dom-serializer "~0.1.1" entities "~1.1.1" htmlparser2 "^3.9.1" lodash "^4.15.0" @@ -6361,13 +6370,21 @@ dom-converter@~0.1: version "3.3.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.3.1.tgz#fc1a4e15ffdf60ddde03a480a9c0fece821dd4a6" -dom-serializer@0, dom-serializer@~0.1.0: +dom-serializer@0: version "0.1.0" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" dependencies: domelementtype "~1.1.1" entities "~1.1.1" +dom-serializer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" + integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + dependencies: + domelementtype "^1.3.0" + entities "^1.1.1" + dom-walk@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" @@ -6610,35 +6627,45 @@ entities@^1.1.2: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -enzyme-adapter-react-16@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.12.1.tgz#6a2d74c80559d35ac0a91ca162fa45f4186290cf" - integrity sha512-GB61gvY97XvrA6qljExGY+lgI6BBwz+ASLaRKct9VQ3ozu0EraqcNn3CcrUckSGIqFGa1+CxO5gj5is5t3lwrw== +enzyme-adapter-react-16@1.15.2: + version "1.15.2" + resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.2.tgz#b16db2f0ea424d58a808f9df86ab6212895a4501" + integrity sha512-SkvDrb8xU3lSxID8Qic9rB8pvevDbLybxPK6D/vW7PrT0s2Cl/zJYuXvsd1EBTz0q4o3iqG3FJhpYz3nUNpM2Q== dependencies: - enzyme-adapter-utils "^1.11.0" + enzyme-adapter-utils "^1.13.0" + enzyme-shallow-equal "^1.0.1" + has "^1.0.3" object.assign "^4.1.0" - object.values "^1.1.0" + object.values "^1.1.1" prop-types "^15.7.2" - react-is "^16.8.6" + react-is "^16.12.0" react-test-renderer "^16.0.0-0" - semver "^5.6.0" + semver "^5.7.0" -enzyme-adapter-utils@^1.11.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.12.0.tgz#96e3730d76b872f593e54ce1c51fa3a451422d93" - integrity sha512-wkZvE0VxcFx/8ZsBw0iAbk3gR1d9hK447ebnSYBf95+r32ezBq+XDSAvRErkc4LZosgH8J7et7H7/7CtUuQfBA== +enzyme-adapter-utils@^1.13.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/enzyme-adapter-utils/-/enzyme-adapter-utils-1.13.0.tgz#01c885dde2114b4690bf741f8dc94cee3060eb78" + integrity sha512-YuEtfQp76Lj5TG1NvtP2eGJnFKogk/zT70fyYHXK2j3v6CtuHqc8YmgH/vaiBfL8K1SgVVbQXtTcgQZFwzTVyQ== dependencies: - airbnb-prop-types "^2.13.2" - function.prototype.name "^1.1.0" + airbnb-prop-types "^2.15.0" + function.prototype.name "^1.1.2" object.assign "^4.1.0" - object.fromentries "^2.0.0" + object.fromentries "^2.0.2" prop-types "^15.7.2" - semver "^5.6.0" + semver "^5.7.1" -enzyme@^3.9.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.9.0.tgz#2b491f06ca966eb56b6510068c7894a7e0be3909" - integrity sha512-JqxI2BRFHbmiP7/UFqvsjxTirWoM1HfeaJrmVSZ9a1EADKkZgdPcAuISPMpoUiHlac9J4dYt81MC5BBIrbJGMg== +enzyme-shallow-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/enzyme-shallow-equal/-/enzyme-shallow-equal-1.0.1.tgz#7afe03db3801c9b76de8440694096412a8d9d49e" + integrity sha512-hGA3i1so8OrYOZSM9whlkNmVHOicJpsjgTzC+wn2JMJXhq1oO4kA4bJ5MsfzSIcC71aLDKzJ6gZpIxrqt3QTAQ== + dependencies: + has "^1.0.3" + object-is "^1.0.2" + +enzyme@3.10.x: + version "3.10.0" + resolved "https://registry.yarnpkg.com/enzyme/-/enzyme-3.10.0.tgz#7218e347c4a7746e133f8e964aada4a3523452f6" + integrity sha512-p2yy9Y7t/PFbPoTvrWde7JIYB2ZyGC+NgTNbVEGvZ5/EyoYSr9aG/2rSbVvyNvMHEhw9/dmGUJHWtfQIEiX9pg== dependencies: array.prototype.flat "^1.2.1" cheerio "^1.0.0-rc.2" @@ -6690,7 +6717,7 @@ error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.10.0, es-abstract@^1.11.0, es-abstract@^1.12.0: +es-abstract@^1.10.0, es-abstract@^1.12.0: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== @@ -6718,19 +6745,26 @@ es-abstract@^1.13.0, es-abstract@^1.14.2, es-abstract@^1.15.0, es-abstract@^1.16 string.prototype.trimleft "^2.1.0" string.prototype.trimright "^2.1.0" -es-abstract@^1.5.0, es-abstract@^1.5.1, es-abstract@^1.7.0: - version "1.10.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" +es-abstract@^1.17.0-next.1: + version "1.17.4" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.4.tgz#e3aedf19706b20e7c2594c35fc0d57605a79e184" + integrity sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ== dependencies: - es-to-primitive "^1.1.1" + es-to-primitive "^1.2.1" function-bind "^1.1.1" - has "^1.0.1" - is-callable "^1.1.3" - is-regex "^1.0.4" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.5" + is-regex "^1.0.5" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimleft "^2.1.1" + string.prototype.trimright "^2.1.1" -es-abstract@^1.6.1: - version "1.12.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165" +es-abstract@^1.5.1, es-abstract@^1.7.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864" dependencies: es-to-primitive "^1.1.1" function-bind "^1.1.1" @@ -6746,6 +6780,15 @@ es-to-primitive@^1.1.1, es-to-primitive@^1.2.0: is-date-object "^1.0.1" is-symbol "^1.0.2" +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + es5-ext@^0.10.35, es5-ext@^0.10.50: version "0.10.52" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.52.tgz#bb21777e919a04263736ded120a9d665f10ea63f" @@ -7988,7 +8031,7 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: mkdirp ">=0.5 0" rimraf "2" -function-bind@^1.0.2, function-bind@^1.1.0, function-bind@^1.1.1: +function-bind@^1.0.2, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -8000,10 +8043,24 @@ function.prototype.name@^1.1.0: function-bind "^1.1.1" is-callable "^1.1.3" +function.prototype.name@^1.1.1, function.prototype.name@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.2.tgz#5cdf79d7c05db401591dfde83e3b70c5123e9a45" + integrity sha512-C8A+LlHBJjB2AdcRPorc5JvJ5VUoWlXdEHLOJdCI7kjHEtGTpHQUiqMvCIKUwIsGwZX2jZJy761AXsn356bJQg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + functions-have-names "^1.2.0" + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" +functions-have-names@^1.2.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.1.tgz#a981ac397fa0c9964551402cdc5533d7a4d52f91" + integrity sha512-j48B/ZI7VKs3sgeI2cZp7WXWmZXu7Iq5pl5/vptV5N2mq+DGFuS/ulaDjtaoLpYzuD6u8UgrUKHfgo7fDTSiBA== + fuse.js@^3.4.4: version "3.4.5" resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.5.tgz#8954fb43f9729bd5dbcb8c08f251db552595a7a6" @@ -8550,6 +8607,11 @@ has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -8757,9 +8819,9 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" html-element-map@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.0.1.tgz#3c4fcb4874ebddfe4283b51c8994e7713782b592" - integrity sha512-BZSfdEm6n706/lBfXKWa4frZRZcT5k1cOusw95ijZsHlI+GdgY0v95h6IzO3iIDf2ROwq570YTwqNPqHcNMozw== + version "1.2.0" + resolved "https://registry.yarnpkg.com/html-element-map/-/html-element-map-1.2.0.tgz#dfbb09efe882806af63d990cf6db37993f099f22" + integrity sha512-0uXq8HsuG1v2TmQ8QkIhzbrqeskE4kn52Q18QJ9iAA/SnHoEKXWiUxHQtclRsCFWEUD2So34X+0+pZZu862nnw== dependencies: array-filter "^1.0.0" @@ -9277,8 +9339,9 @@ is-binary-path@^1.0.0: binary-extensions "^1.0.0" is-boolean-object@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93" + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.1.tgz#10edc0900dd127697a92f6f9807c7617d68ac48e" + integrity sha512-TqZuVwa/sppcrhUCAYkGBk7w0yxfQQnxq28fjkO53tnK9FQXmdwz2JS5+GjsWQ6RByES1K40nI+yDic5c9/aAQ== is-buffer@^1.0.2, is-buffer@^1.1.5: version "1.1.6" @@ -9295,6 +9358,11 @@ is-callable@^1.1.3, is-callable@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" +is-callable@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" + integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== + is-ci@^1.0.10: version "1.1.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.1.0.tgz#247e4162e7860cebbdaf30b774d6b0ac7dcfe7a5" @@ -9436,8 +9504,9 @@ is-hexadecimal@^1.0.0: integrity sha512-zxQ9//Q3D/34poZf8fiy3m3XVpbQc7ren15iKqrTtLPwkPD/t3Scy9Imp63FujULGxuK0ZlCwoo5xNpktFgbOA== is-number-object@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.3.tgz#f265ab89a9f445034ef6aff15a8f00b00f551799" + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" + integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== is-number@^2.1.0: version "2.1.0" @@ -9539,6 +9608,13 @@ is-regex@^1.0.4: dependencies: has "^1.0.1" +is-regex@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" + integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== + dependencies: + has "^1.0.3" + is-root@2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c" @@ -9556,8 +9632,9 @@ is-stream@^1.0.1, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" is-string@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.4.tgz#cc3a9b69857d621e963725a24caeec873b826e64" + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" + integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== is-subset@^0.1.1: version "0.1.1" @@ -11718,6 +11795,11 @@ object-inspect@^1.6.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.6.0.tgz#c70b6cbf72f274aab4c34c0c82f5167bf82cf15b" integrity sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ== +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + object-inspect@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-0.4.0.tgz#f5157c116c1455b243b06ee97703392c5ad89fec" @@ -11726,6 +11808,11 @@ object-is@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.1.tgz#0aa60ec9989a0b3ed795cf4d06f62cf1ad6539b6" +object-is@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/object-is/-/object-is-1.0.2.tgz#6b80eb84fe451498f65007982f035a5b445edec4" + integrity sha512-Epah+btZd5wrrfjkJZq1AOB9O6OxUQto45hzFd7lXGrpHPGE0W1k+426yrZV+k6NJOzLNNW/nVsmZdIWsAqoOQ== + object-keys@^1.0.11, object-keys@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2" @@ -11763,13 +11850,14 @@ object.assign@^4.1.0: object-keys "^1.0.11" object.entries@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.0.4.tgz#1bf9a4dd2288f5b33f3a993d257661f05d161a5f" + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.1.tgz#ee1cf04153de02bb093fec33683900f57ce5399b" + integrity sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ== dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" object.entries@^1.1.0: version "1.1.0" @@ -11781,16 +11869,6 @@ object.entries@^1.1.0: function-bind "^1.1.1" has "^1.0.3" -object.fromentries@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.0.tgz#49a543d92151f8277b3ac9600f1e930b189d30ab" - integrity sha512-9iLiI6H083uiqUuvzyY6qrlmc/Gz8hLQFOcb/Ri/0xXFkSNS3ctV+CbE6yM2+AnkYfOB3dGjdzC0wrMLIhQICA== - dependencies: - define-properties "^1.1.2" - es-abstract "^1.11.0" - function-bind "^1.1.1" - has "^1.0.1" - "object.fromentries@^2.0.0 || ^1.0.0", object.fromentries@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.1.tgz#050f077855c7af8ae6649f45c80b16ee2d31e704" @@ -11801,6 +11879,16 @@ object.fromentries@^2.0.0: function-bind "^1.1.1" has "^1.0.3" +object.fromentries@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" + integrity sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" + object.getownpropertydescriptors@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" @@ -11821,14 +11909,15 @@ object.pick@^1.3.0: dependencies: isobject "^3.0.1" -object.values@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.0.4.tgz#e524da09b4f66ff05df457546ec72ac99f13069a" +object.values@^1.0.4, object.values@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.1.tgz#68a99ecde356b7e9295a3c5e0ce31dc8c953de5e" + integrity sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA== dependencies: - define-properties "^1.1.2" - es-abstract "^1.6.1" - function-bind "^1.1.0" - has "^1.0.1" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has "^1.0.3" object.values@^1.1.0: version "1.1.0" @@ -13241,12 +13330,19 @@ quote-stream@~0.0.0: minimist "0.0.8" through2 "~0.4.1" -raf@^3.1.0, raf@^3.4.0: +raf@^3.1.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" dependencies: performance-now "^2.1.0" +raf@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + railroad-diagrams@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz#eb7e6267548ddedfb899c1b90e57374559cddb7e" @@ -13604,6 +13700,11 @@ react-inspector@^3.0.2: is-dom "^1.0.9" prop-types "^15.6.1" +react-is@^16.12.0, react-is@^16.9.0: + version "16.12.0" + resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c" + integrity sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q== + react-is@^16.3.2: version "16.5.2" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.5.2.tgz#e2a7b7c3f5d48062eb769fcb123505eb928722e3" @@ -14805,6 +14906,11 @@ semver@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" +semver@^5.7.0, semver@^5.7.1: + version "5.7.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" + integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== + semver@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-6.0.0.tgz#05e359ee571e5ad7ed641a6eec1e547ba52dea65" @@ -15455,12 +15561,13 @@ string.prototype.startswith@^0.2.0: resolved "https://registry.yarnpkg.com/string.prototype.startswith/-/string.prototype.startswith-0.2.0.tgz#da68982e353a4e9ac4a43b450a2045d1c445ae7b" string.prototype.trim@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz#d04de2c89e137f4d7d206f086b5ed2fae6be8cea" + version "1.2.1" + resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz#141233dff32c82bfad80684d7e5f0869ee0fb782" + integrity sha512-MjGFEeqixw47dAMFMtgUro/I0+wNqZB5GKXGt1fFr24u3TzDXCPu7J9Buppzoe3r/LqkSDLDDJzE15RGWDGAVw== dependencies: - define-properties "^1.1.2" - es-abstract "^1.5.0" - function-bind "^1.0.2" + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" string.prototype.trimleft@^2.1.0: version "2.1.0" @@ -15470,6 +15577,14 @@ string.prototype.trimleft@^2.1.0: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimleft@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" + integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string.prototype.trimright@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" @@ -15478,6 +15593,14 @@ string.prototype.trimright@^2.1.0: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimright@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" + integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string_decoder@^1.0.0, string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"