From ebddb518c5fb2b708db65f925ce2c17a685e87e3 Mon Sep 17 00:00:00 2001 From: Samuel Padgett Date: Sat, 28 Sep 2019 09:45:15 -0400 Subject: [PATCH] Enable prettier on frontend/ --- frontend/.eslintrc | 18 +- frontend/.vscode/settings.json | 25 +- frontend/__mocks__/catalogItemsMocks.ts | 2461 ++++++++--------- frontend/__mocks__/clusterVersinMock.js | 15 +- frontend/__mocks__/k8sResourcesMocks.ts | 18 +- frontend/__mocks__/localStorage.ts | 2 +- frontend/__tests__/actions/dashboards.spec.ts | 46 +- frontend/__tests__/actions/k8s.spec.ts | 80 +- frontend/__tests__/actions/ui.spec.ts | 30 +- .../__tests__/components/catalog.spec.tsx | 45 +- .../components/cluster-settings.spec.tsx | 195 +- .../basicauth-idp-form.spec.tsx | 18 +- .../cluster-settings/github-idp-form.spec.tsx | 21 +- .../cluster-settings/gitlab-idp-form.spec.tsx | 7 +- .../cluster-settings/google-idp-form.spec.tsx | 7 +- .../htpasswd-idp-form.spec.tsx | 2 +- .../keystone-idp-form.spec.tsx | 2 +- .../cluster-settings/ldap-idp-form.spec.tsx | 35 +- .../cluster-settings/openid-idp-form.spec.tsx | 35 +- .../request-header-idp-form.spec.tsx | 42 +- .../__tests__/components/create-yaml.spec.tsx | 18 +- .../__tests__/components/environment.spec.tsx | 121 +- .../components/factory/details.spec.tsx | 32 +- .../components/factory/list-page.spec.tsx | 90 +- .../__tests__/components/graphs/area.spec.tsx | 9 +- .../__tests__/components/graphs/bar.spec.tsx | 5 +- .../components/graphs/gauge.spec.tsx | 11 +- .../components/graphs/helpers.spec.ts | 53 +- .../graphs/prometheus-graph.spec.tsx | 29 +- .../__tests__/components/graphs/utils.spec.ts | 16 +- .../__tests__/components/limitrange.spec.tsx | 18 +- .../configure-update-strategy-modal.spec.tsx | 48 +- .../__tests__/components/namespace.spec.tsx | 37 +- frontend/__tests__/components/pod.spec.tsx | 60 +- .../components/resource-pages.spec.tsx | 7 +- .../components/resource-quota.spec.tsx | 8 +- .../__tests__/components/route-pages.spec.tsx | 179 +- .../components/safety-first.spec.tsx | 63 +- .../components/storage-class-form.spec.tsx | 25 +- .../__tests__/components/utils/async.spec.tsx | 135 +- .../utils/copy-to-clipboard.spec.tsx | 15 +- .../components/utils/datetime.spec.ts | 101 +- .../components/utils/download-button.spec.tsx | 23 +- .../components/utils/error-boundary.spec.tsx | 30 +- .../components/utils/firehose.spec.tsx | 77 +- .../utils/name-value-editor.spec.tsx | 30 +- .../components/utils/page-heading.spec.tsx | 34 +- .../utils/promise-component.spec.tsx | 58 +- frontend/__tests__/container.spec.ts | 56 +- .../__tests__/module/k8s/k8s-models.spec.ts | 38 +- frontend/__tests__/module/k8s/pods.spec.ts | 57 +- frontend/__tests__/probe.spec.ts | 41 +- frontend/__tests__/public/co-fetch.spec.js | 10 +- .../__tests__/reducers/dashboards.spec.ts | 41 +- frontend/__tests__/reducers/features.spec.tsx | 41 +- frontend/__tests__/reducers/ui.spec.ts | 30 +- .../__tests__/selector-requirement.spec.js | 87 +- frontend/__tests__/selector.spec.js | 135 +- frontend/__tests__/units.spec.js | 57 +- frontend/integration-tests/protractor.conf.ts | 124 +- .../integration-tests/tests/base.scenario.ts | 16 +- .../tests/cluster-settings.scenario.ts | 23 +- .../tests/crd-extensions.scenario.ts | 207 +- .../integration-tests/tests/crud.scenario.ts | 230 +- .../tests/deploy-image.scenario.ts | 11 +- .../devconsole/dev-perspective.scenario.ts | 13 +- .../devconsole/git-import-flow.scenario.ts | 10 +- .../tests/developer-catalog.scenario.ts | 38 +- .../tests/environment.scenario.ts | 54 +- .../tests/filter.scenario.ts | 55 +- .../integration-tests/tests/login.scenario.ts | 36 +- .../tests/modal-annotations.scenario.ts | 60 +- .../tests/monitoring.scenario.ts | 82 +- .../tests/overview/overview.scenario.ts | 19 +- .../tests/performance.scenario.ts | 89 +- .../tests/secrets.scenario.ts | 359 ++- .../service-binding.scenario.ts | 13 +- .../service-broker.scenario.ts | 11 +- .../service-catalog.scenario.ts | 14 +- .../service-catalog/service-class.scenario.ts | 21 +- .../tests/storage.scenario.ts | 24 +- .../views/catalog-page.view.ts | 13 +- .../integration-tests/views/catalog.view.ts | 13 +- .../views/cluster-settings.view.ts | 10 +- .../views/create-role-binding.view.ts | 10 +- frontend/integration-tests/views/crud.view.ts | 90 +- .../devconsole-view/dev-perspective.view.ts | 9 +- .../views/environment.view.ts | 6 +- .../views/horizontal-nav.view.ts | 9 +- .../integration-tests/views/login.view.ts | 6 +- .../views/modal-annotations.view.ts | 26 +- .../views/monitoring.view.ts | 2 +- .../integration-tests/views/search.view.ts | 4 +- .../integration-tests/views/secrets.view.ts | 52 +- .../views/service-catalog.view.ts | 15 +- .../integration-tests/views/sidenav.view.ts | 19 +- .../integration-tests/views/storage.view.ts | 6 +- frontend/integration-tests/views/yaml.view.ts | 10 +- frontend/plugin-stats.js | 6 +- frontend/public/actions/dashboards.ts | 58 +- frontend/public/actions/features.ts | 211 +- frontend/public/actions/k8s.ts | 125 +- frontend/public/actions/monitoring.ts | 5 +- frontend/public/actions/ui.ts | 118 +- frontend/public/co-fetch.js | 45 +- frontend/public/components/RBAC/bindings.jsx | 672 +++-- frontend/public/components/RBAC/edit-rule.jsx | 371 ++- frontend/public/components/RBAC/role.jsx | 301 +- frontend/public/components/RBAC/rules.jsx | 174 +- frontend/public/components/about-modal.tsx | 67 +- frontend/public/components/alert-manager.tsx | 201 +- frontend/public/components/api-explorer.tsx | 530 ++-- frontend/public/components/app-contents.tsx | 672 ++++- frontend/public/components/app.jsx | 68 +- .../public/components/broker-management.tsx | 32 +- frontend/public/components/build-config.tsx | 138 +- frontend/public/components/build-logs.jsx | 39 +- frontend/public/components/build-pipeline.tsx | 142 +- frontend/public/components/build.tsx | 326 ++- .../catalog/catalog-item-details.jsx | 136 +- .../components/catalog/catalog-item-icon.tsx | 65 +- .../components/catalog/catalog-items.jsx | 109 +- .../components/catalog/catalog-page.tsx | 321 ++- frontend/public/components/chargeback.tsx | 494 ++-- frontend/public/components/checkbox.tsx | 2 +- .../components/cluster-service-broker.tsx | 185 +- .../components/cluster-service-class-info.tsx | 56 +- .../components/cluster-service-class.tsx | 150 +- .../components/cluster-service-plan.tsx | 144 +- .../cluster-settings/basicauth-idp-form.tsx | 164 +- .../cluster-settings/cluster-operator.tsx | 146 +- .../cluster-settings/cluster-settings.tsx | 492 ++-- .../cluster-settings/cluster-version.tsx | 19 +- .../cluster-settings/github-idp-form.tsx | 208 +- .../cluster-settings/gitlab-idp-form.tsx | 159 +- .../cluster-settings/global-config.tsx | 129 +- .../cluster-settings/google-idp-form.tsx | 136 +- .../cluster-settings/htpasswd-idp-form.tsx | 106 +- .../cluster-settings/idp-cafile-input.tsx | 16 +- .../cluster-settings/idp-name-input.tsx | 12 +- .../components/cluster-settings/index.ts | 12 +- .../cluster-settings/keystone-idp-form.tsx | 188 +- .../cluster-settings/ldap-idp-form.tsx | 244 +- .../components/cluster-settings/oauth.tsx | 76 +- .../cluster-settings/openid-idp-form.tsx | 245 +- .../request-header-idp-form.tsx | 196 +- .../public/components/command-line-tools.tsx | 159 +- frontend/public/components/conditions.tsx | 72 +- .../components/configmap-and-secret-data.tsx | 92 +- frontend/public/components/configmap.jsx | 109 +- .../public/components/console-notifier.tsx | 79 +- .../public/components/container-selector.tsx | 26 +- frontend/public/components/container.tsx | 438 +-- frontend/public/components/create-yaml.tsx | 60 +- frontend/public/components/cron-job.jsx | 155 +- .../components/custom-resource-definition.tsx | 156 +- frontend/public/components/daemon-set.jsx | 158 +- .../dashboard/capacity-card/capacity-body.tsx | 2 +- .../dashboard/capacity-card/capacity-item.tsx | 79 +- .../dashboard/dashboard-card/card-body.tsx | 12 +- .../dashboard/dashboard-card/card-header.tsx | 10 +- .../dashboard/dashboard-card/card-link.tsx | 24 +- .../dashboard/dashboard-card/card-title.tsx | 12 +- .../dashboard/dashboard-card/card.tsx | 12 +- .../dashboard/details-card/detail-item.tsx | 2 +- .../dashboard/details-card/details-body.tsx | 6 +- .../dashboard/events-card/event-item.tsx | 6 +- .../dashboard/events-card/events-body.tsx | 25 +- frontend/public/components/dashboard/grid.tsx | 54 +- .../dashboard/health-card/alert-item.tsx | 4 +- .../dashboard/health-card/health-body.tsx | 6 +- .../dashboard/health-card/health-item.tsx | 10 +- .../dashboard/health-card/utils.tsx | 5 +- .../inventory-card/inventory-body.tsx | 4 +- .../inventory-card/inventory-item.tsx | 153 +- .../dashboard/inventory-card/utils.ts | 20 +- .../top-consumers-card/consumers-body.tsx | 4 +- .../top-consumers-card/consumers-filter.tsx | 22 +- .../utilization-card/utilization-body.tsx | 20 +- .../utilization-card/utilization-item.tsx | 55 +- .../components/dashboards-page/dashboards.tsx | 104 +- .../overview-dashboard/capacity-card.tsx | 83 +- .../overview-dashboard/details-card.tsx | 177 +- .../overview-dashboard/events-card.tsx | 16 +- .../overview-dashboard/health-card.tsx | 318 ++- .../overview-dashboard/inventory-card.tsx | 56 +- .../overview-dashboard/overview-dashboard.tsx | 6 +- .../overview-dashboard/queries.ts | 35 +- .../overview-dashboard/top-consumers-card.tsx | 292 +- .../overview-dashboard/utilization-card.tsx | 68 +- .../overview-dashboard/utils.ts | 5 +- .../with-dashboard-resources.tsx | 70 +- .../public/components/default-resource.jsx | 96 +- .../public/components/deployment-config.tsx | 265 +- frontend/public/components/deployment.tsx | 244 +- .../public/components/droppable-edit-yaml.tsx | 126 +- frontend/public/components/edit-yaml.jsx | 324 ++- frontend/public/components/environment.jsx | 391 ++- frontend/public/components/error.tsx | 89 +- frontend/public/components/events.jsx | 323 ++- .../public/components/factory/details.tsx | 38 +- .../public/components/factory/list-page.jsx | 379 +-- frontend/public/components/factory/list.tsx | 376 ++- frontend/public/components/factory/modal.tsx | 100 +- .../components/factory/table-filters.ts | 35 +- frontend/public/components/factory/table.tsx | 315 ++- .../components/global-notifications.jsx | 10 +- frontend/public/components/graphs/area.tsx | 22 +- frontend/public/components/graphs/bar.tsx | 74 +- frontend/public/components/graphs/gauge.tsx | 56 +- .../public/components/graphs/graph-empty.tsx | 21 +- frontend/public/components/graphs/health.jsx | 95 +- frontend/public/components/graphs/helpers.ts | 28 +- frontend/public/components/graphs/index.tsx | 36 +- .../components/graphs/prometheus-graph.tsx | 25 +- .../components/graphs/prometheus-poll-hook.ts | 6 +- .../components/graphs/require-prometheus.tsx | 10 +- frontend/public/components/graphs/status.jsx | 57 +- frontend/public/components/graphs/themes.ts | 4 +- frontend/public/components/graphs/utils.ts | 8 +- frontend/public/components/hpa.tsx | 316 ++- .../public/components/image-stream-tag.tsx | 231 +- .../components/image-stream-timeline.tsx | 116 +- frontend/public/components/image-stream.tsx | 340 ++- .../components/impersonate-notifier.jsx | 26 +- frontend/public/components/import-yaml.tsx | 19 +- frontend/public/components/ingress.jsx | 204 +- .../components/instantiate-template.tsx | 291 +- frontend/public/components/job.jsx | 177 +- .../public/components/kube-admin-notifier.jsx | 12 +- frontend/public/components/limit-range.tsx | 196 +- .../public/components/machine-autoscaler.tsx | 124 +- .../public/components/machine-config-pool.tsx | 321 ++- frontend/public/components/machine-config.tsx | 104 +- .../public/components/machine-deployment.tsx | 199 +- frontend/public/components/machine-set.tsx | 287 +- frontend/public/components/machine.tsx | 145 +- frontend/public/components/markdown-view.tsx | 68 +- .../public/components/masthead-toolbar.jsx | 267 +- frontend/public/components/masthead.jsx | 2 +- .../modals/add-secret-to-workload.tsx | 216 +- .../components/modals/alert-routing-modal.tsx | 197 +- .../modals/cluster-channel-modal.tsx | 77 +- .../modals/cluster-update-modal.tsx | 96 +- .../modals/configure-count-modal.tsx | 85 +- .../configure-machine-autoscaler-modal.tsx | 87 +- .../modals/configure-ns-pull-secret-modal.jsx | 302 +- .../modals/configure-unschedulable-modal.jsx | 26 +- .../configure-update-strategy-modal.tsx | 269 +- .../components/modals/confirm-modal.jsx | 24 +- .../modals/create-namespace-modal.jsx | 296 +- .../public/components/modals/delete-modal.jsx | 69 +- .../modals/delete-namespace-modal.jsx | 50 +- .../public/components/modals/error-modal.jsx | 10 +- .../components/modals/expand-pvc-modal.jsx | 59 +- frontend/public/components/modals/index.ts | 172 +- .../public/components/modals/labels-modal.jsx | 94 +- .../components/modals/remove-volume-modal.tsx | 93 +- frontend/public/components/modals/tags.tsx | 58 +- .../public/components/modals/taints-modal.tsx | 162 +- .../components/modals/tolerations-modal.tsx | 239 +- frontend/public/components/monitoring.tsx | 1779 +++++++----- .../monitoring/alert-manager-config.tsx | 98 +- .../monitoring/alert-manager-yaml-editor.tsx | 125 +- .../public/components/monitoring/metrics.tsx | 715 +++-- .../components/monitoring/query-browser.tsx | 552 ++-- frontend/public/components/namespace.jsx | 519 ++-- frontend/public/components/nav/admin-nav.tsx | 128 +- frontend/public/components/nav/index.tsx | 2 +- frontend/public/components/nav/items.tsx | 47 +- frontend/public/components/nav/nav-header.tsx | 23 +- .../public/components/nav/perspective-nav.tsx | 6 +- frontend/public/components/nav/section.tsx | 64 +- frontend/public/components/network-policy.jsx | 222 +- frontend/public/components/node.tsx | 505 ++-- .../operator-hub-community-provider-modal.tsx | 72 +- .../components/overview/build-overview.tsx | 149 +- .../overview/daemon-set-overview.tsx | 16 +- .../overview/deployment-config-overview.tsx | 72 +- .../overview/deployment-overview.tsx | 70 +- frontend/public/components/overview/index.tsx | 667 +++-- .../overview/namespace-overview.tsx | 210 +- .../overview/networking-overview.tsx | 96 +- .../components/overview/pod-overview.tsx | 36 +- .../components/overview/pods-overview.tsx | 40 +- .../components/overview/project-overview.tsx | 368 ++- .../overview/resource-overview-details.tsx | 48 +- .../overview/resource-overview-page.tsx | 19 +- .../overview/resource-overview-pages.tsx | 38 +- .../overview/stateful-set-overview.tsx | 10 +- .../components/persistent-volume-claim.jsx | 221 +- .../public/components/persistent-volume.jsx | 126 +- frontend/public/components/pod-exec.jsx | 333 +-- frontend/public/components/pod-logs.jsx | 68 +- frontend/public/components/pod.tsx | 471 ++-- frontend/public/components/prometheus.jsx | 71 +- .../components/provisioned-services.tsx | 32 +- frontend/public/components/radio.tsx | 57 +- frontend/public/components/replicaset.jsx | 167 +- .../components/replication-controller.jsx | 233 +- .../public/components/resource-dropdown.tsx | 122 +- frontend/public/components/resource-list.tsx | 76 +- frontend/public/components/resource-pages.ts | 529 +++- frontend/public/components/resource-quota.jsx | 456 +-- frontend/public/components/routes.tsx | 469 ++-- .../public/components/routes/create-route.tsx | 535 ++-- frontend/public/components/row-filter.jsx | 82 +- frontend/public/components/safety-first.tsx | 2 +- frontend/public/components/search.jsx | 76 +- frontend/public/components/secret.jsx | 139 +- .../components/secrets/create-secret.tsx | 1225 ++++---- .../public/components/service-account.jsx | 202 +- .../public/components/service-binding.tsx | 217 +- .../components/service-catalog-parameters.tsx | 70 +- .../service-catalog/create-binding.tsx | 223 +- .../service-catalog/create-instance.tsx | 253 +- .../service-catalog/schema-form.tsx | 144 +- .../public/components/service-instance.tsx | 338 ++- .../public/components/service-monitor.jsx | 88 +- frontend/public/components/service.jsx | 274 +- .../sidebars/build-config-sidebar.jsx | 27 +- .../sidebars/explore-type-sidebar.tsx | 100 +- .../sidebars/network-policy-sidebar.jsx | 40 +- .../sidebars/resource-quota-sidebar.jsx | 25 +- .../components/sidebars/resource-sidebar.jsx | 172 +- .../components/sidebars/role-sidebar.jsx | 37 +- frontend/public/components/start-guide.tsx | 68 +- frontend/public/components/stateful-set.jsx | 96 +- .../public/components/storage-class-form.tsx | 257 +- frontend/public/components/storage-class.tsx | 134 +- .../components/storage/attach-storage.tsx | 156 +- .../public/components/storage/create-pvc.tsx | 121 +- .../public/components/template-instance.tsx | 105 +- frontend/public/components/terminal.jsx | 32 +- frontend/public/components/utils/alerts.tsx | 38 +- frontend/public/components/utils/async.tsx | 53 +- .../public/components/utils/breadcrumbs.ts | 9 +- .../public/components/utils/build-hooks.tsx | 24 +- .../components/utils/build-strategy.tsx | 118 +- .../public/components/utils/button-bar.jsx | 44 +- .../components/utils/camel-case-wrap.tsx | 15 +- .../public/components/utils/close-button.jsx | 5 +- .../public/components/utils/cloud-provider.js | 4 +- .../components/utils/container-table.tsx | 46 +- .../components/utils/copy-to-clipboard.tsx | 35 +- frontend/public/components/utils/datetime.ts | 37 +- .../public/components/utils/details-page.tsx | 159 +- frontend/public/components/utils/disabled.tsx | 4 +- .../public/components/utils/documentation.tsx | 3 +- .../components/utils/download-button.tsx | 46 +- .../components/utils/drag-drop-context.tsx | 2 +- frontend/public/components/utils/dropdown.jsx | 448 ++- .../public/components/utils/entitlements.ts | 2 +- .../components/utils/error-boundary.tsx | 32 +- .../public/components/utils/event-stream.tsx | 108 +- .../components/utils/field-level-help.tsx | 2 +- .../public/components/utils/file-input.tsx | 198 +- frontend/public/components/utils/firehose.jsx | 181 +- frontend/public/components/utils/headings.tsx | 219 +- .../components/utils/horizontal-nav.tsx | 203 +- frontend/public/components/utils/index.tsx | 10 +- frontend/public/components/utils/inject.js | 6 +- frontend/public/components/utils/kebab.tsx | 211 +- .../public/components/utils/label-list.tsx | 18 +- .../public/components/utils/line-buffer.ts | 7 +- frontend/public/components/utils/link.tsx | 23 +- .../public/components/utils/list-dropdown.jsx | 117 +- .../public/components/utils/list-input.tsx | 34 +- .../public/components/utils/log-window.jsx | 65 +- .../components/utils/name-value-editor.jsx | 782 ++++-- .../components/utils/number-spinner.tsx | 18 +- .../components/utils/owner-references.tsx | 14 +- frontend/public/components/utils/poll-hook.ts | 3 +- .../components/utils/promise-component.tsx | 24 +- frontend/public/components/utils/rbac.tsx | 121 +- .../components/utils/request-size-input.tsx | 8 +- .../public/components/utils/resource-icon.tsx | 21 +- .../public/components/utils/resource-link.tsx | 75 +- .../public/components/utils/resource-log.jsx | 277 +- frontend/public/components/utils/router.ts | 22 +- .../components/utils/safe-fetch-hook.ts | 2 +- .../utils/scroll-to-top-on-mount.tsx | 3 +- .../components/utils/selector-input.jsx | 57 +- frontend/public/components/utils/selector.tsx | 30 +- .../utils/service-catalog-status.tsx | 4 +- .../components/utils/simple-tab-nav.tsx | 54 +- .../components/utils/skeleton-catalog.tsx | 14 +- .../public/components/utils/status-box.tsx | 170 +- .../utils/storage-class-dropdown.tsx | 161 +- .../components/utils/tile-view-page.jsx | 355 ++- .../public/components/utils/timestamp.tsx | 51 +- .../public/components/utils/toggle-play.jsx | 12 +- .../components/utils/truncate-middle.ts | 1 - frontend/public/components/utils/units.js | 89 +- .../components/utils/value-from-pair.jsx | 236 +- .../public/components/utils/volume-type.tsx | 23 +- frontend/public/components/utils/webhooks.tsx | 134 +- .../components/utils/workload-pause.tsx | 42 +- frontend/public/components/volumes-table.tsx | 155 +- frontend/public/components/workload-table.tsx | 58 +- frontend/public/const.ts | 3 +- frontend/public/declarations.d.ts | 2 +- frontend/public/kinds.ts | 63 +- frontend/public/load-test.sw.js | 67 +- frontend/public/models/index.ts | 8 +- frontend/public/models/yaml-templates.ts | 377 ++- frontend/public/module/auth.js | 20 +- frontend/public/module/k8s/builds.ts | 10 +- .../public/module/k8s/cluster-operator.ts | 2 +- .../public/module/k8s/cluster-settings.ts | 63 +- frontend/public/module/k8s/command.js | 35 +- frontend/public/module/k8s/container.ts | 13 +- frontend/public/module/k8s/get-resources.ts | 194 +- frontend/public/module/k8s/index.ts | 88 +- frontend/public/module/k8s/job.js | 10 +- frontend/public/module/k8s/k8s-models.ts | 38 +- frontend/public/module/k8s/k8s.ts | 49 +- frontend/public/module/k8s/label-selector.js | 83 +- frontend/public/module/k8s/node.ts | 18 +- .../module/k8s/openapi-to-json-schema.ts | 24 +- frontend/public/module/k8s/pods.ts | 86 +- frontend/public/module/k8s/probe.ts | 7 +- frontend/public/module/k8s/resource.js | 82 +- .../public/module/k8s/selector-requirement.js | 34 +- frontend/public/module/k8s/selector.ts | 14 +- frontend/public/module/k8s/service-catalog.ts | 13 +- frontend/public/module/k8s/swagger.ts | 27 +- frontend/public/module/status.js | 4 +- frontend/public/module/ws-factory.js | 12 +- frontend/public/plugins.ts | 9 +- frontend/public/propTypes.ts | 6 +- frontend/public/reducers/dashboards.ts | 30 +- frontend/public/reducers/features.ts | 55 +- frontend/public/reducers/k8s.ts | 129 +- frontend/public/reducers/monitoring.ts | 43 +- frontend/public/reducers/ui.ts | 102 +- frontend/public/redux.ts | 2 +- frontend/webpack.config.ts | 2 +- 438 files changed, 31901 insertions(+), 19139 deletions(-) diff --git a/frontend/.eslintrc b/frontend/.eslintrc index 66375290636..3ced1e1d4f4 100644 --- a/frontend/.eslintrc +++ b/frontend/.eslintrc @@ -1,4 +1,5 @@ { + "root": true, "env": { "browser": true, "es6": true, @@ -9,6 +10,7 @@ "plugin:import/errors", "plugin:import/warnings", "plugin:react/recommended", + "plugin:console/prettier", ], "parser": "@typescript-eslint/parser", "parserOptions": { @@ -26,24 +28,17 @@ "@typescript-eslint", ], "rules": { - "brace-style": 2, "camelcase": 2, - "comma-dangle": ["error", "always-multiline"], "consistent-return": 0, "consistent-this": [1, "that"], "curly": [2, "all"], "default-case": [2], "dot-notation": [2], - "eol-last": [2, "unix"], "no-multiple-empty-lines": [2, {"max": 2, "maxEOF": 0}], "eqeqeq": [2, "allow-null"], "guard-for-in": 2, "import/no-unresolved": ["error"], "import/no-duplicates": ["error"], - "indent": [2, 2, {"SwitchCase": 1, "VariableDeclarator": 2}], - "jsx-quotes": "error", - "keyword-spacing": "error", - "linebreak-style": [2, "unix"], "max-nested-callbacks": [1, 4], "no-alert": 2, "no-caller": 2, @@ -53,9 +48,7 @@ "no-else-return": ["error"], "no-global-strict": 0, "no-irregular-whitespace": ["error"], - "no-multi-spaces": [2], "no-shadow": ["error"], - "no-trailing-spaces": ["error"], "no-underscore-dangle": 0, "@typescript-eslint/no-unused-vars": ["error", {"varsIgnorePattern": "React", "args": "after-used"}], "@typescript-eslint/no-use-before-define": 2, @@ -63,21 +56,14 @@ "object-shorthand": ["error", "properties"], "prefer-const": ["error", {"destructuring": "all"}], "prefer-template": 2, - "quotes": [2, "single"], "radix": 2, - "react/jsx-curly-spacing": 2, - "react/jsx-equals-spacing": 2, "react/jsx-no-duplicate-props": 2, "react/no-string-refs": 1, "react/jsx-uses-react": "error", "react/jsx-uses-vars": "error", - "react/jsx-tag-spacing": 2, "react/no-unknown-property": "error", "react/prop-types": 0, "react/self-closing-comp": ["error", {"component": true, "html": false}], - "semi": 2, - "space-before-function-paren": ["error", "never"], - "wrap-iife": [2, "inside"], "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "react/display-name": 0, diff --git a/frontend/.vscode/settings.json b/frontend/.vscode/settings.json index ec2df4b69d9..f5750749d47 100644 --- a/frontend/.vscode/settings.json +++ b/frontend/.vscode/settings.json @@ -1,17 +1,16 @@ { - // TODO uncomment once we enable new eslint & prettier support across the entire project - // "[javascript]": { - // "editor.formatOnSave": true - // }, - // "[javascriptreact]": { - // "editor.formatOnSave": true - // }, - // "[typescript]": { - // "editor.formatOnSave": true - // }, - // "[typescriptreact]": { - // "editor.formatOnSave": true - // }, + "[javascript]": { + "editor.formatOnSave": true + }, + "[javascriptreact]": { + "editor.formatOnSave": true + }, + "[typescript]": { + "editor.formatOnSave": true + }, + "[typescriptreact]": { + "editor.formatOnSave": true + }, "eslint.validate": [ "javascript", diff --git a/frontend/__mocks__/catalogItemsMocks.ts b/frontend/__mocks__/catalogItemsMocks.ts index a2a487d1f30..56db5a20680 100644 --- a/frontend/__mocks__/catalogItemsMocks.ts +++ b/frontend/__mocks__/catalogItemsMocks.ts @@ -1,880 +1,872 @@ export const catalogListPageProps = { - 'namespace': 'default', - 'clusterServiceClasses': { - 'data': [ + namespace: 'default', + clusterServiceClasses: { + data: [ { - 'metadata': { - 'name': 'c02503f2-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe5f470-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'database', - 'mongodb', - ], - 'externalMetadata': { + metadata: { + name: 'c02503f2-c641-11e8-be32-54e1ad486c15', + uid: 'cbe5f470-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['database', 'mongodb'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-mongodb', - 'displayName': 'MongoDB', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'MongoDB', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'MongoDB database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/mongodb-container/blob/master/3.2/README.md.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', + description: + 'MongoDB database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/mongodb-container/blob/master/3.2/README.md.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c02180ca-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe6d3b1-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'instant-app', - 'jenkins', - ], - 'externalMetadata': { + metadata: { + name: 'c02180ca-c641-11e8-be32-54e1ad486c15', + uid: 'cbe6d3b1-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['instant-app', 'jenkins'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-jenkins', - 'displayName': 'Jenkins', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'Jenkins', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'Jenkins service, with persistent storage.\n\nNOTE: You must have persistent volumes available in your cluster to use this template.', + description: + 'Jenkins service, with persistent storage.\n\nNOTE: You must have persistent volumes available in your cluster to use this template.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c02a3a94-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe665be-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'quickstart', - 'php', - 'cakephp', - ], - 'externalMetadata': { + metadata: { + name: 'c02a3a94-c641-11e8-be32-54e1ad486c15', + uid: 'cbe665be-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['quickstart', 'php', 'cakephp'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-php', - 'displayName': 'CakePHP + MySQL', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'CakePHP + MySQL', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'An example CakePHP application with a MySQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/cakephp-ex/blob/master/README.md.', + description: + 'An example CakePHP application with a MySQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/cakephp-ex/blob/master/README.md.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c01f3bb7-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe7e8da-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'quickstart', - 'ruby', - 'rails', - ], - 'externalMetadata': { + metadata: { + name: 'c01f3bb7-c641-11e8-be32-54e1ad486c15', + uid: 'cbe7e8da-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['quickstart', 'ruby', 'rails'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-ruby', - 'displayName': 'Rails + PostgreSQL', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'Rails + PostgreSQL', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'An example Rails application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/rails-ex/blob/master/README.md.', + description: + 'An example Rails application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/rails-ex/blob/master/README.md.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c01a67e0-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe46c67-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'quickstart', - 'python', - 'django', - ], - 'externalMetadata': { + metadata: { + name: 'c01a67e0-c641-11e8-be32-54e1ad486c15', + uid: 'cbe46c67-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['quickstart', 'python', 'django'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-python', - 'displayName': 'Django + PostgreSQL', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'Django + PostgreSQL', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'An example Django application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/django-ex/blob/master/README.md.', + description: + 'An example Django application with a PostgreSQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/django-ex/blob/master/README.md.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c017c1f2-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe73a61-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'database', - 'postgresql', - ], - 'externalMetadata': { + metadata: { + name: 'c017c1f2-c641-11e8-be32-54e1ad486c15', + uid: 'cbe73a61-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['database', 'postgresql'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-postgresql', - 'displayName': 'PostgreSQL', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'PostgreSQL', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'PostgreSQL database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', + description: + 'PostgreSQL database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c0265596-c641-11e8-be32-54e1ad486c15', - 'uid': 'cc17770c-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'database', - 'mariadb', - ], - 'externalMetadata': { + metadata: { + name: 'c0265596-c641-11e8-be32-54e1ad486c15', + uid: 'cc17770c-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['database', 'mariadb'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-mariadb', - 'displayName': 'MariaDB', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'MariaDB', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'MariaDB database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/mariadb-container/blob/master/10.2/root/usr/share/container-scripts/mysql/README.md.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', + description: + 'MariaDB database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/mariadb-container/blob/master/10.2/root/usr/share/container-scripts/mysql/README.md.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c0280380-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe3d07d-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'database', - 'mysql', - ], - 'externalMetadata': { + metadata: { + name: 'c0280380-c641-11e8-be32-54e1ad486c15', + uid: 'cbe3d07d-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['database', 'mysql'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-mysql-database', - 'displayName': 'MySQL', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'MySQL', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'MySQL database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/mysql-container/blob/master/5.7/root/usr/share/container-scripts/mysql/README.md.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', + description: + 'MySQL database service, with persistent storage. For more information about using this template, including OpenShift considerations, see https://github.com/sclorg/mysql-container/blob/master/5.7/root/usr/share/container-scripts/mysql/README.md.\n\nNOTE: Scaling to more than one replica is not supported. You must have persistent volumes available in your cluster to use this template.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c023a1d8-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe554a0-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'instant-app', - 'jenkins', - ], - 'externalMetadata': { + metadata: { + name: 'c023a1d8-c641-11e8-be32-54e1ad486c15', + uid: 'cbe554a0-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['instant-app', 'jenkins'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-jenkins', - 'displayName': 'Pipeline Build Example', + displayName: 'Pipeline Build Example', }, - 'description': 'This example showcases the new Jenkins Pipeline integration in OpenShift,\nwhich performs continuous integration and deployment right on the platform.\nThe template contains a Jenkinsfile - a definition of a multi-stage CI/CD process - that\nleverages the underlying OpenShift platform for dynamic and scalable\nbuilds. OpenShift integrates the status of your pipeline builds into the web\nconsole allowing you to see your entire application lifecycle in a single view.', + description: + 'This example showcases the new Jenkins Pipeline integration in OpenShift,\nwhich performs continuous integration and deployment right on the platform.\nThe template contains a Jenkinsfile - a definition of a multi-stage CI/CD process - that\nleverages the underlying OpenShift platform for dynamic and scalable\nbuilds. OpenShift integrates the status of your pipeline builds into the web\nconsole allowing you to see your entire application lifecycle in a single view.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c02bc18e-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbf8eaeb-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'quickstart', - 'perl', - 'dancer', - ], - 'externalMetadata': { + metadata: { + name: 'c02bc18e-c641-11e8-be32-54e1ad486c15', + uid: 'cbf8eaeb-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['quickstart', 'perl', 'dancer'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-perl', - 'displayName': 'Dancer + MySQL', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'Dancer + MySQL', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'An example Dancer application with a MySQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/dancer-ex/blob/master/README.md.', + description: + 'An example Dancer application with a MySQL database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/dancer-ex/blob/master/README.md.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, { - 'metadata': { - 'name': 'c01d0061-c641-11e8-be32-54e1ad486c15', - 'uid': 'cbe4e0fa-c641-11e8-8889-0242ac110004', - }, - 'spec': { - 'tags': [ - 'quickstart', - 'nodejs', - ], - 'externalMetadata': { + metadata: { + name: 'c01d0061-c641-11e8-be32-54e1ad486c15', + uid: 'cbe4e0fa-c641-11e8-8889-0242ac110004', + }, + spec: { + tags: ['quickstart', 'nodejs'], + externalMetadata: { 'console.openshift.io/iconClass': 'icon-nodejs', - 'displayName': 'Node.js + MongoDB', - 'providerDisplayName': 'Red Hat, Inc.', + displayName: 'Node.js + MongoDB', + providerDisplayName: 'Red Hat, Inc.', }, - 'description': 'An example Node.js application with a MongoDB database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/nodejs-ex/blob/master/README.md.', + description: + 'An example Node.js application with a MongoDB database. For more information about using this template, including OpenShift considerations, see https://github.com/openshift/nodejs-ex/blob/master/README.md.', }, - 'status': { - 'removedFromBrokerCatalog': false, + status: { + removedFromBrokerCatalog: false, }, }, ], - 'filters': {}, - 'loadError': '', - 'loaded': true, - 'selected': null, + filters: {}, + loadError: '', + loaded: true, + selected: null, }, - 'templateMetadata': [ + templateMetadata: [ { - 'apiVersion': 'meta.k8s.io/v1beta1', - 'kind': 'PartialObjectMetadata', - 'metadata': { - 'name': 'amq63-basic', - 'namespace': 'openshift', - 'selfLink': '/apis/template.openshift.io/v1/namespaces/openshift/templates/amq63-basic', - 'uid': 'effe623a-682c-11e9-be91-0a580a82000e', - 'resourceVersion': '9593', - 'creationTimestamp': '2019-04-26T14:09:42Z', - 'labels': { + apiVersion: 'meta.k8s.io/v1beta1', + kind: 'PartialObjectMetadata', + metadata: { + name: 'amq63-basic', + namespace: 'openshift', + selfLink: '/apis/template.openshift.io/v1/namespaces/openshift/templates/amq63-basic', + uid: 'effe623a-682c-11e9-be91-0a580a82000e', + resourceVersion: '9593', + creationTimestamp: '2019-04-26T14:09:42Z', + labels: { 'samples.operator.openshift.io/managed': 'true', }, - 'annotations': { - 'description': 'Application template for JBoss A-MQ brokers. These can be deployed as standalone or in a mesh. This template doesn\'t feature SSL support.', - 'iconClass': 'icon-amq', + annotations: { + description: + "Application template for JBoss A-MQ brokers. These can be deployed as standalone or in a mesh. This template doesn't feature SSL support.", + iconClass: 'icon-amq', 'openshift.io/display-name': 'Red Hat JBoss A-MQ 6.3 (Ephemeral, no SSL)', 'openshift.io/provider-display-name': 'Red Hat, Inc.', 'samples.operator.openshift.io/version': '4.1.0-0.ci-2019-04-26-103919', - 'tags': 'messaging,amq,jboss', - 'version': '1.4.12', + tags: 'messaging,amq,jboss', + version: '1.4.12', }, }, }, { - 'apiVersion': 'meta.k8s.io/v1beta1', - 'kind': 'PartialObjectMetadata', - 'metadata': { - 'name': 'amq63-persistent', - 'namespace': 'openshift', - 'selfLink': '/apis/template.openshift.io/v1/namespaces/openshift/templates/amq63-persistent', - 'uid': 'f2469368-682c-11e9-994a-0a580a80000a', - 'resourceVersion': '10083', - 'creationTimestamp': '2019-04-26T14:09:46Z', - 'labels': { + apiVersion: 'meta.k8s.io/v1beta1', + kind: 'PartialObjectMetadata', + metadata: { + name: 'amq63-persistent', + namespace: 'openshift', + selfLink: '/apis/template.openshift.io/v1/namespaces/openshift/templates/amq63-persistent', + uid: 'f2469368-682c-11e9-994a-0a580a80000a', + resourceVersion: '10083', + creationTimestamp: '2019-04-26T14:09:46Z', + labels: { 'samples.operator.openshift.io/managed': 'true', }, - 'annotations': { - 'description': 'An example JBoss A-MQ application. For more information about using this template, see https://github.com/jboss-openshift/application-templates.', - 'iconClass': 'icon-amq', + annotations: { + description: + 'An example JBoss A-MQ application. For more information about using this template, see https://github.com/jboss-openshift/application-templates.', + iconClass: 'icon-amq', 'openshift.io/display-name': 'JBoss A-MQ 6.3 (no SSL)', 'openshift.io/provider-display-name': 'Red Hat, Inc.', 'samples.operator.openshift.io/version': '4.1.0-0.ci-2019-04-26-103919', - 'tags': 'messaging,amq,jboss', - 'template.openshift.io/documentation-url': 'https://access.redhat.com/documentation/en/red-hat-jboss-amq/', - 'template.openshift.io/long-description': 'This template defines resources needed to develop Red Hat JBoss A-MQ 6.3 based application, including a deployment configuration and using persistence.', + tags: 'messaging,amq,jboss', + 'template.openshift.io/documentation-url': + 'https://access.redhat.com/documentation/en/red-hat-jboss-amq/', + 'template.openshift.io/long-description': + 'This template defines resources needed to develop Red Hat JBoss A-MQ 6.3 based application, including a deployment configuration and using persistence.', 'template.openshift.io/support-url': 'https://access.redhat.com', - 'version': '1.4.12', + version: '1.4.12', }, }, }, ], - 'imageStreams': { - 'data': [ - { // first imagestream is not a builder - 'spec': { - 'tags': [ - { - 'name': '10.2', - 'annotations': { - 'tags': 'database,mariadb', + imageStreams: { + data: [ + { + // first imagestream is not a builder + spec: { + tags: [ + { + name: '10.2', + annotations: { + tags: 'database,mariadb', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '10.2', + tag: '10.2', }, ], }, }, { - 'metadata': { - 'name': 'perl', - 'uid': 'c00bec39-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'perl', + uid: 'c00bec39-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'Perl', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:38Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '5.24', - 'annotations': { - 'description': 'Build and run Perl 5.24 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-perl-container/blob/master/5.24/README.md.', - 'iconClass': 'icon-perl', + name: '5.24', + annotations: { + description: + 'Build and run Perl 5.24 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-perl-container/blob/master/5.24/README.md.', + iconClass: 'icon-perl', 'openshift.io/display-name': 'Perl 5.24', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,perl', + tags: 'builder,perl', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '5.24', + tag: '5.24', }, ], }, }, { - 'metadata': { - 'name': 'php', - 'uid': 'c00c829d-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'php', + uid: 'c00c829d-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'PHP', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:38Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '7.1', - 'annotations': { - 'description': 'Build and run PHP 7.1 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-php-container/blob/master/7.1/README.md.', - 'iconClass': 'icon-php', + name: '7.1', + annotations: { + description: + 'Build and run PHP 7.1 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-php-container/blob/master/7.1/README.md.', + iconClass: 'icon-php', 'openshift.io/display-name': 'PHP 7.1', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,php', + tags: 'builder,php', }, }, ], }, - 'status': { - 'dockerImageRepository': '172.30.1.1:5000/openshift/php', - 'tags': [ + status: { + dockerImageRepository: '172.30.1.1:5000/openshift/php', + tags: [ { - 'tag': '7.1', + tag: '7.1', }, ], }, }, { - 'metadata': { - 'name': 'nginx', - 'uid': 'c00e6500-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'nginx', + uid: 'c00e6500-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'Nginx HTTP server and a reverse proxy (nginx)', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:37Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '1.12', - 'annotations': { - 'description': 'Build and serve static content via Nginx HTTP Server and a reverse proxy (nginx) on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/nginx-container/blob/master/1.12/README.md.', - 'iconClass': 'icon-nginx', + name: '1.12', + annotations: { + description: + 'Build and serve static content via Nginx HTTP Server and a reverse proxy (nginx) on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/nginx-container/blob/master/1.12/README.md.', + iconClass: 'icon-nginx', 'openshift.io/display-name': 'Nginx HTTP server and a reverse proxy 1.12', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,nginx', + tags: 'builder,nginx', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '1.12', + tag: '1.12', }, ], }, }, { - 'metadata': { - 'name': 'redis', - 'uid': 'c0119342-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'redis', + uid: 'c0119342-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'Redis', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:38Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '3.2', - 'annotations': { - 'description': 'Provides a Redis 3.2 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/3.2/README.md.', - 'iconClass': 'icon-redis', + name: '3.2', + annotations: { + description: + 'Provides a Redis 3.2 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/redis-container/tree/master/3.2/README.md.', + iconClass: 'icon-redis', 'openshift.io/display-name': 'Redis 3.2', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'redis', + tags: 'redis', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '3.2', + tag: '3.2', }, ], }, }, { - 'metadata': { - 'name': 'postgresql', - 'uid': 'c00f890f-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'postgresql', + uid: 'c00f890f-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'PostgreSQL', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:38Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '9.6', - 'annotations': { - 'description': 'Provides a PostgreSQL 9.6 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/tree/master/9.6/README.md.', - 'iconClass': 'icon-postgresql', + name: '9.6', + annotations: { + description: + 'Provides a PostgreSQL 9.6 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/postgresql-container/tree/master/9.6/README.md.', + iconClass: 'icon-postgresql', 'openshift.io/display-name': 'PostgreSQL 9.6', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'database,postgresql', + tags: 'database,postgresql', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '9.6', + tag: '9.6', }, ], }, }, { - 'metadata': { - 'name': 'httpd', - 'uid': 'c00aa68e-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'httpd', + uid: 'c00aa68e-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'Apache HTTP Server (httpd)', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:36Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '2.4', - 'annotations': { - 'description': 'Build and serve static content via Apache HTTP Server (httpd) 2.4 on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/httpd-container/blob/master/2.4/README.md.', - 'iconClass': 'icon-apache', + name: '2.4', + annotations: { + description: + 'Build and serve static content via Apache HTTP Server (httpd) 2.4 on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/httpd-container/blob/master/2.4/README.md.', + iconClass: 'icon-apache', 'openshift.io/display-name': 'Apache HTTP Server 2.4', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,httpd', + tags: 'builder,httpd', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '2.4', + tag: '2.4', }, ], }, }, { - 'metadata': { - 'name': 'dotnet', - 'uid': 'c01320a0-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'dotnet', + uid: 'c01320a0-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': '.NET Core Builder Images', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:36Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '2.0', - 'annotations': { + name: '2.0', + annotations: { 'openshift.io/display-name': '.NET Core 2.0', - 'tags': 'builder,.net,dotnet,dotnetcore,rh-dotnet20', - 'description': 'Build and run .NET Core 2.0 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/redhat-developer/s2i-dotnetcore/tree/master/2.0/build/README.md.', - 'iconClass': 'icon-dotnet', + tags: 'builder,.net,dotnet,dotnetcore,rh-dotnet20', + description: + 'Build and run .NET Core 2.0 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/redhat-developer/s2i-dotnetcore/tree/master/2.0/build/README.md.', + iconClass: 'icon-dotnet', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '2.0', + tag: '2.0', }, ], }, }, { - 'metadata': { - 'name': 'ruby', - 'uid': 'c00b0711-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'ruby', + uid: 'c00b0711-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'Ruby', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:40Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '2.4', - 'annotations': { - 'description': 'Build and run Ruby 2.4 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.4/README.md.', - 'iconClass': 'icon-ruby', + name: '2.4', + annotations: { + description: + 'Build and run Ruby 2.4 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-ruby-container/blob/master/2.4/README.md.', + iconClass: 'icon-ruby', 'openshift.io/display-name': 'Ruby 2.4', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,ruby', + tags: 'builder,ruby', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '2.4', + tag: '2.4', }, ], }, }, { - 'metadata': { - 'name': 'mysql', - 'uid': 'c00e1bc4-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'mysql', + uid: 'c00e1bc4-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'MySQL', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:37Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '5.7', - 'annotations': { - 'description': 'Provides a MySQL 5.7 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/mysql-container/tree/master/5.7/README.md.', - 'iconClass': 'icon-mysql-database', + name: '5.7', + annotations: { + description: + 'Provides a MySQL 5.7 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/mysql-container/tree/master/5.7/README.md.', + iconClass: 'icon-mysql-database', 'openshift.io/display-name': 'MySQL 5.7', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'mysql', + tags: 'mysql', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '5.7', + tag: '5.7', }, ], }, }, { - 'metadata': { - 'name': 'python', - 'uid': 'c00d0c6b-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'python', + uid: 'c00d0c6b-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'Python', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:39Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '3.6', - 'annotations': { - 'description': 'Build and run Python 3.6 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.', - 'iconClass': 'icon-python', + name: '3.6', + annotations: { + description: + 'Build and run Python 3.6 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-python-container/blob/master/3.6/README.md.', + iconClass: 'icon-python', 'openshift.io/display-name': 'Python 3.6', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,python', + tags: 'builder,python', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '3.6', + tag: '3.6', }, ], }, }, { - 'metadata': { - 'name': 'dotnet-runtime', - 'uid': 'c013e3d7-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'dotnet-runtime', + uid: 'c013e3d7-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': '.NET Core Runtime Images', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:36Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '2.0', - 'annotations': { - 'description': 'Run .NET Core applications on CentOS 7. For more information about using this image, including OpenShift considerations, see https://github.com/redhat-developer/s2i-dotnetcore/tree/master/2.0/runtime/README.md.', - 'iconClass': 'icon-dotnet', + name: '2.0', + annotations: { + description: + 'Run .NET Core applications on CentOS 7. For more information about using this image, including OpenShift considerations, see https://github.com/redhat-developer/s2i-dotnetcore/tree/master/2.0/runtime/README.md.', + iconClass: 'icon-dotnet', 'openshift.io/display-name': '.NET Core 2.0 Runtime', - 'tags': 'runtime,.net-runtime,dotnet-runtime,dotnetcore-runtime', + tags: 'runtime,.net-runtime,dotnet-runtime,dotnetcore-runtime', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '2.0', + tag: '2.0', }, ], }, }, { - 'metadata': { - 'name': 'wildfly', - 'uid': 'c00d954a-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'wildfly', + uid: 'c00d954a-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'WildFly', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:39Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '10.1', - 'annotations': { - 'description': 'Build and run WildFly 10.1 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/openshift-s2i/s2i-wildfly/blob/master/README.md.', - 'iconClass': 'icon-wildfly', + name: '10.1', + annotations: { + description: + 'Build and run WildFly 10.1 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/openshift-s2i/s2i-wildfly/blob/master/README.md.', + iconClass: 'icon-wildfly', 'openshift.io/display-name': 'WildFly 10.1', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,wildfly,java', + tags: 'builder,wildfly,java', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '10.1', + tag: '10.1', }, ], }, }, { - 'metadata': { - 'name': 'mongodb', - 'uid': 'c00fd59d-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'mongodb', + uid: 'c00fd59d-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'MongoDB', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:37Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '3.4', - 'annotations': { - 'description': 'Provides a MongoDB 3.4 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/mongodb-container/tree/master/3.4/README.md.', - 'iconClass': 'icon-mongodb', + name: '3.4', + annotations: { + description: + 'Provides a MongoDB 3.4 database on CentOS 7. For more information about using this database image, including OpenShift considerations, see https://github.com/sclorg/mongodb-container/tree/master/3.4/README.md.', + iconClass: 'icon-mongodb', 'openshift.io/display-name': 'MongoDB 3.4', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'database,mongodb', + tags: 'database,mongodb', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '3.4', + tag: '3.4', }, ], }, }, { - 'metadata': { - 'name': 'nodejs', - 'uid': 'c00b594d-c641-11e8-be32-54e1ad486c15', - 'annotations': { + metadata: { + name: 'nodejs', + uid: 'c00b594d-c641-11e8-be32-54e1ad486c15', + annotations: { 'openshift.io/display-name': 'Node.js', 'openshift.io/image.dockerRepositoryCheck': '2018-10-02T12:50:38Z', }, }, - 'spec': { - 'tags': [ + spec: { + tags: [ { - 'name': '8', - 'annotations': { - 'description': 'Build and run Node.js 8 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-nodejs-container/blob/master/8/README.md.', - 'iconClass': 'icon-nodejs', + name: '8', + annotations: { + description: + 'Build and run Node.js 8 applications on CentOS 7. For more information about using this builder image, including OpenShift considerations, see https://github.com/sclorg/s2i-nodejs-container/blob/master/8/README.md.', + iconClass: 'icon-nodejs', 'openshift.io/display-name': 'Node.js 8', 'openshift.io/provider-display-name': 'Red Hat, Inc.', - 'tags': 'builder,nodejs', + tags: 'builder,nodejs', }, }, ], }, - 'status': { - 'tags': [ + status: { + tags: [ { - 'tag': '8', + tag: '8', }, ], }, }, ], - 'filters': {}, - 'loadError': '', - 'loaded': true, - 'selected': null, + filters: {}, + loadError: '', + loaded: true, + selected: null, }, - 'clusterServiceVersions': { - 'data': [ + clusterServiceVersions: { + data: [ { - 'apiVersion': 'operators.coreos.com/v1alpha1', - 'kind': 'ClusterServiceVersion', - 'metadata': { - 'annotations': { + apiVersion: 'operators.coreos.com/v1alpha1', + kind: 'ClusterServiceVersion', + metadata: { + annotations: { 'olm.operatorGroup': 'olm-operators', 'olm.operatorNamespace': 'openshift-operator-lifecycle-manager', 'olm.targetNamespaces': 'openshift-operator-lifecycle-manager', }, - 'selfLink': '/apis/operators.coreos.com/v1alpha1/namespaces/openshift-operator-lifecycle-manager/clusterserviceversions/packageserver.v0.8.0', - 'resourceVersion': '11112', - 'name': 'packageserver.v0.8.0', - 'uid': '63ba72dc-1404-11e9-99d5-027d9941c4da', - 'creationTimestamp': '2019-01-09T11:47:49Z', - 'generation': 1, - 'namespace': 'openshift-operator-lifecycle-manager', - 'labels': { + selfLink: + '/apis/operators.coreos.com/v1alpha1/namespaces/openshift-operator-lifecycle-manager/clusterserviceversions/packageserver.v0.8.0', + resourceVersion: '11112', + name: 'packageserver.v0.8.0', + uid: '63ba72dc-1404-11e9-99d5-027d9941c4da', + creationTimestamp: '2019-01-09T11:47:49Z', + generation: 1, + namespace: 'openshift-operator-lifecycle-manager', + labels: { 'alm-catalog': 'olm-operators', }, }, - 'spec': { - 'customresourcedefinitions': {}, - 'apiservicedefinitions': { - 'owned': [ + spec: { + customresourcedefinitions: {}, + apiservicedefinitions: { + owned: [ { - 'containerPort': 5443, - 'deploymentName': 'packageserver', - 'description': 'A PackageManifest is a resource generated from existing CatalogSources and their ConfigMaps', - 'displayName': 'PackageManifest', - 'group': 'packages.apps.redhat.com', - 'kind': 'PackageManifest', - 'name': '', - 'version': 'v1alpha1', + containerPort: 5443, + deploymentName: 'packageserver', + description: + 'A PackageManifest is a resource generated from existing CatalogSources and their ConfigMaps', + displayName: 'PackageManifest', + group: 'packages.apps.redhat.com', + kind: 'PackageManifest', + name: '', + version: 'v1alpha1', }, ], }, - 'keywords': [ - 'packagemanifests', - 'olm', - 'packages', - ], - 'displayName': 'Package Server', - 'provider': { - 'name': 'Red Hat', + keywords: ['packagemanifests', 'olm', 'packages'], + displayName: 'Package Server', + provider: { + name: 'Red Hat', }, - 'maturity': 'alpha', - 'installModes': [ + maturity: 'alpha', + installModes: [ { - 'supported': true, - 'type': 'OwnNamespace', + supported: true, + type: 'OwnNamespace', }, { - 'supported': true, - 'type': 'SingleNamespace', + supported: true, + type: 'SingleNamespace', }, { - 'supported': true, - 'type': 'MultiNamespace', + supported: true, + type: 'MultiNamespace', }, { - 'supported': true, - 'type': 'AllNamespaces', + supported: true, + type: 'AllNamespaces', }, ], - 'version': '0.8.0', - 'links': [ + version: '0.8.0', + links: [ { - 'name': 'Package Server', - 'url': 'https://github.com/operator-framework/operator-lifecycle-manager/tree/master/pkg/packageserver', + name: 'Package Server', + url: + 'https://github.com/operator-framework/operator-lifecycle-manager/tree/master/pkg/packageserver', }, ], - 'install': { - 'spec': { - 'clusterPermissions': [ + install: { + spec: { + clusterPermissions: [ { - 'rules': [ + rules: [ { - 'apiGroups': [''], - 'resources': ['configmaps'], - 'verbs': ['get', 'list', 'watch'], + apiGroups: [''], + resources: ['configmaps'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['operators.coreos.com'], - 'resources': ['catalogsources'], - 'verbs': ['get', 'list', 'watch'], + apiGroups: ['operators.coreos.com'], + resources: ['catalogsources'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['packages.apps.redhat.com'], - 'resources': ['packagemanifests'], - 'verbs': ['get', 'list', 'watch', 'create', 'delete', 'patch', 'update'], + apiGroups: ['packages.apps.redhat.com'], + resources: ['packagemanifests'], + verbs: ['get', 'list', 'watch', 'create', 'delete', 'patch', 'update'], }, ], - 'serviceAccountName': 'packageserver', + serviceAccountName: 'packageserver', }, ], - 'deployments': [ + deployments: [ { - 'name': 'packageserver', - 'spec': { - 'replicas': 1, - 'selector': { - 'matchLabels': { - 'app': 'packageserver', + name: 'packageserver', + spec: { + replicas: 1, + selector: { + matchLabels: { + app: 'packageserver', }, }, - 'strategy': { - 'type': 'RollingUpdate', + strategy: { + type: 'RollingUpdate', }, - 'template': { - 'metadata': { - 'labels': { - 'app': 'packageserver', + template: { + metadata: { + labels: { + app: 'packageserver', }, }, - 'spec': { - 'containers': [ + spec: { + containers: [ { - 'command': [ + command: [ '/bin/package-server', '-v=4', '--secure-port', @@ -882,544 +874,498 @@ export const catalogListPageProps = { '--global-namespace', 'openshift-operator-lifecycle-manager', ], - 'image': 'registry.svc.ci.openshift.org/openshift/origin-v4.0-2019-01-09-071417@sha256:907be1f98330efb06ff91054d359e3d0bf46d41e811493ea05540ff64666d6a1', - 'imagePullPolicy': 'Always', - 'livenessProbe': { - 'httpGet': { - 'path': '/healthz', - 'port': 5443, - 'scheme': 'HTTPS', + image: + 'registry.svc.ci.openshift.org/openshift/origin-v4.0-2019-01-09-071417@sha256:907be1f98330efb06ff91054d359e3d0bf46d41e811493ea05540ff64666d6a1', + imagePullPolicy: 'Always', + livenessProbe: { + httpGet: { + path: '/healthz', + port: 5443, + scheme: 'HTTPS', }, }, - 'name': 'packageserver', - 'ports': [ + name: 'packageserver', + ports: [ { - 'containerPort': 5443, + containerPort: 5443, }, ], - 'readinessProbe': { - 'httpGet': { - 'path': '/healthz', - 'port': 5443, - 'scheme': 'HTTPS', + readinessProbe: { + httpGet: { + path: '/healthz', + port: 5443, + scheme: 'HTTPS', }, }, }, ], - 'serviceAccountName': 'packageserver', + serviceAccountName: 'packageserver', }, }, }, }, ], }, - 'strategy': 'deployment', + strategy: 'deployment', }, - 'maintainers': [ + maintainers: [ { - 'email': 'openshift-operators@redhat.com', - 'name': 'Red Hat', + email: 'openshift-operators@redhat.com', + name: 'Red Hat', }, ], - 'description': 'Represents an Operator package that is available from a given CatalogSource which will resolve to a ClusterServiceVersion.', + description: + 'Represents an Operator package that is available from a given CatalogSource which will resolve to a ClusterServiceVersion.', }, - 'status': { - 'reason': 'InstallSucceeded', - 'message': 'install strategy completed with no errors', - 'lastUpdateTime': '2019-01-09T11:51:16Z', - 'requirementStatus': [ + status: { + reason: 'InstallSucceeded', + message: 'install strategy completed with no errors', + lastUpdateTime: '2019-01-09T11:51:16Z', + requirementStatus: [ { - 'group': 'apiregistration.k8s.io', - 'kind': 'APIService', - 'message': '', - 'name': 'v1alpha1.packages.apps.redhat.com', - 'status': 'DeploymentFound', - 'version': 'v1', + group: 'apiregistration.k8s.io', + kind: 'APIService', + message: '', + name: 'v1alpha1.packages.apps.redhat.com', + status: 'DeploymentFound', + version: 'v1', }, { - 'dependents': [ + dependents: [ { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'\'],\'resources\': [\'configmaps\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': [''],'resources': ['configmaps']}", + status: 'Satisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'operators.coreos.com\'],\'resources\': [\'catalogsources\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': ['operators.coreos.com'],'resources': ['catalogsources']}", + status: 'Satisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\',\'create\',\'delete\',\'patch\',\'update\'],\'apiGroups\': [\'packages.apps.redhat.com\'],\'resources\': [\'packagemanifests\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch','create','delete','patch','update'],'apiGroups': ['packages.apps.redhat.com'],'resources': ['packagemanifests']}", + status: 'Satisfied', + version: 'v1beta1', }, ], - 'group': '', - 'kind': 'ServiceAccount', - 'message': '', - 'name': 'packageserver', - 'status': 'Present', - 'version': 'v1', + group: '', + kind: 'ServiceAccount', + message: '', + name: 'packageserver', + status: 'Present', + version: 'v1', }, ], - 'certsLastUpdated': '2019-01-09T11:47:53Z', - 'lastTransitionTime': '2019-01-09T11:51:16Z', - 'conditions': [ + certsLastUpdated: '2019-01-09T11:47:53Z', + lastTransitionTime: '2019-01-09T11:51:16Z', + conditions: [ { - 'lastTransitionTime': '2019-01-09T11:47:49Z', - 'lastUpdateTime': '2019-01-09T11:47:49Z', - 'message': 'requirements not yet checked', - 'phase': 'Pending', - 'reason': 'RequirementsUnknown', + lastTransitionTime: '2019-01-09T11:47:49Z', + lastUpdateTime: '2019-01-09T11:47:49Z', + message: 'requirements not yet checked', + phase: 'Pending', + reason: 'RequirementsUnknown', }, { - 'lastTransitionTime': '2019-01-09T11:47:49Z', - 'lastUpdateTime': '2019-01-09T11:47:49Z', - 'message': 'one or more requirements couldn\'t be found', - 'phase': 'Pending', - 'reason': 'RequirementsNotMet', + lastTransitionTime: '2019-01-09T11:47:49Z', + lastUpdateTime: '2019-01-09T11:47:49Z', + message: "one or more requirements couldn't be found", + phase: 'Pending', + reason: 'RequirementsNotMet', }, { - 'lastTransitionTime': '2019-01-09T11:47:51Z', - 'lastUpdateTime': '2019-01-09T11:47:51Z', - 'message': 'all requirements found, attempting install', - 'phase': 'InstallReady', - 'reason': 'AllRequirementsMet', + lastTransitionTime: '2019-01-09T11:47:51Z', + lastUpdateTime: '2019-01-09T11:47:51Z', + message: 'all requirements found, attempting install', + phase: 'InstallReady', + reason: 'AllRequirementsMet', }, { - 'lastTransitionTime': '2019-01-09T11:47:52Z', - 'lastUpdateTime': '2019-01-09T11:47:52Z', - 'message': 'waiting for install components to report healthy', - 'phase': 'Installing', - 'reason': 'InstallSucceeded', + lastTransitionTime: '2019-01-09T11:47:52Z', + lastUpdateTime: '2019-01-09T11:47:52Z', + message: 'waiting for install components to report healthy', + phase: 'Installing', + reason: 'InstallSucceeded', }, { - 'lastTransitionTime': '2019-01-09T11:47:52Z', - 'lastUpdateTime': '2019-01-09T11:47:54Z', - 'message': 'APIServices not installed', - 'phase': 'Installing', - 'reason': 'InstallWaiting', + lastTransitionTime: '2019-01-09T11:47:52Z', + lastUpdateTime: '2019-01-09T11:47:54Z', + message: 'APIServices not installed', + phase: 'Installing', + reason: 'InstallWaiting', }, { - 'lastTransitionTime': '2019-01-09T11:48:57Z', - 'lastUpdateTime': '2019-01-09T11:48:57Z', - 'message': 'install strategy completed with no errors', - 'phase': 'Succeeded', - 'reason': 'InstallSucceeded', + lastTransitionTime: '2019-01-09T11:48:57Z', + lastUpdateTime: '2019-01-09T11:48:57Z', + message: 'install strategy completed with no errors', + phase: 'Succeeded', + reason: 'InstallSucceeded', }, { - 'lastTransitionTime': '2019-01-09T11:50:58Z', - 'lastUpdateTime': '2019-01-09T11:50:58Z', - 'message': 'APIServices not installed', - 'phase': 'Failed', - 'reason': 'ComponentUnhealthy', + lastTransitionTime: '2019-01-09T11:50:58Z', + lastUpdateTime: '2019-01-09T11:50:58Z', + message: 'APIServices not installed', + phase: 'Failed', + reason: 'ComponentUnhealthy', }, { - 'lastTransitionTime': '2019-01-09T11:51:16Z', - 'lastUpdateTime': '2019-01-09T11:51:16Z', - 'message': 'install strategy completed with no errors', - 'phase': 'Succeeded', - 'reason': 'InstallSucceeded', + lastTransitionTime: '2019-01-09T11:51:16Z', + lastUpdateTime: '2019-01-09T11:51:16Z', + message: 'install strategy completed with no errors', + phase: 'Succeeded', + reason: 'InstallSucceeded', }, ], - 'phase': 'Succeeded', - 'certsRotateAt': '2021-01-07T11:47:52Z', + phase: 'Succeeded', + certsRotateAt: '2021-01-07T11:47:52Z', }, }, { - 'apiVersion': 'operators.coreos.com/v1alpha1', - 'kind': 'ClusterServiceVersion', - 'metadata': { - 'annotations': { + apiVersion: 'operators.coreos.com/v1alpha1', + kind: 'ClusterServiceVersion', + metadata: { + annotations: { 'olm.operatorGroup': 'olm-operators', 'olm.operatorNamespace': 'openshift-operator-lifecycle-manager', 'olm.targetNamespaces': 'openshift-operator-lifecycle-manager', }, - 'selfLink': '/apis/operators.coreos.com/v1alpha1/namespaces/openshift-operator-lifecycle-manager/clusterserviceversions/svcat.v0.1.34', - 'resourceVersion': '121331', - 'name': 'svcat.v0.1.34', - 'uid': 'ae8afaee-1407-11e9-8620-027d9941c4da', - 'creationTimestamp': '2019-01-09T12:11:23Z', - 'generation': 1, - 'namespace': 'openshift-operator-lifecycle-manager', - 'labels': { + selfLink: + '/apis/operators.coreos.com/v1alpha1/namespaces/openshift-operator-lifecycle-manager/clusterserviceversions/svcat.v0.1.34', + resourceVersion: '121331', + name: 'svcat.v0.1.34', + uid: 'ae8afaee-1407-11e9-8620-027d9941c4da', + creationTimestamp: '2019-01-09T12:11:23Z', + generation: 1, + namespace: 'openshift-operator-lifecycle-manager', + labels: { 'alm-catalog': 'rh-operators', }, }, - 'spec': { - 'customresourcedefinitions': {}, - 'apiservicedefinitions': { - 'owned': [ + spec: { + customresourcedefinitions: {}, + apiservicedefinitions: { + owned: [ { - 'containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ClusterServiceClass', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ClusterServiceClass', - 'name': 'clusterserviceclasses', - 'version': 'v1beta1', + containerPort: 5443, + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ClusterServiceClass', + group: 'servicecatalog.k8s.io', + kind: 'ClusterServiceClass', + name: 'clusterserviceclasses', + version: 'v1beta1', }, { - 'containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ClusterServicePlan', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ClusterServicePlan', - 'name': 'clusterserviceplans', - 'version': 'v1beta1', + containerPort: 5443, + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ClusterServicePlan', + group: 'servicecatalog.k8s.io', + kind: 'ClusterServicePlan', + name: 'clusterserviceplans', + version: 'v1beta1', }, { - 'containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ClusterServiceBroker', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ClusterServiceBroker', - 'name': 'clusterservicebrokers', - 'version': 'v1beta1', + containerPort: 5443, + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ClusterServiceBroker', + group: 'servicecatalog.k8s.io', + kind: 'ClusterServiceBroker', + name: 'clusterservicebrokers', + version: 'v1beta1', }, { - 'containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ServiceInstance', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ServiceInstance', - 'name': 'serviceinstances', - 'version': 'v1beta1', + containerPort: 5443, + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ServiceInstance', + group: 'servicecatalog.k8s.io', + kind: 'ServiceInstance', + name: 'serviceinstances', + version: 'v1beta1', }, { ': [containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ServiceBinding', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ServiceBinding', - 'name': 'servicebindings', - 'version': 'v1beta1', + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ServiceBinding', + group: 'servicecatalog.k8s.io', + kind: 'ServiceBinding', + name: 'servicebindings', + version: 'v1beta1', }, { ': [containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ServiceClass', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ServiceClass', - 'name': 'serviceclasses', - 'version': 'v1beta1', + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ServiceClass', + group: 'servicecatalog.k8s.io', + kind: 'ServiceClass', + name: 'serviceclasses', + version: 'v1beta1', }, { ': [containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ServicePlan', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ServicePlan', - 'name': 'serviceplans', - 'version': 'v1beta1', + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ServicePlan', + group: 'servicecatalog.k8s.io', + kind: 'ServicePlan', + name: 'serviceplans', + version: 'v1beta1', }, { ': [containerPort': 5443, - 'deploymentName': 'apiserver', - 'description': 'A service catalog resource', - 'displayName': 'ServiceBroker', - 'group': 'servicecatalog.k8s.io', - 'kind': 'ServiceBroker', - 'name': 'servicebrokers', - 'version': 'v1beta1', + deploymentName: 'apiserver', + description: 'A service catalog resource', + displayName: 'ServiceBroker', + group: 'servicecatalog.k8s.io', + kind: 'ServiceBroker', + name: 'servicebrokers', + version: 'v1beta1', }, ], }, - 'keywords': ['catalog', - 'service', - 'svcat', - 'osb', - 'broker'], - 'displayName': 'Service Catalog', - 'provider': { - 'name': 'Red Hat', + keywords: ['catalog', 'service', 'svcat', 'osb', 'broker'], + displayName: 'Service Catalog', + provider: { + name: 'Red Hat', }, - 'maturity': 'alpha', - 'installModes': [ + maturity: 'alpha', + installModes: [ { - 'supported': true, - 'type': 'OwnNamespace', + supported: true, + type: 'OwnNamespace', }, { - 'supported': true, - 'type': 'SingleNamespace', + supported: true, + type: 'SingleNamespace', }, { - 'supported': false, - 'type': 'MultiNamespace', + supported: false, + type: 'MultiNamespace', }, { - 'supported': true, - 'type': 'AllNamespaces', + supported: true, + type: 'AllNamespaces', }, ], - 'version': '0.1.34', - 'links': [ + version: '0.1.34', + links: [ { - 'name': 'Documentation', - 'url': 'https://svc-cat.io/docs', + name: 'Documentation', + url: 'https://svc-cat.io/docs', }, { - 'name': 'Service Catalog', - 'url': 'https://github.com/kubernetes-incubator/service-catalog', + name: 'Service Catalog', + url: 'https://github.com/kubernetes-incubator/service-catalog', }, ], - 'install': { - 'spec': { - 'clusterPermissions': [ + install: { + spec: { + clusterPermissions: [ { - 'rules': [ + rules: [ { - 'apiGroups': [''], - 'resources': ['events'], - 'verbs': ['create', - 'patch', - 'update'], + apiGroups: [''], + resources: ['events'], + verbs: ['create', 'patch', 'update'], }, { - 'apiGroups': [''], - 'resources': ['secrets'], - 'verbs': ['get', - 'create', - 'update', - 'delete', - 'list', - 'watch', - 'patch'], + apiGroups: [''], + resources: ['secrets'], + verbs: ['get', 'create', 'update', 'delete', 'list', 'watch', 'patch'], }, { - 'apiGroups': [''], - 'resources': ['pods'], - 'verbs': ['get', - 'list', - 'update', - 'patch', - 'watch', - 'delete', - 'initialize'], + apiGroups: [''], + resources: ['pods'], + verbs: ['get', 'list', 'update', 'patch', 'watch', 'delete', 'initialize'], }, { - 'apiGroups': [''], - 'resources': ['namespaces'], - 'verbs': ['get', - 'list', - 'watch'], + apiGroups: [''], + resources: ['namespaces'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['clusterserviceclasses'], - 'verbs': ['get', - 'list', - 'watch', - 'create', - 'patch', - 'update', - 'delete'], + apiGroups: ['servicecatalog.k8s.io'], + resources: ['clusterserviceclasses'], + verbs: ['get', 'list', 'watch', 'create', 'patch', 'update', 'delete'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['clusterserviceplans'], - 'verbs': ['get', - 'list', - 'watch', - 'create', - 'patch', - 'update', - 'delete'], + apiGroups: ['servicecatalog.k8s.io'], + resources: ['clusterserviceplans'], + verbs: ['get', 'list', 'watch', 'create', 'patch', 'update', 'delete'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['clusterservicebrokers'], - 'verbs': ['get', - 'list', - 'watch'], + apiGroups: ['servicecatalog.k8s.io'], + resources: ['clusterservicebrokers'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['serviceinstances', - 'servicebindings'], - 'verbs': ['get', - 'list', - 'watch', - 'update'], + apiGroups: ['servicecatalog.k8s.io'], + resources: ['serviceinstances', 'servicebindings'], + verbs: ['get', 'list', 'watch', 'update'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['clusterservicebrokers/status', + apiGroups: ['servicecatalog.k8s.io'], + resources: [ + 'clusterservicebrokers/status', 'clusterserviceclasses/status', 'clusterserviceplans/status', 'serviceinstances/status', 'serviceinstances/reference', 'servicebindings/status', - 'servicebindings/finalizers'], - 'verbs': ['update'], + 'servicebindings/finalizers', + ], + verbs: ['update'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['serviceclasses'], - 'verbs': ['get', - 'list', - 'watch', - 'create', - 'patch', - 'update', - 'delete'], + apiGroups: ['servicecatalog.k8s.io'], + resources: ['serviceclasses'], + verbs: ['get', 'list', 'watch', 'create', 'patch', 'update', 'delete'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['serviceplans'], - 'verbs': ['get', - 'list', - 'watch', - 'create', - 'patch', - 'update', - 'delete'], + apiGroups: ['servicecatalog.k8s.io'], + resources: ['serviceplans'], + verbs: ['get', 'list', 'watch', 'create', 'patch', 'update', 'delete'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['servicebrokers'], - 'verbs': ['get', - 'list', - 'watch'], + apiGroups: ['servicecatalog.k8s.io'], + resources: ['servicebrokers'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['servicecatalog.k8s.io'], - 'resources': ['servicebrokers/status', + apiGroups: ['servicecatalog.k8s.io'], + resources: [ + 'servicebrokers/status', 'serviceclasses/status', - 'serviceplans/status'], - 'verbs': ['update'], + 'serviceplans/status', + ], + verbs: ['update'], }, ], - 'serviceAccountName': 'service-catalog-controller', + serviceAccountName: 'service-catalog-controller', }, { - 'rules': [ + rules: [ { - 'apiGroups': [''], - 'resourceNames': ['extension-apiserver-authentication'], - 'resources': ['configmaps'], - 'verbs': ['get'], + apiGroups: [''], + resourceNames: ['extension-apiserver-authentication'], + resources: ['configmaps'], + verbs: ['get'], }, { - 'apiGroups': [''], - 'resources': ['namespaces'], - 'verbs': ['get', - 'list', - 'watch'], + apiGroups: [''], + resources: ['namespaces'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['admissionregistration.k8s.io'], - 'resources': ['validatingwebhookconfigurations'], - 'verbs': ['get', - 'list', - 'watch'], + apiGroups: ['admissionregistration.k8s.io'], + resources: ['validatingwebhookconfigurations'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['admissionregistration.k8s.io'], - 'resources': ['mutatingwebhookconfigurations'], - 'verbs': ['get', - 'list', - 'watch'], + apiGroups: ['admissionregistration.k8s.io'], + resources: ['mutatingwebhookconfigurations'], + verbs: ['get', 'list', 'watch'], }, { - 'apiGroups': ['authentication.k8s.io'], - 'resources': ['tokenreviews'], - 'verbs': ['create'], + apiGroups: ['authentication.k8s.io'], + resources: ['tokenreviews'], + verbs: ['create'], }, { - 'apiGroups': ['authorization.k8s.io'], - 'resources': ['subjectaccessreviews'], - 'verbs': ['create'], + apiGroups: ['authorization.k8s.io'], + resources: ['subjectaccessreviews'], + verbs: ['create'], }, ], - 'serviceAccountName': 'service-catalog-apiserver', + serviceAccountName: 'service-catalog-apiserver', }, ], - 'deployments': [ + deployments: [ { - 'name': 'apiserver', - 'spec': { - 'replicas': 1, 'selector': { - 'matchLabels': { - 'app': 'apiserver', + name: 'apiserver', + spec: { + replicas: 1, + selector: { + matchLabels: { + app: 'apiserver', }, }, - 'strategy': { - 'type': 'RollingUpdate', + strategy: { + type: 'RollingUpdate', }, - 'template': { - 'metadata': { - 'labels': { - 'app': 'apiserver', + template: { + metadata: { + labels: { + app: 'apiserver', }, }, - 'spec': { - 'containers': [ + spec: { + containers: [ { - 'resources': { - 'limits': { - 'cpu': '100m', - 'memory': '140Mi', + resources: { + limits: { + cpu: '100m', + memory: '140Mi', }, - 'requests': { - 'cpu': '100m', - 'memory': '40Mi', + requests: { + cpu: '100m', + memory: '40Mi', }, }, - 'readinessProbe': { - 'failureThreshold': 1, - 'httpGet': { - 'path': '/healthz', - 'port': 5443, - 'scheme': 'HTTPS', + readinessProbe: { + failureThreshold: 1, + httpGet: { + path: '/healthz', + port: 5443, + scheme: 'HTTPS', }, - 'initialDelaySeconds': 30, - 'periodSeconds': 5, - 'successThreshold': 1, - 'timeoutSeconds': 5, + initialDelaySeconds: 30, + periodSeconds: 5, + successThreshold: 1, + timeoutSeconds: 5, }, - 'name': 'apiserver', - 'command': ['/usr/bin/service-catalog'], - 'livenessProbe': { - 'failureThreshold': 3, - 'httpGet': { - 'path': '/healthz', - 'port': 5443, - 'scheme': 'HTTPS', + name: 'apiserver', + command: ['/usr/bin/service-catalog'], + livenessProbe: { + failureThreshold: 3, + httpGet: { + path: '/healthz', + port: 5443, + scheme: 'HTTPS', }, - 'initialDelaySeconds': 30, - 'periodSeconds': 10, - 'successThreshold': 1, - 'timeoutSeconds': 5, + initialDelaySeconds: 30, + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 5, }, - 'ports': [ + ports: [ { - 'containerPort': 5443, + containerPort: 5443, }, ], - 'imagePullPolicy': 'IfNotPresent', - 'volumeMounts': [ + imagePullPolicy: 'IfNotPresent', + volumeMounts: [ { - 'mountPath': '/var/run/kubernetes-service-catalog', - 'name': 'apiservice-cert', + mountPath: '/var/run/kubernetes-service-catalog', + name: 'apiservice-cert', }, ], - 'image': 'quay.io/openshift/origin-service-catalog:v4.0.0', - 'args': ['apiserver', + image: 'quay.io/openshift/origin-service-catalog:v4.0.0', + args: [ + 'apiserver', '--enable-admission-plugins', 'NamespaceLifecycle,DefaultServicePlan,ServiceBindingsLifecycle,ServicePlanChangeValidator,BrokerAuthSarCheck', '--secure-port', @@ -1431,73 +1377,76 @@ export const catalogListPageProps = { '--feature-gates', 'OriginatingIdentity=true', '--feature-gates', - 'NamespacedServiceBroker=true'], + 'NamespacedServiceBroker=true', + ], }, { - 'resources': { - 'limits': { - 'cpu': '100m', - 'memory': '150Mi', + resources: { + limits: { + cpu: '100m', + memory: '150Mi', }, - 'requests': { - 'cpu': '100m', - 'memory': '50Mi', + requests: { + cpu: '100m', + memory: '50Mi', }, }, - 'readinessProbe': { - 'failureThreshold': 1, - 'httpGet': { - 'path': '/health', - 'port': 2379, + readinessProbe: { + failureThreshold: 1, + httpGet: { + path: '/health', + port: 2379, }, - 'initialDelaySeconds': 30, - 'periodSeconds': 5, - 'successThreshold': 1, - 'timeoutSeconds': 5, + initialDelaySeconds: 30, + periodSeconds: 5, + successThreshold: 1, + timeoutSeconds: 5, }, - 'name': 'etcd', - 'command': ['/usr/local/bin/etcd', + name: 'etcd', + command: [ + '/usr/local/bin/etcd', '--listen-client-urls', 'http://0.0.0.0:2379', '--advertise-client-urls', - 'http://localhost:2379'], - 'livenessProbe': { - 'failureThreshold': 3, - 'httpGet': { - 'path': '/health', - 'port': 2379, + 'http://localhost:2379', + ], + livenessProbe: { + failureThreshold: 3, + httpGet: { + path: '/health', + port: 2379, }, - 'initialDelaySeconds': 30, - 'periodSeconds': 10, - 'successThreshold': 1, - 'timeoutSeconds': 5, + initialDelaySeconds: 30, + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 5, }, - 'env': [ + env: [ { - 'name': 'ETCD_DATA_DIR', - 'value': '/etcd-data-dir', + name: 'ETCD_DATA_DIR', + value: '/etcd-data-dir', }, ], - 'ports': [ + ports: [ { - 'containerPort': 2379, + containerPort: 2379, }, ], - 'imagePullPolicy': 'Always', - 'volumeMounts': [ + imagePullPolicy: 'Always', + volumeMounts: [ { - 'mountPath': '/etcd-data-dir', - 'name': 'etcd-data-dir', + mountPath: '/etcd-data-dir', + name: 'etcd-data-dir', }, ], - 'image': 'quay.io/coreos/etcd:latest', + image: 'quay.io/coreos/etcd:latest', }, ], - 'serviceAccountName': 'service-catalog-apiserver', - 'volumes': [ + serviceAccountName: 'service-catalog-apiserver', + volumes: [ { - 'emptyDir': {}, - 'name': 'etcd-data-dir', + emptyDir: {}, + name: 'etcd-data-dir', }, ], }, @@ -1505,86 +1454,87 @@ export const catalogListPageProps = { }, }, { - 'name': 'controller-manager', - 'spec': { - 'replicas': 1, - 'selector': { - 'matchLabels': { - 'app': 'controller-manager', + name: 'controller-manager', + spec: { + replicas: 1, + selector: { + matchLabels: { + app: 'controller-manager', }, }, - 'strategy': { - 'type': 'RollingUpdate', + strategy: { + type: 'RollingUpdate', }, - 'template': { - 'metadata': { - 'labels': { - 'app': 'controller-manager', + template: { + metadata: { + labels: { + app: 'controller-manager', }, }, - 'spec': { - 'containers': [ + spec: { + containers: [ { - 'resources': { - 'limits': { - 'cpu': '100m', - 'memory': '150Mi', + resources: { + limits: { + cpu: '100m', + memory: '150Mi', }, - 'requests': { - 'cpu': '100m', - 'memory': '100Mi', + requests: { + cpu: '100m', + memory: '100Mi', }, }, - 'readinessProbe': { - 'failureThreshold': 1, - 'httpGet': { - 'path': '/healthz', - 'port': 8444, - 'scheme': 'HTTPS', + readinessProbe: { + failureThreshold: 1, + httpGet: { + path: '/healthz', + port: 8444, + scheme: 'HTTPS', }, - 'initialDelaySeconds': 20, - 'periodSeconds': 10, - 'successThreshold': 1, - 'timeoutSeconds': 2, + initialDelaySeconds: 20, + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 2, }, - 'name': 'controller-manager', - 'command': ['/usr/bin/service-catalog'], - 'livenessProbe': { - 'failureThreshold': 3, - 'httpGet': { - 'path': '/healthz', - 'port': 8444, - 'scheme': 'HTTPS', + name: 'controller-manager', + command: ['/usr/bin/service-catalog'], + livenessProbe: { + failureThreshold: 3, + httpGet: { + path: '/healthz', + port: 8444, + scheme: 'HTTPS', }, - 'initialDelaySeconds': 20, - 'periodSeconds': 10, - 'successThreshold': 1, - 'timeoutSeconds': 2, + initialDelaySeconds: 20, + periodSeconds: 10, + successThreshold: 1, + timeoutSeconds: 2, }, - 'env': [ + env: [ { - 'name': 'K8S_NAMESPACE', - 'valueFrom': { - 'fieldRef': { - 'fieldPath': 'metadata.namespace', + name: 'K8S_NAMESPACE', + valueFrom: { + fieldRef: { + fieldPath: 'metadata.namespace', }, }, }, ], - 'ports': [ + ports: [ { - 'containerPort': 8444, + containerPort: 8444, }, ], - 'imagePullPolicy': 'IfNotPresent', - 'volumeMounts': [ + imagePullPolicy: 'IfNotPresent', + volumeMounts: [ { - 'mountPath': '/var/run/kubernetes-service-catalog', - 'name': 'apiservice-cert', + mountPath: '/var/run/kubernetes-service-catalog', + name: 'apiservice-cert', }, ], - 'image': 'quay.io/openshift/origin-service-catalog:v4.0.0', - 'args': ['controller-manager', + image: 'quay.io/openshift/origin-service-catalog:v4.0.0', + args: [ + 'controller-manager', '--secure-port', '8444', '-v', @@ -1601,26 +1551,27 @@ export const catalogListPageProps = { '--feature-gates', 'AsyncBindingOperations=true', '--feature-gates', - 'NamespacedServiceBroker=true'], + 'NamespacedServiceBroker=true', + ], }, ], - 'serviceAccountName': 'service-catalog-controller', - 'volumes': [ + serviceAccountName: 'service-catalog-controller', + volumes: [ { - 'name': 'apiservice-cert', - 'secret': { - 'defaultMode': 420, - 'items': [ + name: 'apiservice-cert', + secret: { + defaultMode: 420, + items: [ { - 'key': 'tls.crt', - 'path': 'apiserver.crt', + key: 'tls.crt', + path: 'apiserver.crt', }, { - 'key': 'tls.key', - 'path': 'apiserver.key', + key: 'tls.key', + path: 'apiserver.key', }, ], - 'secretName': 'v1beta1.servicecatalog.k8s.io-cert', + secretName: 'v1beta1.servicecatalog.k8s.io-cert', }, }, ], @@ -1629,439 +1580,385 @@ export const catalogListPageProps = { }, }, ], - 'permissions': [ + permissions: [ { - 'rules': [ + rules: [ { - 'apiGroups': [''], - 'resourceNames': ['cluster-info'], - 'resources': ['configmaps'], - 'verbs': ['get', - 'create', - 'list', - 'watch', - 'update'], + apiGroups: [''], + resourceNames: ['cluster-info'], + resources: ['configmaps'], + verbs: ['get', 'create', 'list', 'watch', 'update'], }, { - 'apiGroups': [''], - 'resources': ['configmaps'], - 'verbs': ['create', - 'list', - 'watch', - 'get', - 'update'], + apiGroups: [''], + resources: ['configmaps'], + verbs: ['create', 'list', 'watch', 'get', 'update'], }, { - 'apiGroups': [''], - 'resourceNames': ['service-catalog-controller-manager'], - 'resources': ['configmaps'], - 'verbs': ['get', - 'update'], + apiGroups: [''], + resourceNames: ['service-catalog-controller-manager'], + resources: ['configmaps'], + verbs: ['get', 'update'], }, ], - 'serviceAccountName': 'service-catalog-controller', + serviceAccountName: 'service-catalog-controller', }, ], }, - 'strategy': 'deployment', + strategy: 'deployment', }, - 'maintainers': [ + maintainers: [ { - 'email': 'openshift-operators@redhat.com', - 'name': 'Red Hat', + email: 'openshift-operators@redhat.com', + name: 'Red Hat', }, ], - 'description': 'Service Catalog lets you provision cloud services directly from the comfort of native Kubernetes tooling. This project is in incubation to bring integration with service brokers to the Kubernetes ecosystem via the Open Service Broker API.', + description: + 'Service Catalog lets you provision cloud services directly from the comfort of native Kubernetes tooling. This project is in incubation to bring integration with service brokers to the Kubernetes ecosystem via the Open Service Broker API.', }, - 'status': { - 'reason': 'RequirementsNotMet', - 'message': 'one or more requirements couldn\'t be found', - 'lastUpdateTime': '2019-01-09T14:36:26Z', - 'requirementStatus': [ + status: { + reason: 'RequirementsNotMet', + message: "one or more requirements couldn't be found", + lastUpdateTime: '2019-01-09T14:36:26Z', + requirementStatus: [ { - 'group': 'apiregistration.k8s.io', - 'kind': 'APIService', - 'message': '', - 'name': 'v1beta1.servicecatalog.k8s.io', - 'status': 'DeploymentFound', - 'version': 'v1', + group: 'apiregistration.k8s.io', + kind: 'APIService', + message: '', + name: 'v1beta1.servicecatalog.k8s.io', + status: 'DeploymentFound', + version: 'v1', }, { - 'dependents': [ + dependents: [ { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'namespaced rule:{\'verbs\': [\'get\',\'create\',\'list\',\'watch\',\'update\'],\'apiGroups\': [\'\'],\'resources\': [\'configmaps\'],\'resourceNames\': [\'cluster-info\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "namespaced rule:{'verbs': ['get','create','list','watch','update'],'apiGroups': [''],'resources': ['configmaps'],'resourceNames': ['cluster-info']}", + status: 'Satisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'namespaced rule:{\'verbs\': [\'create\',\'list\',\'watch\',\'get\',\'update\'],\'apiGroups\': [\'\'],\'resources\': [\'configmaps\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "namespaced rule:{'verbs': ['create','list','watch','get','update'],'apiGroups': [''],'resources': ['configmaps']}", + status: 'Satisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'namespaced rule:{\'verbs\': [\'get\',\'update\'],\'apiGroups\': [\'\'],\'resources\': [\'configmaps\'],\'resourceNames\': [\'service-catalog-controller-manager\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "namespaced rule:{'verbs': ['get','update'],'apiGroups': [''],'resources': ['configmaps'],'resourceNames': ['service-catalog-controller-manager']}", + status: 'Satisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'create\',\'patch\',\'update\'],\'apiGroups\': [\'\'],\'resources\': [\'events\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['create','patch','update'],'apiGroups': [''],'resources': ['events']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'create\',\'update\',\'delete\',\'list\',\'watch\',\'patch\'],\'apiGroups\': [\'\'],\'resources\': [\'secrets\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','create','update','delete','list','watch','patch'],'apiGroups': [''],'resources': ['secrets']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'update\',\'patch\',\'watch\',\'delete\',\'initialize\'],\'apiGroups\': [\'\'],\'resources\': [\'pods\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','update','patch','watch','delete','initialize'],'apiGroups': [''],'resources': ['pods']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'\'],\'resources\': [\'namespaces\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': [''],'resources': ['namespaces']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\',\'create\',\'patch\',\'update\',\'delete\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'clusterserviceclasses\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch','create','patch','update','delete'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['clusterserviceclasses']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\',\'create\',\'patch\',\'update\',\'delete\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'clusterserviceplans\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch','create','patch','update','delete'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['clusterserviceplans']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'clusterservicebrokers\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['clusterservicebrokers']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\',\'update\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'serviceinstances\',\'servicebindings\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch','update'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['serviceinstances','servicebindings']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'update\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'clusterservicebrokers/status\',\'clusterserviceclasses/status\',\'clusterserviceplans/status\',\'serviceinstances/status\',\'serviceinstances/reference\',\'servicebindings/status\',\'servicebindings/finalizers\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['update'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['clusterservicebrokers/status','clusterserviceclasses/status','clusterserviceplans/status','serviceinstances/status','serviceinstances/reference','servicebindings/status','servicebindings/finalizers']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\',\'create\',\'patch\',\'update\',\'delete\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'serviceclasses\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch','create','patch','update','delete'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['serviceclasses']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\',\'create\',\'patch\',\'update\',\'delete\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'serviceplans\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch','create','patch','update','delete'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['serviceplans']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'servicebrokers\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['servicebrokers']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'update\'],\'apiGroups\': [\'servicecatalog.k8s.io\'],\'resources\': [\'servicebrokers/status\',\'serviceclasses/status\',\'serviceplans/status\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['update'],'apiGroups': ['servicecatalog.k8s.io'],'resources': ['servicebrokers/status','serviceclasses/status','serviceplans/status']}", + status: 'NotSatisfied', + version: 'v1beta1', }, ], - 'group': '', - 'kind': 'ServiceAccount', - 'message': 'Policy rule not satisfied for service account', - 'name': 'service-catalog-controller', - 'status': 'PresentNotSatisfied', - 'version': 'v1', + group: '', + kind: 'ServiceAccount', + message: 'Policy rule not satisfied for service account', + name: 'service-catalog-controller', + status: 'PresentNotSatisfied', + version: 'v1', }, { - 'dependents': [ + dependents: [ { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\'],\'apiGroups\': [\'\'],\'resources\': [\'configmaps\'],\'resourceNames\': [\'extension-apiserver-authentication\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get'],'apiGroups': [''],'resources': ['configmaps'],'resourceNames': ['extension-apiserver-authentication']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'\'],\'resources\': [\'namespaces\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': [''],'resources': ['namespaces']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'admissionregistration.k8s.io\'],\'resources\': [\'validatingwebhookconfigurations\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': ['admissionregistration.k8s.io'],'resources': ['validatingwebhookconfigurations']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'get\',\'list\',\'watch\'],\'apiGroups\': [\'admissionregistration.k8s.io\'],\'resources\': [\'mutatingwebhookconfigurations\']}', - 'status': 'NotSatisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['get','list','watch'],'apiGroups': ['admissionregistration.k8s.io'],'resources': ['mutatingwebhookconfigurations']}", + status: 'NotSatisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'create\'],\'apiGroups\': [\'authentication.k8s.io\'],\'resources\': [\'tokenreviews\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['create'],'apiGroups': ['authentication.k8s.io'],'resources': ['tokenreviews']}", + status: 'Satisfied', + version: 'v1beta1', }, { - 'group': 'rbac.authorization.k8s.io', - 'kind': 'PolicyRule', - 'message': 'cluster rule:{\'verbs\': [\'create\'],\'apiGroups\': [\'authorization.k8s.io\'],\'resources\': [\'subjectaccessreviews\']}', - 'status': 'Satisfied', - 'version': 'v1beta1', + group: 'rbac.authorization.k8s.io', + kind: 'PolicyRule', + message: + "cluster rule:{'verbs': ['create'],'apiGroups': ['authorization.k8s.io'],'resources': ['subjectaccessreviews']}", + status: 'Satisfied', + version: 'v1beta1', }, ], - 'group': '', - 'kind': 'ServiceAccount', - 'message': 'Policy rule not satisfied for service account', - 'name': 'service-catalog-apiserver', - 'status': 'PresentNotSatisfied', - 'version': 'v1', + group: '', + kind: 'ServiceAccount', + message: 'Policy rule not satisfied for service account', + name: 'service-catalog-apiserver', + status: 'PresentNotSatisfied', + version: 'v1', }, ], - 'certsLastUpdated': '2019-01-09T12:11:43Z', - 'lastTransitionTime': '2019-01-09T14:36:12Z', - 'conditions': [ + certsLastUpdated: '2019-01-09T12:11:43Z', + lastTransitionTime: '2019-01-09T14:36:12Z', + conditions: [ { - 'lastTransitionTime': '2019-01-09T12:11:23Z', - 'lastUpdateTime': '2019-01-09T12:11:23Z', - 'message': 'requirements not yet checked', - 'phase': 'Pending', - 'reason': 'RequirementsUnknown', + lastTransitionTime: '2019-01-09T12:11:23Z', + lastUpdateTime: '2019-01-09T12:11:23Z', + message: 'requirements not yet checked', + phase: 'Pending', + reason: 'RequirementsUnknown', }, { - 'lastTransitionTime': '2019-01-09T12:11:28Z', - 'lastUpdateTime': '2019-01-09T12:11:28Z', - 'message': 'all requirements found, attempting install', - 'phase': 'InstallReady', - 'reason': 'AllRequirementsMet', + lastTransitionTime: '2019-01-09T12:11:28Z', + lastUpdateTime: '2019-01-09T12:11:28Z', + message: 'all requirements found, attempting install', + phase: 'InstallReady', + reason: 'AllRequirementsMet', }, { - 'lastTransitionTime': '2019-01-09T12:11:42Z', - 'lastUpdateTime': '2019-01-09T12:11:42Z', - 'message': 'waiting for install components to report healthy', - 'phase': 'Installing', - 'reason': 'InstallSucceeded', + lastTransitionTime: '2019-01-09T12:11:42Z', + lastUpdateTime: '2019-01-09T12:11:42Z', + message: 'waiting for install components to report healthy', + phase: 'Installing', + reason: 'InstallSucceeded', }, { - 'lastTransitionTime': '2019-01-09T12:11:42Z', - 'lastUpdateTime': '2019-01-09T12:11:57Z', - 'message': 'APIServices not installed', - 'phase': 'Installing', - 'reason': 'InstallWaiting', + lastTransitionTime: '2019-01-09T12:11:42Z', + lastUpdateTime: '2019-01-09T12:11:57Z', + message: 'APIServices not installed', + phase: 'Installing', + reason: 'InstallWaiting', }, { - 'lastTransitionTime': '2019-01-09T12:12:44Z', - 'lastUpdateTime': '2019-01-09T12:12:44Z', - 'message': 'install strategy completed with no errors', - 'phase': 'Succeeded', - 'reason': 'InstallSucceeded', + lastTransitionTime: '2019-01-09T12:12:44Z', + lastUpdateTime: '2019-01-09T12:12:44Z', + message: 'install strategy completed with no errors', + phase: 'Succeeded', + reason: 'InstallSucceeded', }, { - 'lastTransitionTime': '2019-01-09T14:35:58Z', - 'lastUpdateTime': '2019-01-09T14:35:58Z', - 'message': 'requirements no longer met', - 'phase': 'Failed', - 'reason': 'RequirementsNotMet', + lastTransitionTime: '2019-01-09T14:35:58Z', + lastUpdateTime: '2019-01-09T14:35:58Z', + message: 'requirements no longer met', + phase: 'Failed', + reason: 'RequirementsNotMet', }, { - 'lastTransitionTime': '2019-01-09T14:36:12Z', - 'lastUpdateTime': '2019-01-09T14:36:12Z', - 'message': 'install strategy completed with no errors', - 'phase': 'Succeeded', - 'reason': 'InstallSucceeded', + lastTransitionTime: '2019-01-09T14:36:12Z', + lastUpdateTime: '2019-01-09T14:36:12Z', + message: 'install strategy completed with no errors', + phase: 'Succeeded', + reason: 'InstallSucceeded', }, { - 'lastTransitionTime': '2019-01-09T14:36:12Z', - 'lastUpdateTime': '2019-01-09T14:36:12Z', - 'message': 'requirements not met', - 'phase': 'Pending', - 'reason': 'RequirementsNotMet', + lastTransitionTime: '2019-01-09T14:36:12Z', + lastUpdateTime: '2019-01-09T14:36:12Z', + message: 'requirements not met', + phase: 'Pending', + reason: 'RequirementsNotMet', }, ], - 'phase': 'Pending', - 'certsRotateAt': '2021-01-07T12:11:42Z', + phase: 'Pending', + certsRotateAt: '2021-01-07T12:11:42Z', }, }, ], - 'filters': {}, - 'loadError': '', - 'loaded': true, - 'selected': null, + filters: {}, + loadError: '', + loaded: true, + selected: null, }, - 'loaded': true, + loaded: true, }; export const catalogItems = [ { - 'tags': [ - 'builder', - '.net', - 'dotnet', - 'dotnetcore', - 'rh-dotnet20', - ], + tags: ['builder', '.net', 'dotnet', 'dotnetcore', 'rh-dotnet20'], }, { - 'tags': [ - 'builder', - 'httpd', - ], + tags: ['builder', 'httpd'], }, { - 'tags': [ - 'quickstart', - 'php', - 'cakephp', - ], + tags: ['quickstart', 'php', 'cakephp'], }, { - 'tags': [ - 'quickstart', - 'perl', - 'dancer', - ], + tags: ['quickstart', 'perl', 'dancer'], }, { - 'tags': [ - 'quickstart', - 'python', - 'django', - ], + tags: ['quickstart', 'python', 'django'], }, { - 'tags': [ - 'instant-app', - 'jenkins', - ], + tags: ['instant-app', 'jenkins'], }, { - 'tags': [ - 'database', - 'mariadb', - ], + tags: ['database', 'mariadb'], }, { - 'tags': [ - 'database', - 'mongodb', - ], + tags: ['database', 'mongodb'], }, { - 'tags': [ - 'database', - 'mysql', - ], + tags: ['database', 'mysql'], }, { - 'tags': [ - 'builder', - 'nginx', - ], + tags: ['builder', 'nginx'], }, { - 'tags': [ - 'builder', - 'nodejs', - ], + tags: ['builder', 'nodejs'], }, { - 'tags': [ - 'quickstart', - 'nodejs', - ], + tags: ['quickstart', 'nodejs'], }, { - 'tags': [ - 'builder', - 'php', - ], + tags: ['builder', 'php'], }, { - 'tags': [ - 'builder', - 'perl', - ], + tags: ['builder', 'perl'], }, { - 'tags': [ - 'instant-app', - 'jenkins', - ], + tags: ['instant-app', 'jenkins'], }, { - 'tags': [ - 'database', - 'postgresql', - ], + tags: ['database', 'postgresql'], }, { - 'tags': [ - 'builder', - 'python', - ], + tags: ['builder', 'python'], }, { - 'tags': [ - 'quickstart', - 'ruby', - 'rails', - ], + tags: ['quickstart', 'ruby', 'rails'], }, { - 'tags': [ - 'builder', - 'ruby', - ], + tags: ['builder', 'ruby'], }, { - 'tags': [ - 'builder', - 'wildfly', - 'java', - ], + tags: ['builder', 'wildfly', 'java'], }, ]; diff --git a/frontend/__mocks__/clusterVersinMock.js b/frontend/__mocks__/clusterVersinMock.js index 0b478b2c64d..a0131e981db 100644 --- a/frontend/__mocks__/clusterVersinMock.js +++ b/frontend/__mocks__/clusterVersinMock.js @@ -26,21 +26,24 @@ export const clusterVersionProps = { }, { lastTransitionTime: '2019-07-30T04:18:13Z', - message: 'Cluster operator monitoring is reporting a failure: Failed to rollout the stack. Error: running task Updating prometheus-adapter failed: reconciling PrometheusAdapter Deployment failed: updating deployment object failed: waiting for DeploymentRollout of prometheus-adapter: deployment prometheus-adapter is not ready. status: (replicas: 3, updated: 1, ready: 2, unavailable: 1)', + message: + 'Cluster operator monitoring is reporting a failure: Failed to rollout the stack. Error: running task Updating prometheus-adapter failed: reconciling PrometheusAdapter Deployment failed: updating deployment object failed: waiting for DeploymentRollout of prometheus-adapter: deployment prometheus-adapter is not ready. status: (replicas: 3, updated: 1, ready: 2, unavailable: 1)', reason: 'ClusterOperatorDegraded', status: 'True', type: 'Failing', }, { lastTransitionTime: '2019-07-29T09:20:13Z', - message: 'Error while reconciling 4.2.0-0.ci-2019-07-22-025130: the cluster operator monitoring is degraded', + message: + 'Error while reconciling 4.2.0-0.ci-2019-07-22-025130: the cluster operator monitoring is degraded', reason: 'ClusterOperatorDegraded', status: 'False', type: 'Progressing', }, { lastTransitionTime: '2019-07-29T09:04:05Z', - message: 'Unable to retrieve available updates: currently installed version 4.2.0-0.ci-2019-07-22-025130 not found in the "stable-4.2" channel', + message: + 'Unable to retrieve available updates: currently installed version 4.2.0-0.ci-2019-07-22-025130 not found in the "stable-4.2" channel', reason: 'RemoteFailed', status: 'False', type: 'RetrievedUpdates', @@ -48,13 +51,15 @@ export const clusterVersionProps = { ], desired: { force: false, - image: 'registry.svc.ci.openshift.org/ocp/release@sha256:12da30aa8d94d8d4d4db3f8c88a30b6bdaf847bc714b2a551a2637a89c36f3c1', + image: + 'registry.svc.ci.openshift.org/ocp/release@sha256:12da30aa8d94d8d4d4db3f8c88a30b6bdaf847bc714b2a551a2637a89c36f3c1', version: '4.2.0-0.ci-2019-07-22-025130', }, history: [ { completionTime: '2019-07-29T09:20:13Z', - image: 'registry.svc.ci.openshift.org/ocp/release@sha256:12da30aa8d94d8d4d4db3f8c88a30b6bdaf847bc714b2a551a2637a89c36f3c1', + image: + 'registry.svc.ci.openshift.org/ocp/release@sha256:12da30aa8d94d8d4d4db3f8c88a30b6bdaf847bc714b2a551a2637a89c36f3c1', startedTime: '2019-07-29T09:04:05Z', state: 'Completed', verified: false, diff --git a/frontend/__mocks__/k8sResourcesMocks.ts b/frontend/__mocks__/k8sResourcesMocks.ts index 33d07364359..d0d800d8b45 100644 --- a/frontend/__mocks__/k8sResourcesMocks.ts +++ b/frontend/__mocks__/k8sResourcesMocks.ts @@ -5,7 +5,7 @@ export const testNamespace: K8sResourceKind = { kind: 'Namespace', metadata: { name: 'default', - annotations: {'alm-manager': 'tectonic-system.alm-operator'}, + annotations: { 'alm-manager': 'tectonic-system.alm-operator' }, }, }; @@ -49,7 +49,7 @@ export const testResourceInstance: K8sResourceKind = { spec: { selector: { matchLabels: { - 'fizz': 'buzz', + fizz: 'buzz', }, }, }, @@ -65,12 +65,14 @@ export const testOwnedResourceInstance: K8sResourceKind = { name: 'owned-test-resource', uid: '62fa5eac-3df4-448d-a576-916dd5b432f2', creationTimestamp: '2005-02-20T18:13:42Z', - ownerReferences: [{ - name: testResourceInstance.metadata.name, - kind: 'TestResource', - apiVersion: testResourceInstance.apiVersion, - uid: testResourceInstance.metadata.uid, - }], + ownerReferences: [ + { + name: testResourceInstance.metadata.name, + kind: 'TestResource', + apiVersion: testResourceInstance.apiVersion, + uid: testResourceInstance.metadata.uid, + }, + ], }, spec: {}, status: { diff --git a/frontend/__mocks__/localStorage.ts b/frontend/__mocks__/localStorage.ts index c727e8312f0..1900c3d8ffc 100644 --- a/frontend/__mocks__/localStorage.ts +++ b/frontend/__mocks__/localStorage.ts @@ -2,7 +2,7 @@ let _localStorage = {}; (window as any).localStorage = (window as any).sessionStorage = { setItem(key: string, value: string) { - Object.assign(_localStorage, { [key]: value} ); + Object.assign(_localStorage, { [key]: value }); }, getItem(key: string): string | null { return _localStorage.hasOwnProperty(key) ? _localStorage[key] : null; diff --git a/frontend/__tests__/actions/dashboards.spec.ts b/frontend/__tests__/actions/dashboards.spec.ts index 1cc3b8e05cc..b2f2a50947e 100644 --- a/frontend/__tests__/actions/dashboards.spec.ts +++ b/frontend/__tests__/actions/dashboards.spec.ts @@ -1,6 +1,12 @@ import { Map as ImmutableMap } from 'immutable'; -import { watchURL, ActionType, stopWatchURL, stopWatchPrometheusQuery, watchPrometheusQuery } from '../../public/actions/dashboards'; +import { + watchURL, + ActionType, + stopWatchURL, + stopWatchPrometheusQuery, + watchPrometheusQuery, +} from '../../public/actions/dashboards'; import { RESULTS_TYPE, defaults } from '../../public/reducers/dashboards'; const testStopWatch = (stopAction, type: RESULTS_TYPE, key: string) => { @@ -14,10 +20,12 @@ const testStopWatch = (stopAction, type: RESULTS_TYPE, key: string) => { }; const testStartWatch = (watchAction, type: RESULTS_TYPE, key: string) => { - const getState = jasmine.createSpy('getState').and.returnValues( - {dashboards: ImmutableMap(defaults)}, - {dashboards: ImmutableMap(defaults).setIn([type, key, 'active'], 1)}, - ); + const getState = jasmine + .createSpy('getState') + .and.returnValues( + { dashboards: ImmutableMap(defaults) }, + { dashboards: ImmutableMap(defaults).setIn([type, key, 'active'], 1) }, + ); const dispatch = jasmine.createSpy('dispatch'); watchAction(key)(dispatch, getState); @@ -40,9 +48,9 @@ const testStartWatch = (watchAction, type: RESULTS_TYPE, key: string) => { }; const testIncrementActiveWatch = (watchAction, type, key) => { - const getState = jasmine.createSpy('getState').and.returnValue( - {dashboards: ImmutableMap(defaults).setIn([type, key, 'active'], 1)}, - ); + const getState = jasmine + .createSpy('getState') + .and.returnValue({ dashboards: ImmutableMap(defaults).setIn([type, key, 'active'], 1) }); const dispatch = jasmine.createSpy('dispatch'); watchAction(key)(dispatch, getState); @@ -61,9 +69,7 @@ describe('dashboards-actions', () => { window.SERVER_FLAGS.prometheusBaseURL = undefined; }); - it('watchURL starts watching URL', () => - testStartWatch(watchURL, RESULTS_TYPE.URL, 'fooURL') - ); + it('watchURL starts watching URL', () => testStartWatch(watchURL, RESULTS_TYPE.URL, 'fooURL')); it('watchPrometheusQuery starts watching Query', () => { window.SERVER_FLAGS.prometheusBaseURL = 'prometheusBaseURL'; @@ -71,9 +77,9 @@ describe('dashboards-actions', () => { }); it('watchPrometheusQuery sets error if base url is not available', () => { - const getState = jasmine.createSpy('getState').and.returnValue( - {dashboards: ImmutableMap(defaults)} - ); + const getState = jasmine + .createSpy('getState') + .and.returnValue({ dashboards: ImmutableMap(defaults) }); const dispatch = jasmine.createSpy('dispatch'); watchPrometheusQuery('fooQuery')(dispatch, getState); @@ -89,18 +95,14 @@ describe('dashboards-actions', () => { }); it('watchURL increments active count for active watch', () => - testIncrementActiveWatch(watchURL, RESULTS_TYPE.URL, 'fooURL') - ); + testIncrementActiveWatch(watchURL, RESULTS_TYPE.URL, 'fooURL')); it('watchPrometheusQuery increments active count for active watch', () => - testIncrementActiveWatch(watchPrometheusQuery, RESULTS_TYPE.PROMETHEUS, 'fooQuery') - ); + testIncrementActiveWatch(watchPrometheusQuery, RESULTS_TYPE.PROMETHEUS, 'fooQuery')); it('stopWatchURL stops watching URL', () => - testStopWatch(stopWatchURL, RESULTS_TYPE.URL, 'fooURL') - ); + testStopWatch(stopWatchURL, RESULTS_TYPE.URL, 'fooURL')); it('stopWatchPrometheusQuery stops watching Prometheus', () => - testStopWatch(stopWatchPrometheusQuery, RESULTS_TYPE.PROMETHEUS, 'fooQuery') - ); + testStopWatch(stopWatchPrometheusQuery, RESULTS_TYPE.PROMETHEUS, 'fooQuery')); }); diff --git a/frontend/__tests__/actions/k8s.spec.ts b/frontend/__tests__/actions/k8s.spec.ts index 8070ea5335b..d9bbf0775d4 100644 --- a/frontend/__tests__/actions/k8s.spec.ts +++ b/frontend/__tests__/actions/k8s.spec.ts @@ -10,15 +10,20 @@ import { testResourceInstance } from '../../__mocks__/k8sResourcesMocks'; import * as coFetch from '../../public/co-fetch'; describe('watchAPIServices', () => { - const {watchAPIServices} = k8sActions; + const { watchAPIServices } = k8sActions; - const spyAndExpect = (spy: Spy) => (returnValue: any) => new Promise(resolve => spy.and.callFake((...args) => { - resolve(args); - return returnValue; - })); + const spyAndExpect = (spy: Spy) => (returnValue: any) => + new Promise((resolve) => + spy.and.callFake((...args) => { + resolve(args); + return returnValue; + }), + ); it('does nothing if already watching `APIServices`', (done) => { - const getState = jasmine.createSpy('getState').and.returnValue({k8s: ImmutableMap().set('apiservices', [])}); + const getState = jasmine + .createSpy('getState') + .and.returnValue({ k8s: ImmutableMap().set('apiservices', []) }); const dispatch = jasmine.createSpy('dispatch').and.callFake((action) => { fail(`Should not dispatch action: ${JSON.stringify(action)}`); }); @@ -28,67 +33,71 @@ describe('watchAPIServices', () => { }); it('dispatches `getResourcesInFlight` action before listing `APIServices`', (done) => { - const getState = jasmine.createSpy('getState').and.returnValue({k8s: ImmutableMap()}); + const getState = jasmine.createSpy('getState').and.returnValue({ k8s: ImmutableMap() }); const dispatch = jasmine.createSpy('dispatch'); spyOn(k8sActions, 'watchK8sList').and.returnValue({}); - spyAndExpect(spyOn(k8sResource, 'k8sList'))(Promise.resolve({})) - .then(() => { - expect(dispatch.calls.argsFor(0)[0].type).toEqual(k8sActions.ActionType.GetResourcesInFlight); - done(); - }); + spyAndExpect(spyOn(k8sResource, 'k8sList'))(Promise.resolve({})).then(() => { + expect(dispatch.calls.argsFor(0)[0].type).toEqual(k8sActions.ActionType.GetResourcesInFlight); + done(); + }); watchAPIServices()(dispatch, getState); }); it('attempts to list `APIServices`', (done) => { - const getState = jasmine.createSpy('getState').and.returnValue({k8s: ImmutableMap()}); + const getState = jasmine.createSpy('getState').and.returnValue({ k8s: ImmutableMap() }); const dispatch = jasmine.createSpy('dispatch'); spyOn(k8sActions, 'watchK8sList'); - spyAndExpect(spyOn(k8sResource, 'k8sList'))(Promise.resolve({})) - .then(([model]) => { - expect(model).toEqual(APIServiceModel); - done(); - }); + spyAndExpect(spyOn(k8sResource, 'k8sList'))(Promise.resolve({})).then(([model]) => { + expect(model).toEqual(APIServiceModel); + done(); + }); watchAPIServices()(dispatch, getState); }); it('falls back to polling Kubernetes `/apis` endpoint if cannot list `APIServices`', (done) => { - const getState = jasmine.createSpy('getState').and.returnValue({k8s: ImmutableMap()}); + const getState = jasmine.createSpy('getState').and.returnValue({ k8s: ImmutableMap() }); const dispatch = jasmine.createSpy('dispatch'); spyOn(k8sResource, 'k8sList').and.returnValue(Promise.reject()); - spyAndExpect(spyOn(coFetch, 'coFetchJSON'))(Promise.resolve({groups: []})) - .then(([path]) => { - expect(path).toEqual('api/kubernetes/apis'); - done(); - }); + spyAndExpect(spyOn(coFetch, 'coFetchJSON'))(Promise.resolve({ groups: [] })).then(([path]) => { + expect(path).toEqual('api/kubernetes/apis'); + done(); + }); watchAPIServices()(dispatch, getState); }); }); describe(k8sActions.ActionType.StartWatchK8sList, () => { - const {watchK8sList} = k8sActions; + const { watchK8sList } = k8sActions; let getState: Spy; - let resourceList: {items: K8sResourceKind[], metadata: {resourceVersion: string, continue?: string}, kind: string, apiVersion: string}; + let resourceList: { + items: K8sResourceKind[]; + metadata: { resourceVersion: string; continue?: string }; + kind: string; + apiVersion: string; + }; let model: K8sKind; beforeEach(() => { - getState = jasmine.createSpy('getState').and.returnValue({UI: ImmutableMap()}); - model = _.cloneDeep({...PodModel, verbs: ['list', 'get']}); + getState = jasmine.createSpy('getState').and.returnValue({ UI: ImmutableMap() }); + model = _.cloneDeep({ ...PodModel, verbs: ['list', 'get'] }); resourceList = { apiVersion: testResourceInstance.apiVersion, kind: `${testResourceInstance.kind}List`, items: new Array(300).fill(testResourceInstance), - metadata: {resourceVersion: '0'}, + metadata: { resourceVersion: '0' }, }; }); it('dispatches `loaded` action only once after first data is received', (done) => { - const k8sList = spyOn(k8sResource, 'k8sList').and.returnValue(Promise.resolve({...resourceList, items: new Array(10).fill(testResourceInstance)})); + const k8sList = spyOn(k8sResource, 'k8sList').and.returnValue( + Promise.resolve({ ...resourceList, items: new Array(10).fill(testResourceInstance) }), + ); const dispatch = jasmine.createSpy('dispatch').and.callFake((action) => { if (action.type === k8sActions.ActionType.Loaded) { @@ -111,8 +120,11 @@ describe(k8sActions.ActionType.StartWatchK8sList, () => { } else { expect(params.continue).toEqual('toNextPage'); } - resourceList.metadata.resourceVersion = (parseInt(resourceList.metadata.resourceVersion, 10) + 1).toString(); - resourceList.metadata.continue = parseInt(resourceList.metadata.resourceVersion, 10) < 10 ? 'toNextPage' : undefined; + resourceList.metadata.resourceVersion = ( + parseInt(resourceList.metadata.resourceVersion, 10) + 1 + ).toString(); + resourceList.metadata.continue = + parseInt(resourceList.metadata.resourceVersion, 10) < 10 ? 'toNextPage' : undefined; return resourceList; }); @@ -120,7 +132,9 @@ describe(k8sActions.ActionType.StartWatchK8sList, () => { let returnedItems = 0; const dispatch = jasmine.createSpy('dispatch').and.callFake((action) => { if (action.type === k8sActions.ActionType.BulkAddToList) { - const bulkAddToListCalls = dispatch.calls.allArgs().filter(args => args[0].type === k8sActions.ActionType.BulkAddToList); + const bulkAddToListCalls = dispatch.calls + .allArgs() + .filter((args) => args[0].type === k8sActions.ActionType.BulkAddToList); expect(action.payload.k8sObjects).toEqual(resourceList.items); expect(bulkAddToListCalls.length).toEqual(k8sList.calls.count() - 1); diff --git a/frontend/__tests__/actions/ui.spec.ts b/frontend/__tests__/actions/ui.spec.ts index 4204bbbe964..eb073dbcdc1 100644 --- a/frontend/__tests__/actions/ui.spec.ts +++ b/frontend/__tests__/actions/ui.spec.ts @@ -6,9 +6,11 @@ import store from '../../public/redux'; import * as UIActions from '../../public/actions/ui'; import * as router from '../../public/components/utils/router'; -const setActiveNamespace = ns => store.dispatch(UIActions.setActiveNamespace(ns)); -const setActivePerspective = perspective => store.dispatch(UIActions.setActivePerspective(perspective)); -const getNamespacedRoute = path => UIActions.formatNamespaceRoute(UIActions.getActiveNamespace(), path); +const setActiveNamespace = (ns) => store.dispatch(UIActions.setActiveNamespace(ns)); +const setActivePerspective = (perspective) => + store.dispatch(UIActions.setActivePerspective(perspective)); +const getNamespacedRoute = (path) => + UIActions.formatNamespaceRoute(UIActions.getActiveNamespace(), path); describe('ui-actions', () => { describe('UIActions.formatNamespaceRoute', () => { @@ -19,7 +21,7 @@ describe('ui-actions', () => { ['bar', '/status/ns/foo', '/status/ns/bar'], ['bar', '/k8s/all-namespaces/foo', '/k8s/ns/bar/foo'], ['bar', '/k8s/ns/foo/bar/baz', '/k8s/ns/bar/bar'], - ].forEach(t => { + ].forEach((t) => { expect(UIActions.formatNamespaceRoute(t[0], t[1])).toEqual(t[2]); }); }); @@ -50,22 +52,28 @@ describe('ui-actions', () => { it('should redirect namespaced location paths for known namespace-friendly prefixes', () => { window.location.pathname = '/k8s/ns/floorwax/pods'; setActiveNamespace('dessert-topping'); - expect(UIActions.formatNamespacedRouteForResource('pods')).toEqual('/k8s/ns/dessert-topping/pods'); + expect(UIActions.formatNamespacedRouteForResource('pods')).toEqual( + '/k8s/ns/dessert-topping/pods', + ); }); it('should redirect namespaced location paths to their prefixes', () => { window.location.pathname = '/k8s/ns/floorwax/pods/new-shimmer'; setActiveNamespace(ALL_NAMESPACES_KEY); // reset active namespace - expect(UIActions.formatNamespacedRouteForResource('pods')).toEqual('/k8s/all-namespaces/pods'); + expect(UIActions.formatNamespacedRouteForResource('pods')).toEqual( + '/k8s/all-namespaces/pods', + ); }); it('should redirect to all if no namespaces is selected', () => { window.location.pathname = '/k8s/ns/floorwax/pods'; setActiveNamespace(ALL_NAMESPACES_KEY); - expect(UIActions.formatNamespacedRouteForResource('pods')).toEqual('/k8s/all-namespaces/pods'); + expect(UIActions.formatNamespacedRouteForResource('pods')).toEqual( + '/k8s/all-namespaces/pods', + ); }); - it('should not redirect if the current path isn\'t namespaced, but should set active namespace in memory', () => { + it("should not redirect if the current path isn't namespaced, but should set active namespace in memory", () => { window.location.pathname = '/not-a-namespaced-path'; setActiveNamespace('dessert-topping'); expect(window.location.pathname).toEqual('/not-a-namespaced-path'); @@ -93,7 +101,7 @@ describe('ui-actions', () => { expect(getNamespacedRoute('/k8s/ns/hello/pods/GRIBBL')).toEqual('/k8s/ns/test/pods'); }); - it('preserves paths that aren\'t namespaced', () => { + it("preserves paths that aren't namespaced", () => { setActiveNamespace(ALL_NAMESPACES_KEY); expect(getNamespacedRoute('/')).toEqual('/'); expect(getNamespacedRoute('/gribbl')).toEqual('/gribbl'); @@ -103,7 +111,9 @@ describe('ui-actions', () => { it('parses resource from path', () => { setActiveNamespace(ALL_NAMESPACES_KEY); expect(getNamespacedRoute('/k8s/ns/foo/pods')).toEqual('/k8s/all-namespaces/pods'); - expect(getNamespacedRoute('/k8s/ns/foo/pods/WACKY_SUFFIX')).toEqual('/k8s/all-namespaces/pods'); + expect(getNamespacedRoute('/k8s/ns/foo/pods/WACKY_SUFFIX')).toEqual( + '/k8s/all-namespaces/pods', + ); }); }); diff --git a/frontend/__tests__/components/catalog.spec.tsx b/frontend/__tests__/components/catalog.spec.tsx index c0627f82f7f..28ae9e08da6 100644 --- a/frontend/__tests__/components/catalog.spec.tsx +++ b/frontend/__tests__/components/catalog.spec.tsx @@ -6,9 +6,20 @@ import { CatalogTile } from '../../node_modules/patternfly-react-extensions/dist import { VerticalTabsTab } from '../../node_modules/patternfly-react-extensions/dist/js/components/VerticalTabs'; import { FilterSidePanel } from '../../node_modules/patternfly-react-extensions/dist/js/components/FilterSidePanel'; -import { CatalogListPage, CatalogListPageProps, CatalogListPageState } from '../../public/components/catalog/catalog-page'; -import { CatalogTileViewPage, catalogCategories as initCatalogCategories } from '../../public/components/catalog/catalog-items'; -import { catalogListPageProps, catalogItems, catalogCategories} from '../../__mocks__/catalogItemsMocks'; +import { + CatalogListPage, + CatalogListPageProps, + CatalogListPageState, +} from '../../public/components/catalog/catalog-page'; +import { + CatalogTileViewPage, + catalogCategories as initCatalogCategories, +} from '../../public/components/catalog/catalog-items'; +import { + catalogListPageProps, + catalogItems, + catalogCategories, +} from '../../__mocks__/catalogItemsMocks'; import { categorizeItems } from '../../public/components/utils/tile-view-page'; describe(CatalogTileViewPage.displayName, () => { @@ -46,33 +57,49 @@ describe(CatalogTileViewPage.displayName, () => { expect(cakeSqlTileProps.iconImg).toEqual('test-file-stub'); expect(cakeSqlTileProps.iconClass).toBe(null); expect(cakeSqlTileProps.vendor).toEqual('provided by Red Hat, Inc.'); - expect(cakeSqlTileProps.description.startsWith('An example CakePHP application with a MySQL database')).toBe(true); + expect( + cakeSqlTileProps.description.startsWith( + 'An example CakePHP application with a MySQL database', + ), + ).toBe(true); const amqTileProps = tiles.at(19).props(); expect(amqTileProps.title).toEqual('Red Hat JBoss A-MQ 6.3 (Ephemeral, no SSL)'); expect(amqTileProps.iconImg).toEqual('test-file-stub'); expect(amqTileProps.iconClass).toBe(null); expect(amqTileProps.vendor).toEqual('provided by Red Hat, Inc.'); - expect(amqTileProps.description.startsWith('Application template for JBoss A-MQ brokers. These can be deployed as standalone or in a mesh. This template doesn\'t feature SSL support.')).toBe(true); + expect( + amqTileProps.description.startsWith( + "Application template for JBoss A-MQ brokers. These can be deployed as standalone or in a mesh. This template doesn't feature SSL support.", + ), + ).toBe(true); const wildflyTileProps = tiles.at(21).props(); expect(wildflyTileProps.title).toEqual('WildFly'); expect(wildflyTileProps.iconImg).toEqual('test-file-stub'); expect(wildflyTileProps.iconClass).toBe(null); expect(wildflyTileProps.vendor).toEqual('provided by Red Hat, Inc.'); - expect(wildflyTileProps.description.startsWith('Build and run WildFly 10.1 applications on CentOS 7. For more information about using this builder image')).toBe(true); + expect( + wildflyTileProps.description.startsWith( + 'Build and run WildFly 10.1 applications on CentOS 7. For more information about using this builder image', + ), + ).toBe(true); }); it('categorizes catalog items', () => { - const categories = categorizeItems(catalogItems, itemsToSort => _.sortBy(itemsToSort, 'tileName'), initCatalogCategories); + const categories = categorizeItems( + catalogItems, + (itemsToSort) => _.sortBy(itemsToSort, 'tileName'), + initCatalogCategories, + ); expect(_.keys(categories).length).toEqual(_.keys(catalogCategories).length); - _.each(_.keys(categories), key => { + _.each(_.keys(categories), (key) => { const category = categories[key]; expect(category.numItems).toEqual(catalogCategories[key].numItems); if (category.subcategories) { expect(category.subcategories.length).toEqual(catalogCategories[key].subcategories.length); } - _.each(_.keys(category.subcategories), subKey => { + _.each(_.keys(category.subcategories), (subKey) => { const subcategory = category.subcategories[subKey]; expect(subcategory.numItems).toEqual(catalogCategories[key].subcategories[subKey].numItems); }); diff --git a/frontend/__tests__/components/cluster-settings.spec.tsx b/frontend/__tests__/components/cluster-settings.spec.tsx index c5feaaee49f..b72c78d19f8 100644 --- a/frontend/__tests__/components/cluster-settings.spec.tsx +++ b/frontend/__tests__/components/cluster-settings.spec.tsx @@ -14,17 +14,11 @@ import { ClusterOperatorTabPage, } from '../../public/components/cluster-settings/cluster-settings'; import { GlobalConfigPage } from '../../public/components/cluster-settings/global-config'; -import { - Firehose, - HorizontalNav, - ResourceLink, - Timestamp, -} from '../../public/components/utils'; -import { AddCircleOIcon} from '@patternfly/react-icons'; - +import { Firehose, HorizontalNav, ResourceLink, Timestamp } from '../../public/components/utils'; +import { AddCircleOIcon } from '@patternfly/react-icons'; describe('Cluster Settings page', () => { - let wrapper : ShallowWrapper; + let wrapper: ShallowWrapper; const match = { url: '/settings/cluster', params: {}, isExact: true, path: '/settings/cluster' }; beforeEach(() => { @@ -39,28 +33,98 @@ describe('Cluster Settings page', () => { }); it('should render the Firehose Component with the props', () => { expect(wrapper.find(Firehose).exists()).toBe(true); - expect(wrapper.find(Firehose).at(0).props().resources.length).toBe(2); - expect(wrapper.find(Firehose).at(0).props().resources[0].kind).toBe('config.openshift.io~v1~ClusterVersion'); - expect(wrapper.find(Firehose).at(0).props().resources[1].kind).toBe('autoscaling.openshift.io~v1~ClusterAutoscaler'); - expect(wrapper.find(Firehose).at(0).props().resources[0].name).toBe('version'); - expect(wrapper.find(Firehose).at(0).props().resources[0].isList).toBe(false); - expect(wrapper.find(Firehose).at(0).props().resources[1].isList).toBe(true); + expect( + wrapper + .find(Firehose) + .at(0) + .props().resources.length, + ).toBe(2); + expect( + wrapper + .find(Firehose) + .at(0) + .props().resources[0].kind, + ).toBe('config.openshift.io~v1~ClusterVersion'); + expect( + wrapper + .find(Firehose) + .at(0) + .props().resources[1].kind, + ).toBe('autoscaling.openshift.io~v1~ClusterAutoscaler'); + expect( + wrapper + .find(Firehose) + .at(0) + .props().resources[0].name, + ).toBe('version'); + expect( + wrapper + .find(Firehose) + .at(0) + .props().resources[0].isList, + ).toBe(false); + expect( + wrapper + .find(Firehose) + .at(0) + .props().resources[1].isList, + ).toBe(true); }); it('should render the HorizontalNav Component with the props', () => { expect(wrapper.find(HorizontalNav).exists()).toBe(true); - expect(wrapper.find(HorizontalNav).at(0).props().pages.length).toBe(3); - expect(wrapper.find(HorizontalNav).at(0).props().hideDivider).toBe(true); - expect(wrapper.find(HorizontalNav).at(0).props().pages[0].name).toBe('Overview'); - expect(wrapper.find(HorizontalNav).at(0).props().pages[1].name).toBe('Cluster Operators'); - expect(wrapper.find(HorizontalNav).at(0).props().pages[2].name).toBe('Global Configuration'); - expect(wrapper.find(HorizontalNav).at(0).props().pages[0].component).toEqual(ClusterVersionDetailsTable); - expect(wrapper.find(HorizontalNav).at(0).props().pages[1].component).toEqual(ClusterOperatorTabPage); - expect(wrapper.find(HorizontalNav).at(0).props().pages[2].component).toEqual(GlobalConfigPage); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().pages.length, + ).toBe(3); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().hideDivider, + ).toBe(true); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().pages[0].name, + ).toBe('Overview'); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().pages[1].name, + ).toBe('Cluster Operators'); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().pages[2].name, + ).toBe('Global Configuration'); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().pages[0].component, + ).toEqual(ClusterVersionDetailsTable); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().pages[1].component, + ).toEqual(ClusterOperatorTabPage); + expect( + wrapper + .find(HorizontalNav) + .at(0) + .props().pages[2].component, + ).toEqual(GlobalConfigPage); }); }); describe('Cluster Version Details Table page', () => { - let wrapper : ShallowWrapper; + let wrapper: ShallowWrapper; let cv; beforeEach(() => { @@ -82,20 +146,76 @@ describe('Cluster Version Details Table page', () => { expect(wrapper.find(Timestamp).exists()).toBe(true); }); it('should render correct values of ClusterVersionDetailsTable component', () => { - expect(wrapper.find(CurrentChannel).at(0).props().cv.spec.channel).toEqual('stable-4.2'); - expect(wrapper.find(CurrentVersion).at(0).props().cv.status.desired.version).toEqual('4.2.0-0.ci-2019-07-22-025130'); - expect(wrapper.find('[data-test-id="cv-details-table-cid"]').text()).toEqual('342d8338-c08f-44ae-a82e-a032a4481fa9'); - expect(wrapper.find('[data-test-id="cv-details-table-image"]').text()).toEqual('registry.svc.ci.openshift.org/ocp/release@sha256:12da30aa8d94d8d4d4db3f8c88a30b6bdaf847bc714b2a551a2637a89c36f3c1'); - expect(wrapper.find('[data-test-id="cv-details-table-version"]').text()).toEqual('4.2.0-0.ci-2019-07-22-025130'); + expect( + wrapper + .find(CurrentChannel) + .at(0) + .props().cv.spec.channel, + ).toEqual('stable-4.2'); + expect( + wrapper + .find(CurrentVersion) + .at(0) + .props().cv.status.desired.version, + ).toEqual('4.2.0-0.ci-2019-07-22-025130'); + expect(wrapper.find('[data-test-id="cv-details-table-cid"]').text()).toEqual( + '342d8338-c08f-44ae-a82e-a032a4481fa9', + ); + expect(wrapper.find('[data-test-id="cv-details-table-image"]').text()).toEqual( + 'registry.svc.ci.openshift.org/ocp/release@sha256:12da30aa8d94d8d4d4db3f8c88a30b6bdaf847bc714b2a551a2637a89c36f3c1', + ); + expect(wrapper.find('[data-test-id="cv-details-table-version"]').text()).toEqual( + '4.2.0-0.ci-2019-07-22-025130', + ); expect(wrapper.find('[data-test-id="cv-details-table-state"]').text()).toEqual('Completed'); - expect(wrapper.find(ResourceLink).at(0).props().name).toEqual('version'); - expect(wrapper.find(Link).childAt(1).text()).toEqual('Create Autoscaler'); - expect(wrapper.find(Timestamp).at(0).props().timestamp).toEqual('2019-07-29T09:04:05Z'); - expect(wrapper.find(Timestamp).at(1).props().timestamp).toEqual('2019-07-29T09:20:13Z'); - expect(wrapper.find(ResourceLink).at(0).props().name).toEqual('version'); - expect(wrapper.find(Link).childAt(1).text()).toEqual('Create Autoscaler'); - expect(wrapper.find(Timestamp).at(0).props().timestamp).toEqual('2019-07-29T09:04:05Z'); - expect(wrapper.find(Timestamp).at(1).props().timestamp).toEqual('2019-07-29T09:20:13Z'); + expect( + wrapper + .find(ResourceLink) + .at(0) + .props().name, + ).toEqual('version'); + expect( + wrapper + .find(Link) + .childAt(1) + .text(), + ).toEqual('Create Autoscaler'); + expect( + wrapper + .find(Timestamp) + .at(0) + .props().timestamp, + ).toEqual('2019-07-29T09:04:05Z'); + expect( + wrapper + .find(Timestamp) + .at(1) + .props().timestamp, + ).toEqual('2019-07-29T09:20:13Z'); + expect( + wrapper + .find(ResourceLink) + .at(0) + .props().name, + ).toEqual('version'); + expect( + wrapper + .find(Link) + .childAt(1) + .text(), + ).toEqual('Create Autoscaler'); + expect( + wrapper + .find(Timestamp) + .at(0) + .props().timestamp, + ).toEqual('2019-07-29T09:04:05Z'); + expect( + wrapper + .find(Timestamp) + .at(1) + .props().timestamp, + ).toEqual('2019-07-29T09:20:13Z'); }); }); @@ -126,7 +246,6 @@ describe('Current Version', () => { it('should render the Current Version value', () => { expect(wrapper.text()).toBe('4.2.0-0.ci-2019-07-22-025130'); }); - }); describe('Current Version Header', () => { let wrapper: ShallowWrapper; diff --git a/frontend/__tests__/components/cluster-settings/basicauth-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/basicauth-idp-form.spec.tsx index e93925a7aab..af68a926c71 100644 --- a/frontend/__tests__/components/cluster-settings/basicauth-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/basicauth-idp-form.spec.tsx @@ -13,12 +13,24 @@ import { export const controlButtonTest = (wrapper: ShallowWrapper) => { expect(wrapper.find(ButtonBar).exists()).toBe(true); - expect(wrapper.find(Button).at(0).childAt(0).text()).toEqual('Add'); - expect(wrapper.find(Button).at(1).childAt(0).text()).toEqual('Cancel'); + expect( + wrapper + .find(Button) + .at(0) + .childAt(0) + .text(), + ).toEqual('Add'); + expect( + wrapper + .find(Button) + .at(1) + .childAt(0) + .text(), + ).toEqual('Cancel'); }; describe('Add Identity Provider: BasicAuthentication', () => { - let wrapper : ShallowWrapper<{}, AddBasicAuthPageState>; + let wrapper: ShallowWrapper<{}, AddBasicAuthPageState>; beforeEach(() => { wrapper = shallow(); diff --git a/frontend/__tests__/components/cluster-settings/github-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/github-idp-form.spec.tsx index 5fa9601dfc5..76a137e71d9 100644 --- a/frontend/__tests__/components/cluster-settings/github-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/github-idp-form.spec.tsx @@ -4,11 +4,14 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { ListInput } from '../../../public/components/utils'; import { IDPNameInput } from '../../../public/components/cluster-settings/idp-name-input'; import { IDPCAFileInput } from '../../../public/components/cluster-settings/idp-cafile-input'; -import { AddGitHubPage, AddGitHubPageState } from '../../../public/components/cluster-settings/github-idp-form'; +import { + AddGitHubPage, + AddGitHubPageState, +} from '../../../public/components/cluster-settings/github-idp-form'; import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: GitHub', () => { - let wrapper : ShallowWrapper<{}, AddGitHubPageState>; + let wrapper: ShallowWrapper<{}, AddGitHubPageState>; beforeEach(() => { wrapper = shallow(); @@ -40,7 +43,17 @@ describe('Add Identity Provider: GitHub', () => { }); it('should prefill GitHub list input default values as empty', () => { - expect(wrapper.find(ListInput).at(0).props().initialValues).toEqual(undefined); - expect(wrapper.find(ListInput).at(1).props().initialValues).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(0) + .props().initialValues, + ).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(1) + .props().initialValues, + ).toEqual(undefined); }); }); diff --git a/frontend/__tests__/components/cluster-settings/gitlab-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/gitlab-idp-form.spec.tsx index 6aca9435614..495ecf3e33c 100644 --- a/frontend/__tests__/components/cluster-settings/gitlab-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/gitlab-idp-form.spec.tsx @@ -3,11 +3,14 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { IDPNameInput } from '../../../public/components/cluster-settings/idp-name-input'; import { IDPCAFileInput } from '../../../public/components/cluster-settings/idp-cafile-input'; -import { AddGitLabPage, AddGitLabPageState } from '../../../public/components/cluster-settings/gitlab-idp-form'; +import { + AddGitLabPage, + AddGitLabPageState, +} from '../../../public/components/cluster-settings/gitlab-idp-form'; import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: GitLab', () => { - let wrapper : ShallowWrapper<{}, AddGitLabPageState>; + let wrapper: ShallowWrapper<{}, AddGitLabPageState>; beforeEach(() => { wrapper = shallow(); diff --git a/frontend/__tests__/components/cluster-settings/google-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/google-idp-form.spec.tsx index 73dd7e799df..c93ab9dd7eb 100644 --- a/frontend/__tests__/components/cluster-settings/google-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/google-idp-form.spec.tsx @@ -2,11 +2,14 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import { IDPNameInput } from '../../../public/components/cluster-settings/idp-name-input'; -import { AddGooglePage, AddGooglePageState } from '../../../public/components/cluster-settings/google-idp-form'; +import { + AddGooglePage, + AddGooglePageState, +} from '../../../public/components/cluster-settings/google-idp-form'; import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: Google', () => { - let wrapper : ShallowWrapper<{}, AddGooglePageState>; + let wrapper: ShallowWrapper<{}, AddGooglePageState>; beforeEach(() => { wrapper = shallow(); diff --git a/frontend/__tests__/components/cluster-settings/htpasswd-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/htpasswd-idp-form.spec.tsx index e11321dd0ee..813b55bdcfe 100644 --- a/frontend/__tests__/components/cluster-settings/htpasswd-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/htpasswd-idp-form.spec.tsx @@ -10,7 +10,7 @@ import { import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: HTPasswd', () => { - let wrapper : ShallowWrapper<{}, AddHTPasswdPageState>; + let wrapper: ShallowWrapper<{}, AddHTPasswdPageState>; beforeEach(() => { wrapper = shallow(); diff --git a/frontend/__tests__/components/cluster-settings/keystone-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/keystone-idp-form.spec.tsx index feb34a964d4..de662e76ece 100644 --- a/frontend/__tests__/components/cluster-settings/keystone-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/keystone-idp-form.spec.tsx @@ -11,7 +11,7 @@ import { import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: Keystone', () => { - let wrapper : ShallowWrapper<{}, AddKeystonePageState>; + let wrapper: ShallowWrapper<{}, AddKeystonePageState>; beforeEach(() => { wrapper = shallow(); diff --git a/frontend/__tests__/components/cluster-settings/ldap-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/ldap-idp-form.spec.tsx index 84649cc103e..ce43d606e31 100644 --- a/frontend/__tests__/components/cluster-settings/ldap-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/ldap-idp-form.spec.tsx @@ -4,11 +4,14 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { ListInput } from '../../../public/components/utils'; import { IDPNameInput } from '../../../public/components/cluster-settings/idp-name-input'; import { IDPCAFileInput } from '../../../public/components/cluster-settings/idp-cafile-input'; -import { AddLDAPPage, AddLDAPPageState } from '../../../public/components/cluster-settings/ldap-idp-form'; +import { + AddLDAPPage, + AddLDAPPageState, +} from '../../../public/components/cluster-settings/ldap-idp-form'; import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: LDAP', () => { - let wrapper : ShallowWrapper<{}, AddLDAPPageState>; + let wrapper: ShallowWrapper<{}, AddLDAPPageState>; beforeEach(() => { wrapper = shallow(); @@ -40,9 +43,29 @@ describe('Add Identity Provider: LDAP', () => { }); it('should prefill ldap attribute list input default values', () => { - expect(wrapper.find(ListInput).at(0).props().initialValues).toEqual(['dn']); - expect(wrapper.find(ListInput).at(1).props().initialValues).toEqual(['uid']); - expect(wrapper.find(ListInput).at(2).props().initialValues).toEqual(['cn']); - expect(wrapper.find(ListInput).at(3).props().initialValues).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(0) + .props().initialValues, + ).toEqual(['dn']); + expect( + wrapper + .find(ListInput) + .at(1) + .props().initialValues, + ).toEqual(['uid']); + expect( + wrapper + .find(ListInput) + .at(2) + .props().initialValues, + ).toEqual(['cn']); + expect( + wrapper + .find(ListInput) + .at(3) + .props().initialValues, + ).toEqual(undefined); }); }); diff --git a/frontend/__tests__/components/cluster-settings/openid-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/openid-idp-form.spec.tsx index 5df0e8f268d..b5f65f4b4e7 100644 --- a/frontend/__tests__/components/cluster-settings/openid-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/openid-idp-form.spec.tsx @@ -4,11 +4,14 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { ListInput } from '../../../public/components/utils'; import { IDPNameInput } from '../../../public/components/cluster-settings/idp-name-input'; import { IDPCAFileInput } from '../../../public/components/cluster-settings/idp-cafile-input'; -import { AddOpenIDPage, AddOpenIDIDPPageState } from '../../../public/components/cluster-settings/openid-idp-form'; +import { + AddOpenIDPage, + AddOpenIDIDPPageState, +} from '../../../public/components/cluster-settings/openid-idp-form'; import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: OpenID Connect', () => { - let wrapper : ShallowWrapper<{}, AddOpenIDIDPPageState>; + let wrapper: ShallowWrapper<{}, AddOpenIDIDPPageState>; beforeEach(() => { wrapper = shallow(); @@ -40,9 +43,29 @@ describe('Add Identity Provider: OpenID Connect', () => { }); it('should prefill OpenID list input default values', () => { - expect(wrapper.find(ListInput).at(0).props().initialValues).toEqual(['preferred_username']); - expect(wrapper.find(ListInput).at(1).props().initialValues).toEqual(['name']); - expect(wrapper.find(ListInput).at(2).props().initialValues).toEqual(['email']); - expect(wrapper.find(ListInput).at(3).props().initialValues).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(0) + .props().initialValues, + ).toEqual(['preferred_username']); + expect( + wrapper + .find(ListInput) + .at(1) + .props().initialValues, + ).toEqual(['name']); + expect( + wrapper + .find(ListInput) + .at(2) + .props().initialValues, + ).toEqual(['email']); + expect( + wrapper + .find(ListInput) + .at(3) + .props().initialValues, + ).toEqual(undefined); }); }); diff --git a/frontend/__tests__/components/cluster-settings/request-header-idp-form.spec.tsx b/frontend/__tests__/components/cluster-settings/request-header-idp-form.spec.tsx index d18456908c8..e7f5a30db77 100644 --- a/frontend/__tests__/components/cluster-settings/request-header-idp-form.spec.tsx +++ b/frontend/__tests__/components/cluster-settings/request-header-idp-form.spec.tsx @@ -4,11 +4,14 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { ListInput } from '../../../public/components/utils'; import { IDPNameInput } from '../../../public/components/cluster-settings/idp-name-input'; import { IDPCAFileInput } from '../../../public/components/cluster-settings/idp-cafile-input'; -import { AddRequestHeaderPage, AddRequestHeaderPageState } from '../../../public/components/cluster-settings/request-header-idp-form'; +import { + AddRequestHeaderPage, + AddRequestHeaderPageState, +} from '../../../public/components/cluster-settings/request-header-idp-form'; import { controlButtonTest } from './basicauth-idp-form.spec'; describe('Add Identity Provider: Request Header', () => { - let wrapper : ShallowWrapper<{}, AddRequestHeaderPageState>; + let wrapper: ShallowWrapper<{}, AddRequestHeaderPageState>; beforeEach(() => { wrapper = shallow(); @@ -39,10 +42,35 @@ describe('Add Identity Provider: Request Header', () => { }); it('should prefill Request Header more options list input default values as empty', () => { - expect(wrapper.find(ListInput).at(0).props().initialValues).toEqual(undefined); - expect(wrapper.find(ListInput).at(1).props().initialValues).toEqual(undefined); - expect(wrapper.find(ListInput).at(2).props().initialValues).toEqual(undefined); - expect(wrapper.find(ListInput).at(3).props().initialValues).toEqual(undefined); - expect(wrapper.find(ListInput).at(4).props().initialValues).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(0) + .props().initialValues, + ).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(1) + .props().initialValues, + ).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(2) + .props().initialValues, + ).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(3) + .props().initialValues, + ).toEqual(undefined); + expect( + wrapper + .find(ListInput) + .at(4) + .props().initialValues, + ).toEqual(undefined); }); }); diff --git a/frontend/__tests__/components/create-yaml.spec.tsx b/frontend/__tests__/components/create-yaml.spec.tsx index f4677b0616e..a8b800a9ef2 100644 --- a/frontend/__tests__/components/create-yaml.spec.tsx +++ b/frontend/__tests__/components/create-yaml.spec.tsx @@ -12,12 +12,14 @@ describe(CreateYAML.displayName, () => { let wrapper: ShallowWrapper; beforeEach(() => { - const match = {url: '', params: {ns: 'default', plural: 'pods'}, isExact: true, path: ''}; - wrapper = shallow(); + const match = { url: '', params: { ns: 'default', plural: 'pods' }, isExact: true, path: '' }; + wrapper = shallow( + , + ); }); it('renders loading box if `props.kindsInFlight` is true', () => { - wrapper = wrapper.setProps({kindObj: null, kindsInFlight: true}); + wrapper = wrapper.setProps({ kindObj: null, kindsInFlight: true }); expect(wrapper.find(AsyncComponent).exists()).toBe(false); expect(wrapper.find(LoadingBox).exists()).toBe(true); @@ -28,9 +30,13 @@ describe(CreateYAML.displayName, () => { }); it('uses `props.template` to create sample object if given', () => { - const templateObj = {apiVersion: 'v1', kind: 'Pod', metadata: {name: 'cool-app'}}; - const expectedObj = {apiVersion: 'v1', kind: 'Pod', metadata: {name: 'cool-app', namespace: 'default'}}; - wrapper = wrapper.setProps({template: safeDump(templateObj)}); + const templateObj = { apiVersion: 'v1', kind: 'Pod', metadata: { name: 'cool-app' } }; + const expectedObj = { + apiVersion: 'v1', + kind: 'Pod', + metadata: { name: 'cool-app', namespace: 'default' }, + }; + wrapper = wrapper.setProps({ template: safeDump(templateObj) }); expect(wrapper.find(AsyncComponent).props().obj).toEqual(expectedObj); }); diff --git a/frontend/__tests__/components/environment.spec.tsx b/frontend/__tests__/components/environment.spec.tsx index a8d0699ce79..b8e21a00acf 100644 --- a/frontend/__tests__/components/environment.spec.tsx +++ b/frontend/__tests__/components/environment.spec.tsx @@ -10,23 +10,26 @@ import { DeploymentModel } from '../../public/models'; import * as k8s from '../../public/module/k8s'; describe(EnvironmentPage.name, () => { - - const configMaps={}, secrets = {}, obj = {'metadata': {'namespace': 'test'}}; + const configMaps = {}, + secrets = {}, + obj = { metadata: { namespace: 'test' } }; let wrapper, wrapperRO; let environmentPage, environmentPageRO; describe('When readOnly attribute is "true"', () => { beforeEach(() => { - environmentPageRO=; + environmentPageRO = ( + + ); wrapperRO = shallow(environmentPageRO); - wrapperRO.setState({allowed: true}); + wrapperRO.setState({ allowed: true }); }); it('does not show field level help', () => { @@ -40,14 +43,18 @@ describe(EnvironmentPage.name, () => { describe('When user does not have permission', () => { beforeEach(() => { - spyOn(utils, 'checkAccess').and.callFake(() => Promise.resolve({ status: { allowed: false } })); - environmentPageRO=; + spyOn(utils, 'checkAccess').and.callFake(() => + Promise.resolve({ status: { allowed: false } }), + ); + environmentPageRO = ( + + ); wrapperRO = shallow(environmentPageRO); }); @@ -63,16 +70,20 @@ describe(EnvironmentPage.name, () => { describe('When readOnly attribute is "false"', () => { beforeEach(() => { spyOn(k8s, 'k8sGet').and.callFake(() => Promise.resolve()); - spyOn(utils, 'checkAccess').and.callFake(() => Promise.resolve({ status: { allowed: true } })); - environmentPage=; + spyOn(utils, 'checkAccess').and.callFake(() => + Promise.resolve({ status: { allowed: true } }), + ); + environmentPage = ( + + ); wrapper = shallow(environmentPage); - wrapper.setState({secrets, configMaps}); + wrapper.setState({ secrets, configMaps }); }); it('shows field level help component', () => { @@ -80,51 +91,65 @@ describe(EnvironmentPage.name, () => { }); it('renders save and reload buttons', () => { - expect(wrapper.find({type: 'submit', variant: 'primary'}).childAt(0).text()).toEqual('Save'); - expect(wrapper.find({type: 'button', variant: 'secondary'}).childAt(0).text()).toEqual('Reload'); + expect( + wrapper + .find({ type: 'submit', variant: 'primary' }) + .childAt(0) + .text(), + ).toEqual('Save'); + expect( + wrapper + .find({ type: 'button', variant: 'secondary' }) + .childAt(0) + .text(), + ).toEqual('Reload'); }); }); describe('When page has error messages or alerts', () => { beforeEach(() => { - environmentPage=; + environmentPage = ( + + ); wrapper = shallow(environmentPage); - wrapper.setState({secrets, configMaps, allowed: true}); + wrapper.setState({ secrets, configMaps, allowed: true }); }); it('renders error message when error in state', () => { - wrapper.setState({errorMessage: 'errorMessage'}); + wrapper.setState({ errorMessage: 'errorMessage' }); expect(wrapper.find('.environment-buttons Alert [variant="danger"]')); }); it('renders error message when data is stale', () => { - wrapper.setState({stale: true}); + wrapper.setState({ stale: true }); expect(wrapper.find('.environment-buttons Alert [variant="info"]')); }); it('renders success message when data is updated successfully', () => { - wrapper.setState({success: 'success'}); + wrapper.setState({ success: 'success' }); expect(wrapper.find('.environment-buttons Alert [variant="success"]')); }); }); describe('When page does not have error messages or alerts', () => { beforeEach(() => { - environmentPage=; + environmentPage = ( + + ); wrapper = shallow(environmentPage); - wrapper.setState({secrets, configMaps}); + wrapper.setState({ secrets, configMaps }); }); it('does not render error message when error not in state', () => { diff --git a/frontend/__tests__/components/factory/details.spec.tsx b/frontend/__tests__/components/factory/details.spec.tsx index e0aad9d33f1..88e559114b0 100644 --- a/frontend/__tests__/components/factory/details.spec.tsx +++ b/frontend/__tests__/components/factory/details.spec.tsx @@ -10,9 +10,19 @@ describe(DetailsPage.displayName, () => { let wrapper: ShallowWrapper; beforeEach(() => { - const match = {params: {ns: 'default'}, isExact: true, path: '', url: ''}; + const match = { params: { ns: 'default' }, isExact: true, path: '', url: '' }; - wrapper = shallow().childAt(0).shallow(); + wrapper = shallow( + , + ) + .childAt(0) + .shallow(); }); it('renders a `Firehose` using the given props', () => { @@ -26,14 +36,16 @@ describe(DetailsPage.displayName, () => { }); it('adds extra resources to `Firehose` if provided in props', () => { - const resources = [{ - kind: referenceForModel(ConfigMapModel), - name: 'test-configmap', - namespace: 'kube-system', - isList: false, - prop: 'configMap', - }]; - wrapper = wrapper.setProps({resources}); + const resources = [ + { + kind: referenceForModel(ConfigMapModel), + name: 'test-configmap', + namespace: 'kube-system', + isList: false, + prop: 'configMap', + }, + ]; + wrapper = wrapper.setProps({ resources }); expect(wrapper.find(Firehose).props().resources.length).toEqual(resources.length + 1); resources.forEach((resource, i) => { diff --git a/frontend/__tests__/components/factory/list-page.spec.tsx b/frontend/__tests__/components/factory/list-page.spec.tsx index 8870ba40aa4..64b0d31b380 100644 --- a/frontend/__tests__/components/factory/list-page.spec.tsx +++ b/frontend/__tests__/components/factory/list-page.spec.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { TextFilter, ListPageWrapper_, FireMan_, MultiListPage } from '../../../public/components/factory/list-page'; +import { + TextFilter, + ListPageWrapper_, + FireMan_, + MultiListPage, +} from '../../../public/components/factory/list-page'; import { Firehose, PageHeading } from '../../../public/components/utils'; import { CheckBoxes } from '../../../public/components/row-filter'; @@ -32,7 +37,7 @@ describe(FireMan_.displayName, () => { let wrapper: ShallowWrapper; beforeEach(() => { - const resources = [{kind: 'Node'}]; + const resources = [{ kind: 'Node' }]; wrapper = shallow(); }); @@ -40,7 +45,7 @@ describe(FireMan_.displayName, () => { expect(wrapper.find(PageHeading).exists()).toBe(false); const title = 'My pods'; - wrapper.setProps({title}); + wrapper.setProps({ title }); expect(wrapper.find(PageHeading).props().title).toEqual(title); }); @@ -48,10 +53,17 @@ describe(FireMan_.displayName, () => { it('renders create button if given `canCreate` true', () => { expect(wrapper.find('button#yaml-create').exists()).toBe(false); - const createProps = {foo: 'bar'}; - const button = wrapper.setProps({canCreate: true, createProps, createButtonText: 'Create Me!'}).find('#yaml-create'); + const createProps = { foo: 'bar' }; + const button = wrapper + .setProps({ canCreate: true, createProps, createButtonText: 'Create Me!' }) + .find('#yaml-create'); - expect(wrapper.find('#yaml-create').childAt(0).text()).toEqual('Create Me!'); + expect( + wrapper + .find('#yaml-create') + .childAt(0) + .text(), + ).toEqual('Create Me!'); Object.keys(createProps).forEach((key) => { expect(createProps[key] === button.props()[key]).toBe(true); @@ -59,13 +71,21 @@ describe(FireMan_.displayName, () => { }); it('renders expand button if given `canExpand` true', () => { - wrapper.setProps({canExpand: true}); - expect(wrapper.find('.co-m-pane__filter-bar-group').not('.co-m-pane__filter-bar-group--filter').childAt(0).dive().find('.compaction-btn').exists()).toBe(true); + wrapper.setProps({ canExpand: true }); + expect( + wrapper + .find('.co-m-pane__filter-bar-group') + .not('.co-m-pane__filter-bar-group--filter') + .childAt(0) + .dive() + .find('.compaction-btn') + .exists(), + ).toBe(true); }); it('renders `TextFilter` with given props', () => { const filterLabel = 'My filter'; - wrapper.setProps({filterLabel}); + wrapper.setProps({ filterLabel }); const filter = wrapper.find(TextFilter); expect(filter.props().label).toEqual(filterLabel); @@ -73,26 +93,31 @@ describe(FireMan_.displayName, () => { }); describe(ListPageWrapper_.displayName, () => { - const data: any[] = [ - {kind: 'Pod'}, - {kind: 'Pod'}, - {kind: 'Node'}, - ]; + const data: any[] = [{ kind: 'Pod' }, { kind: 'Pod' }, { kind: 'Node' }]; const flatten = () => data; const ListComponent = () =>
; - const wrapper: ShallowWrapper = shallow(); + const wrapper: ShallowWrapper = shallow( + , + ); it('renders row filters if given `rowFilters`', () => { - const rowFilters = [{ - type: 'app-type', - selected: ['database'], - reducer: (item) => item.kind, - items: [ - {id: 'database', title: 'Databases'}, - {id: 'loadbalancer', title: 'Load Balancers'}, - ], - }]; - wrapper.setProps({rowFilters}); + const rowFilters = [ + { + type: 'app-type', + selected: ['database'], + reducer: (item) => item.kind, + items: [ + { id: 'database', title: 'Databases' }, + { id: 'loadbalancer', title: 'Load Balancers' }, + ], + }, + ]; + wrapper.setProps({ rowFilters }); const checkboxes = wrapper.find(CheckBoxes) as any; expect(checkboxes.length).toEqual(rowFilters.length); @@ -111,10 +136,17 @@ describe(ListPageWrapper_.displayName, () => { describe(MultiListPage.displayName, () => { const ListComponent = () =>
; - const wrapper: ShallowWrapper = shallow(); + const wrapper: ShallowWrapper = shallow( + , + ); it('renders a `Firehose` wrapped `ListPageWrapper_`', () => { expect(wrapper.find(Firehose).exists()).toBe(true); diff --git a/frontend/__tests__/components/graphs/area.spec.tsx b/frontend/__tests__/components/graphs/area.spec.tsx index 9bcc498b4d3..a2a4fcffb42 100644 --- a/frontend/__tests__/components/graphs/area.spec.tsx +++ b/frontend/__tests__/components/graphs/area.spec.tsx @@ -5,7 +5,10 @@ import { Chart, ChartArea, ChartAxis } from '@patternfly/react-charts'; import { AreaChart } from '@console/internal/components/graphs/area'; import { GraphEmpty } from '@console/internal/components/graphs/graph-empty'; import { LoadingBox } from '@console/internal/components/utils'; -import { PrometheusGraph, PrometheusGraphLink } from '@console/internal/components/graphs/prometheus-graph'; +import { + PrometheusGraph, + PrometheusGraphLink, +} from '@console/internal/components/graphs/prometheus-graph'; const MOCK_DATA = [{ x: 1, y: 100 }]; @@ -23,7 +26,9 @@ describe('', () => { }); it('should not render any axes', () => { - const wrapper = shallow(); + const wrapper = shallow( + , + ); expect(wrapper.find(ChartAxis).exists()).toBe(false); }); diff --git a/frontend/__tests__/components/graphs/bar.spec.tsx b/frontend/__tests__/components/graphs/bar.spec.tsx index 063f8fe1308..edf961428bb 100644 --- a/frontend/__tests__/components/graphs/bar.spec.tsx +++ b/frontend/__tests__/components/graphs/bar.spec.tsx @@ -5,7 +5,10 @@ import { ChartBar } from '@patternfly/react-charts'; import { BarChart } from '@console/internal/components/graphs/bar'; import { GraphEmpty } from '@console/internal/components/graphs/graph-empty'; import { LoadingBox } from '@console/internal/components/utils'; -import { PrometheusGraph, PrometheusGraphLink } from '@console/internal/components/graphs/prometheus-graph'; +import { + PrometheusGraph, + PrometheusGraphLink, +} from '@console/internal/components/graphs/prometheus-graph'; const MOCK_DATA = [{ x: 1, y: 100 }]; diff --git a/frontend/__tests__/components/graphs/gauge.spec.tsx b/frontend/__tests__/components/graphs/gauge.spec.tsx index b05c77e5a03..46145a21ef7 100644 --- a/frontend/__tests__/components/graphs/gauge.spec.tsx +++ b/frontend/__tests__/components/graphs/gauge.spec.tsx @@ -3,11 +3,13 @@ import { shallow } from 'enzyme'; import { ChartDonutThreshold, ChartDonutUtilization } from '@patternfly/react-charts'; import { GaugeChart } from '@console/internal/components/graphs/gauge'; -import { PrometheusGraph, PrometheusGraphLink } from '@console/internal/components/graphs/prometheus-graph'; +import { + PrometheusGraph, + PrometheusGraphLink, +} from '@console/internal/components/graphs/prometheus-graph'; const MOCK_DATA = { x: 'test', y: 100 }; - describe('', () => { let wrapper; beforeEach(() => { @@ -24,13 +26,12 @@ describe('', () => { }); it('should show an error state', () => { - wrapper.setProps({error: 'Error Message'}); + wrapper.setProps({ error: 'Error Message' }); expect(wrapper.find(ChartDonutUtilization).props().title).toBe('Error Message'); - }); it('should show a loading state', () => { - wrapper.setProps({loading: true}); + wrapper.setProps({ loading: true }); expect(wrapper.find(ChartDonutUtilization).props().title).toBe('Loading'); }); }); diff --git a/frontend/__tests__/components/graphs/helpers.spec.ts b/frontend/__tests__/components/graphs/helpers.spec.ts index 8bc9975c53a..e5afc86d5c4 100644 --- a/frontend/__tests__/components/graphs/helpers.spec.ts +++ b/frontend/__tests__/components/graphs/helpers.spec.ts @@ -4,11 +4,16 @@ import { getPrometheusURL, PrometheusEndpoint } from '@console/internal/componen describe('getPrometheusURL()', () => { it('should build a Prometheus /api/v1/query URL', () => { - const url = new URL(getPrometheusURL({ - namespace: 'test-namespace', - endpoint: PrometheusEndpoint.QUERY, - query: 'test-query', - }, 'https://mock.prometheus.com')); + const url = new URL( + getPrometheusURL( + { + namespace: 'test-namespace', + endpoint: PrometheusEndpoint.QUERY, + query: 'test-query', + }, + 'https://mock.prometheus.com', + ), + ); // Check that url contains all expected params, in no particular order expect(url.pathname).toBe('/api/v1/query'); @@ -23,15 +28,20 @@ describe('getPrometheusURL()', () => { }); it('should build a Prometheus /api/v1/query_range URL', () => { - const url = new URL(getPrometheusURL({ - endpoint: PrometheusEndpoint.QUERY_RANGE, - endTime: 50000, - namespace: 'test-namespace', - query: 'test-query', - samples: 10, - timeout: '5s', - timespan: 10000, - }, 'https://mock.prometheus.com')); + const url = new URL( + getPrometheusURL( + { + endpoint: PrometheusEndpoint.QUERY_RANGE, + endTime: 50000, + namespace: 'test-namespace', + query: 'test-query', + samples: 10, + timeout: '5s', + timespan: 10000, + }, + 'https://mock.prometheus.com', + ), + ); // Check that url contains all expected params, in no particular order expect(url.pathname).toBe('/api/v1/query_range'); @@ -44,11 +54,16 @@ describe('getPrometheusURL()', () => { }); it('should build a Prometheus /api/v1/label URL', () => { - const url = new URL(getPrometheusURL({ - endpoint: PrometheusEndpoint.LABEL, - namespace: 'test-namespace', - query: 'test-query', - }, 'https://mock.prometheus.com')); + const url = new URL( + getPrometheusURL( + { + endpoint: PrometheusEndpoint.LABEL, + namespace: 'test-namespace', + query: 'test-query', + }, + 'https://mock.prometheus.com', + ), + ); // Check that url contains all expected params, in no particular order expect(url.pathname).toBe('/api/v1/label'); diff --git a/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx b/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx index 3b0516d2862..7c86384f8ba 100644 --- a/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx +++ b/frontend/__tests__/components/graphs/prometheus-graph.spec.tsx @@ -1,7 +1,11 @@ import * as React from 'react'; import { mount, shallow } from 'enzyme'; -import { PrometheusGraph, PrometheusGraphLink, getPrometheusExpressionBrowserURL } from '@console/internal/components/graphs/prometheus-graph'; +import { + PrometheusGraph, + PrometheusGraphLink, + getPrometheusExpressionBrowserURL, +} from '@console/internal/components/graphs/prometheus-graph'; import store from '@console/internal/redux'; import { Provider } from 'react-redux'; @@ -20,23 +24,34 @@ describe('', () => { const wrapper = shallow(); expect(wrapper.find('div.graph-wrapper').exists()).toBe(true); expect(wrapper.find('div.test-class').exists()).toBe(false); - wrapper.setProps({className: 'test-class'}); + wrapper.setProps({ className: 'test-class' }); expect(wrapper.find('div.test-class').exists()).toBe(true); - }); }); describe('', () => { it('should render an anchor element', () => { // Need full mount with redux store since this is a redux-connected compoenent - const wrapper = mount(

); + const wrapper = mount( + + +

+ + , + ); expect(wrapper.find('a').exists()).toBe(true); expect(wrapper.find('p.test-class').exists()).toBe(true); }); it('should not render an anchor element', () => { // Need full mount with redux store since this is a redux-connected compoenent - const wrapper = mount(

); + const wrapper = mount( + + +

+ + , + ); expect(wrapper.find('a').exists()).toBe(false); expect(wrapper.find('p.test-class').exists()).toBe(true); }); @@ -47,5 +62,7 @@ describe('getPrometheusExpressionBrowserURL()', () => { 'prometheus-k8s': 'https://mock.prometheus.url', }; const url = getPrometheusExpressionBrowserURL(urls, ['test-query-1', 'test-query-2']); - expect(url).toBe('https://mock.prometheus.url/graph?g0.range_input=1h&g0.expr=test-query-1&g0.tab=0&g1.range_input=1h&g1.expr=test-query-2&g1.tab=0'); + expect(url).toBe( + 'https://mock.prometheus.url/graph?g0.range_input=1h&g0.expr=test-query-1&g0.tab=0&g1.range_input=1h&g1.expr=test-query-2&g1.tab=0', + ); }); diff --git a/frontend/__tests__/components/graphs/utils.spec.ts b/frontend/__tests__/components/graphs/utils.spec.ts index d0037e87405..0a06bccb69a 100644 --- a/frontend/__tests__/components/graphs/utils.spec.ts +++ b/frontend/__tests__/components/graphs/utils.spec.ts @@ -1,4 +1,7 @@ -import { getRangeVectorStats, getInstantVectorStats } from '@console/internal/components/graphs/utils'; +import { + getRangeVectorStats, + getInstantVectorStats, +} from '@console/internal/components/graphs/utils'; import { PrometheusResponse } from '@console/internal/components/graphs'; import { Humanize } from '@console/internal/components/utils'; @@ -9,10 +12,7 @@ const RANGE_VECTOR_RESPONSE: PrometheusResponse = { result: [ { metric: { testMetric: 'test-0' }, - values: [ - [1, '123.4'], - [2, '5678.9'], - ], + values: [[1, '123.4'], [2, '5678.9']], }, ], }, @@ -51,7 +51,11 @@ describe('getInstantVectorStats()', () => { value: parseFloat(v), unit: 'units', }); - const [{x, y, label}] = getInstantVectorStats(INSTANT_VECTOR_RESPONSE, 'testMetric', humanize); + const [{ x, y, label }] = getInstantVectorStats( + INSTANT_VECTOR_RESPONSE, + 'testMetric', + humanize, + ); expect(x).toBe('test-0'); expect(y).toBe(123.45); expect(label).toBe('123.45 units'); diff --git a/frontend/__tests__/components/limitrange.spec.tsx b/frontend/__tests__/components/limitrange.spec.tsx index fd49497d0bd..63fd6d06d78 100644 --- a/frontend/__tests__/components/limitrange.spec.tsx +++ b/frontend/__tests__/components/limitrange.spec.tsx @@ -1,6 +1,11 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { LimitRangeTableHeader, LimitRangeTableRow, LimitRangeDetailsRowProps, LimitRangeDetailsRow } from '../../public/components/limit-range'; +import { + LimitRangeTableHeader, + LimitRangeTableRow, + LimitRangeDetailsRowProps, + LimitRangeDetailsRow, +} from '../../public/components/limit-range'; describe(LimitRangeTableHeader.displayName, () => { it('returns column header definition for resource', () => { @@ -11,11 +16,17 @@ describe(LimitRangeTableHeader.displayName, () => { describe(LimitRangeTableRow.displayName, () => { let wrapper: ShallowWrapper; const limitContent = { - max:'', min:'1', default:'', defaultRequest:'', maxLimitRequestRatio:'', + max: '', + min: '1', + default: '', + defaultRequest: '', + maxLimitRequestRatio: '', }; beforeEach(() => { - wrapper = shallow(); + wrapper = shallow( + , + ); }); it('renders column for limit type', () => { @@ -52,5 +63,4 @@ describe(LimitRangeTableRow.displayName, () => { const col = wrapper.childAt(6); expect(col.text()).toBe('-'); }); - }); diff --git a/frontend/__tests__/components/modals/configure-update-strategy-modal.spec.tsx b/frontend/__tests__/components/modals/configure-update-strategy-modal.spec.tsx index aba3e03edd3..e93cd93c21a 100644 --- a/frontend/__tests__/components/modals/configure-update-strategy-modal.spec.tsx +++ b/frontend/__tests__/components/modals/configure-update-strategy-modal.spec.tsx @@ -2,7 +2,10 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; import Spy = jasmine.Spy; -import { ConfigureUpdateStrategy, ConfigureUpdateStrategyProps } from '@console/internal/components/modals/configure-update-strategy-modal'; +import { + ConfigureUpdateStrategy, + ConfigureUpdateStrategyProps, +} from '@console/internal/components/modals/configure-update-strategy-modal'; import { RadioInput } from '@console/internal/components/radio'; describe(ConfigureUpdateStrategy.displayName, () => { @@ -16,19 +19,48 @@ describe(ConfigureUpdateStrategy.displayName, () => { onChangeMaxSurge = jasmine.createSpy('onChangeMaxSurge'); onChangeMaxUnavailable = jasmine.createSpy('onChangeMaxUnavailable'); - wrapper = shallow(); + wrapper = shallow( + , + ); }); it('renders two choices for different update strategy types', () => { - expect(wrapper.find(RadioInput).at(0).props().value).toEqual('RollingUpdate'); - expect(wrapper.find(RadioInput).at(1).props().value).toEqual('Recreate'); - expect(wrapper.find(RadioInput).at(1).props().checked).toBe(true); + expect( + wrapper + .find(RadioInput) + .at(0) + .props().value, + ).toEqual('RollingUpdate'); + expect( + wrapper + .find(RadioInput) + .at(1) + .props().value, + ).toEqual('Recreate'); + expect( + wrapper + .find(RadioInput) + .at(1) + .props().checked, + ).toBe(true); }); it('is a controlled component', () => { - wrapper.find(RadioInput).at(0).dive().find('input[type="radio"]').simulate('change', {target: {value: 'RollingUpdate'}}); - wrapper.find('#input-max-unavailable').simulate('change', {target: {value: '25%'}}); - wrapper.find('#input-max-surge').simulate('change', {target: {value: '50%'}}); + wrapper + .find(RadioInput) + .at(0) + .dive() + .find('input[type="radio"]') + .simulate('change', { target: { value: 'RollingUpdate' } }); + wrapper.find('#input-max-unavailable').simulate('change', { target: { value: '25%' } }); + wrapper.find('#input-max-surge').simulate('change', { target: { value: '50%' } }); expect(onChangeStrategyType.calls.argsFor(0)[0]).toEqual('RollingUpdate'); expect(onChangeMaxUnavailable.calls.argsFor(0)[0]).toEqual('25%'); diff --git a/frontend/__tests__/components/namespace.spec.tsx b/frontend/__tests__/components/namespace.spec.tsx index f836e58f808..fe0e7c7d6cc 100644 --- a/frontend/__tests__/components/namespace.spec.tsx +++ b/frontend/__tests__/components/namespace.spec.tsx @@ -11,28 +11,35 @@ import { SecretModel } from '../../public/models'; describe(PullSecret.displayName, () => { let wrapper: ReactWrapper; - const spyAndExpect = (spy: Spy) => (returnValue: any) => new Promise(resolve => spy.and.callFake((...args) => { - resolve(args); - return returnValue; - })); + const spyAndExpect = (spy: Spy) => (returnValue: any) => + new Promise((resolve) => + spy.and.callFake((...args) => { + resolve(args); + return returnValue; + }), + ); it('renders link to open modal once pull secrets are loaded', (done) => { - spyAndExpect(spyOn(k8s, 'k8sGet'))(Promise.resolve({items: []})).then(([model, name, namespace, options]) => { - expect(model).toEqual(SecretModel); - expect(name).toBe(null); - expect(namespace).toEqual(testNamespace.metadata.name); - expect(options).toEqual({queryParams: {fieldSelector: 'type=kubernetes.io/dockerconfigjson'}}); - }).then(() => { - wrapper.update(); - expect(wrapper.find('button').exists()).toBe(true); - done(); - }); + spyAndExpect(spyOn(k8s, 'k8sGet'))(Promise.resolve({ items: [] })) + .then(([model, name, namespace, options]) => { + expect(model).toEqual(SecretModel); + expect(name).toBe(null); + expect(namespace).toEqual(testNamespace.metadata.name); + expect(options).toEqual({ + queryParams: { fieldSelector: 'type=kubernetes.io/dockerconfigjson' }, + }); + }) + .then(() => { + wrapper.update(); + expect(wrapper.find('button').exists()).toBe(true); + done(); + }); wrapper = mount(); }); it('does not render link if still loading', () => { - spyOn(k8s, 'k8sGet').and.returnValue(Promise.resolve({items: []})); + spyOn(k8s, 'k8sGet').and.returnValue(Promise.resolve({ items: [] })); wrapper = mount(); expect(wrapper.find(LoadingInline).exists()).toBe(true); diff --git a/frontend/__tests__/components/pod.spec.tsx b/frontend/__tests__/components/pod.spec.tsx index e29d0d4656c..08a3ae39465 100644 --- a/frontend/__tests__/components/pod.spec.tsx +++ b/frontend/__tests__/components/pod.spec.tsx @@ -18,8 +18,8 @@ describe('Readiness', () => { it('renders pod readiness with error styling if given pod is in invalid readiness state', () => { const invalidReadinessStates = new Set(['Unschedulable', 'PodScheduled']); - invalidReadinessStates.forEach(state => { - pod.status.conditions = [{type: state, status: 'False'}]; + invalidReadinessStates.forEach((state) => { + pod.status.conditions = [{ type: state, status: 'False' }]; const wrapper = shallow(); expect(wrapper.hasClass('co-error')).toBe(true); @@ -28,8 +28,8 @@ describe('Readiness', () => { it('renders pod readiness without error styling if readiness is valid state', () => { const validReadinessStates = new Set(['Ready', 'PodCompleted']); - validReadinessStates.forEach(state => { - pod.status.conditions = [{type: state, status: 'False'}]; + validReadinessStates.forEach((state) => { + pod.status.conditions = [{ type: state, status: 'False' }]; const wrapper = shallow(); expect(wrapper.hasClass('co-error')).toBe(false); @@ -41,7 +41,17 @@ describe(PodsDetailsPage.displayName, () => { let wrapper: ShallowWrapper; beforeEach(() => { - wrapper = shallow(); + wrapper = shallow( + , + ); }); it('renders `DetailsPage` with correct props', () => { @@ -63,14 +73,14 @@ describe('ContainerRow', () => { conditions: [], containerStatuses: [ { - 'name': 'hello-openshift', - 'state': { - 'running': { - 'startedAt': {startTime}, - 'finishedAt': {finishTime}, + name: 'hello-openshift', + state: { + running: { + startedAt: { startTime }, + finishedAt: { finishTime }, }, }, - 'restartCount': 10, + restartCount: 10, }, ], }, @@ -83,7 +93,12 @@ describe('ContainerRow', () => { }); it('renders the container link', () => { - expect(wrapper.find('ContainerLink').find({name: 'hello-openshift'}).exists()).toBe(true); + expect( + wrapper + .find('ContainerLink') + .find({ name: 'hello-openshift' }) + .exists(), + ).toBe(true); }); it('renders the container image', () => { @@ -91,7 +106,12 @@ describe('ContainerRow', () => { }); it('renders the container state', () => { - expect(wrapper.childAt(2).find({status: 'Running'}).exists()).toBe(true); + expect( + wrapper + .childAt(2) + .find({ status: 'Running' }) + .exists(), + ).toBe(true); }); it('renders the container restart times', () => { @@ -99,11 +119,21 @@ describe('ContainerRow', () => { }); it('renders the container started time', () => { - expect(wrapper.childAt(4).find({timestamp: {startTime}}).exists()).toBe(true); + expect( + wrapper + .childAt(4) + .find({ timestamp: { startTime } }) + .exists(), + ).toBe(true); }); it('renders the container finished time', () => { - expect(wrapper.childAt(5).find({timestamp: {finishTime}}).exists()).toBe(true); + expect( + wrapper + .childAt(5) + .find({ timestamp: { finishTime } }) + .exists(), + ).toBe(true); }); it('renders the container exit code', () => { diff --git a/frontend/__tests__/components/resource-pages.spec.tsx b/frontend/__tests__/components/resource-pages.spec.tsx index 32f833742e3..34a8ae9d19e 100644 --- a/frontend/__tests__/components/resource-pages.spec.tsx +++ b/frontend/__tests__/components/resource-pages.spec.tsx @@ -2,28 +2,25 @@ import { resourceDetailsPages, resourceListPages } from '../../public/components import { isGroupVersionKind } from '../../public/module/k8s'; describe('resourceDetailsPages', () => { - it('contains a map of promises which resolve to every resource detail view component', (done) => { resourceDetailsPages.forEach((promise, name) => { expect(name.length > 0).toBe(true); expect(isGroupVersionKind(name)).toBe(true); - promise().then(Component => { + promise().then((Component) => { expect(typeof Component).toEqual('function'); done(); }); }); }); - }); describe('resourceListPages', () => { - it('contains map of promises which resolve to every resource list view component', (done) => { resourceListPages.forEach((promise, name) => { expect(isGroupVersionKind(name) || name === 'ClusterServiceVersionResources').toBe(true); - promise().then(Component => { + promise().then((Component) => { expect(typeof Component).toEqual('function'); done(); }); diff --git a/frontend/__tests__/components/resource-quota.spec.tsx b/frontend/__tests__/components/resource-quota.spec.tsx index 2adb8141431..c4615de560e 100644 --- a/frontend/__tests__/components/resource-quota.spec.tsx +++ b/frontend/__tests__/components/resource-quota.spec.tsx @@ -1,6 +1,10 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { ResourceQuotaTableRow, UsageIcon, ResourceUsageRow } from '../../public/components/resource-quota'; +import { + ResourceQuotaTableRow, + UsageIcon, + ResourceUsageRow, +} from '../../public/components/resource-quota'; describe(ResourceQuotaTableRow.displayName, () => { let wrapper: ShallowWrapper; @@ -43,7 +47,7 @@ describe(ResourceQuotaTableRow.displayName, () => { describe('Check quota table columns by ResourceUsageRow', () => { let wrapper: ShallowWrapper; - const quota = { 'status': {'hard': {'limits.cpu' : 2}, 'used': {'limits.cpu' : 1} } }; + const quota = { status: { hard: { 'limits.cpu': 2 }, used: { 'limits.cpu': 1 } } }; beforeEach(() => { wrapper = shallow(); diff --git a/frontend/__tests__/components/route-pages.spec.tsx b/frontend/__tests__/components/route-pages.spec.tsx index 7b32105a525..9db52554c3a 100644 --- a/frontend/__tests__/components/route-pages.spec.tsx +++ b/frontend/__tests__/components/route-pages.spec.tsx @@ -8,27 +8,27 @@ import { K8sResourceKind } from '../../public/module/k8s'; describe(RouteLocation.displayName, () => { it('renders a https link when TLS Settings', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, - 'spec': { - 'host': 'www.example.com', - 'tls': { - 'termination': 'edge', + spec: { + host: 'www.example.com', + tls: { + termination: 'edge', }, - 'wildcardPolicy': 'None', + wildcardPolicy: 'None', }, - 'status': { - 'ingress': [ + status: { + ingress: [ { - 'host': 'www.example.com', - 'conditions': [ + host: 'www.example.com', + conditions: [ { - 'type': 'Admitted', - 'status': 'True', - 'lastTransitionTime': '2018-04-30T16:55:48Z', + type: 'Admitted', + status: 'True', + lastTransitionTime: '2018-04-30T16:55:48Z', }, ], }, @@ -43,24 +43,24 @@ describe(RouteLocation.displayName, () => { it('renders a http link when no TLS Settings', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, - 'spec': { - 'host': 'www.example.com', - 'wildcardPolicy': 'None', + spec: { + host: 'www.example.com', + wildcardPolicy: 'None', }, - 'status': { - 'ingress': [ + status: { + ingress: [ { - 'host': 'www.example.com', - 'conditions': [ + host: 'www.example.com', + conditions: [ { - 'type': 'Admitted', - 'status': 'True', - 'lastTransitionTime': '2018-04-30T16:55:48Z', + type: 'Admitted', + status: 'True', + lastTransitionTime: '2018-04-30T16:55:48Z', }, ], }, @@ -75,25 +75,25 @@ describe(RouteLocation.displayName, () => { it('renders additional path in url', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, - 'spec': { - 'host': 'www.example.com', - 'path': '\\mypath', - 'wildcardPolicy': 'None', + spec: { + host: 'www.example.com', + path: '\\mypath', + wildcardPolicy: 'None', }, - 'status': { - 'ingress': [ + status: { + ingress: [ { - 'host': 'www.example.com', - 'conditions': [ + host: 'www.example.com', + conditions: [ { - 'type': 'Admitted', - 'status': 'True', - 'lastTransitionTime': '2018-04-30T16:55:48Z', + type: 'Admitted', + status: 'True', + lastTransitionTime: '2018-04-30T16:55:48Z', }, ], }, @@ -108,24 +108,24 @@ describe(RouteLocation.displayName, () => { it('renders Subdomain', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, - 'spec': { - 'host': 'www.example.com', - 'wildcardPolicy': 'Subdomain', + spec: { + host: 'www.example.com', + wildcardPolicy: 'Subdomain', }, - 'status': { - 'ingress': [ + status: { + ingress: [ { - 'host': 'www.example.com', - 'conditions': [ + host: 'www.example.com', + conditions: [ { - 'type': 'Admitted', - 'status': 'True', - 'lastTransitionTime': '2018-04-30T16:55:48Z', + type: 'Admitted', + status: 'True', + lastTransitionTime: '2018-04-30T16:55:48Z', }, ], }, @@ -140,24 +140,24 @@ describe(RouteLocation.displayName, () => { it('renders non-admitted label', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, - 'spec': { - 'host': 'www.example.com', - 'wildcardPolicy': 'None', + spec: { + host: 'www.example.com', + wildcardPolicy: 'None', }, - 'status': { - 'ingress': [ + status: { + ingress: [ { - 'host': 'www.example.com', - 'conditions': [ + host: 'www.example.com', + conditions: [ { - 'type': 'Admitted', - 'status': 'False', - 'lastTransitionTime': '2018-04-30T16:55:48Z', + type: 'Admitted', + status: 'False', + lastTransitionTime: '2018-04-30T16:55:48Z', }, ], }, @@ -171,23 +171,22 @@ describe(RouteLocation.displayName, () => { }); }); - describe(RouteStatus.displayName, () => { it('renders Accepted status', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, - 'status': { - 'ingress': [ + status: { + ingress: [ { - 'conditions': [ + conditions: [ { - 'type': 'Admitted', - 'status': 'True', - 'lastTransitionTime': '2018-04-30T16:55:48Z', + type: 'Admitted', + status: 'True', + lastTransitionTime: '2018-04-30T16:55:48Z', }, ], }, @@ -203,19 +202,19 @@ describe(RouteStatus.displayName, () => { it('renders Rejected status', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, - 'status': { - 'ingress': [ + status: { + ingress: [ { - 'conditions': [ + conditions: [ { - 'type': 'Admitted', - 'status': 'False', - 'lastTransitionTime': '2018-04-30T16:55:48Z', + type: 'Admitted', + status: 'False', + lastTransitionTime: '2018-04-30T16:55:48Z', }, ], }, @@ -231,9 +230,9 @@ describe(RouteStatus.displayName, () => { it('renders Pending status', () => { const route: K8sResourceKind = { - 'apiVersion': 'v1', - 'kind': 'Route', - 'metadata': { + apiVersion: 'v1', + kind: 'Route', + metadata: { name: 'example', }, }; diff --git a/frontend/__tests__/components/safety-first.spec.tsx b/frontend/__tests__/components/safety-first.spec.tsx index 78ef440bc9d..0d7266945d9 100644 --- a/frontend/__tests__/components/safety-first.spec.tsx +++ b/frontend/__tests__/components/safety-first.spec.tsx @@ -1,4 +1,3 @@ - /* eslint-env node */ import * as React from 'react'; @@ -26,18 +25,24 @@ describe('When calling setter from `useState()` hook in an unsafe React componen const consoleErrorSpy = spyOn(global.console, 'error').and.callThrough(); let wrapper = mount(); - const loader = () => new Promise(resolve => { - expect(wrapper.text()).toEqual('Loading...'); - wrapper.unmount(); - resolve(); - }); - - wrapper = wrapper.setProps({loader}); + const loader = () => + new Promise((resolve) => { + expect(wrapper.text()).toEqual('Loading...'); + wrapper.unmount(); + resolve(); + }); + + wrapper = wrapper.setProps({ loader }); wrapper.find('button').simulate('click'); // FIXME(alecmerdler): Shouldn't need a `setTimeout` here... setTimeout(() => { - expect(consoleErrorSpy.calls.all().map(call => call.args[0] as string).some(text => text.includes(warning))).toBe(true); + expect( + consoleErrorSpy.calls + .all() + .map((call) => call.args[0] as string) + .some((text) => text.includes(warning)), + ).toBe(true); done(); }, 500); }); @@ -61,35 +66,47 @@ describe('useSafetyFirst', () => { }); it('does not attempt to set React state if unmounted (using hook)', (done) => { - const loader = () => new Promise(resolve => { - expect(wrapper.text()).toEqual('Loading...'); - wrapper.unmount(); - resolve(); - }); - - wrapper = wrapper.setProps({loader}); + const loader = () => + new Promise((resolve) => { + expect(wrapper.text()).toEqual('Loading...'); + wrapper.unmount(); + resolve(); + }); + + wrapper = wrapper.setProps({ loader }); wrapper.find('button').simulate('click'); // FIXME(alecmerdler): Shouldn't need a `setTimeout` here... setTimeout(() => { - expect(consoleErrorSpy.calls.all().map(call => call.args[0] as string).some(text => text.includes(warning))).toBe(false); + expect( + consoleErrorSpy.calls + .all() + .map((call) => call.args[0] as string) + .some((text) => text.includes(warning)), + ).toBe(false); done(); }, 500); }); it('will set React state if mounted (using hook)', (done) => { - const loader = () => new Promise(resolve => { - expect(wrapper.text()).toEqual('Loading...'); - resolve(); - }); + const loader = () => + new Promise((resolve) => { + expect(wrapper.text()).toEqual('Loading...'); + resolve(); + }); - wrapper = wrapper.setProps({loader}); + wrapper = wrapper.setProps({ loader }); wrapper.find('button').simulate('click'); // FIXME(alecmerdler): Shouldn't need a `setTimeout` here... setTimeout(() => { expect(wrapper.text()).toEqual('Loaded'); - expect(consoleErrorSpy.calls.all().map(call => call.args[0] as string).some(text => text.includes(warning))).toBe(false); + expect( + consoleErrorSpy.calls + .all() + .map((call) => call.args[0] as string) + .some((text) => text.includes(warning)), + ).toBe(false); done(); }, 500); }); diff --git a/frontend/__tests__/components/storage-class-form.spec.tsx b/frontend/__tests__/components/storage-class-form.spec.tsx index 3fa1beac93a..b46448a879b 100644 --- a/frontend/__tests__/components/storage-class-form.spec.tsx +++ b/frontend/__tests__/components/storage-class-form.spec.tsx @@ -3,10 +3,16 @@ import { ShallowWrapper, shallow } from 'enzyme'; import Spy = jasmine.Spy; import { Form } from 'patternfly-react'; -import {ConnectedStorageClassForm, StorageClassFormProps, StorageClassFormState} from '../../public/components/storage-class-form'; +import { + ConnectedStorageClassForm, + StorageClassFormProps, + StorageClassFormState, +} from '../../public/components/storage-class-form'; describe(ConnectedStorageClassForm.displayName, () => { - const Component: React.ComponentType = ConnectedStorageClassForm.WrappedComponent as any; + const Component: React.ComponentType< + StorageClassFormProps + > = ConnectedStorageClassForm.WrappedComponent as any; let wrapper: ShallowWrapper; let onClose: Spy; let watchK8sList: Spy; @@ -24,7 +30,8 @@ describe(ConnectedStorageClassForm.displayName, () => { onClose={onClose} watchK8sList={watchK8sList} stopK8sWatch={stopK8sWatch} - k8s={k8s} /> + k8s={k8s} + />, ); }); @@ -37,7 +44,7 @@ describe(ConnectedStorageClassForm.displayName, () => { }); it('renders a dropdown for selecting the reclaim policy', () => { - expect(wrapper.find({title: 'Select Reclaim Policy'}).exists()).toBe(true); + expect(wrapper.find({ title: 'Select Reclaim Policy' }).exists()).toBe(true); }); it('renders a text box for selecting the storage class name', () => { @@ -45,20 +52,20 @@ describe(ConnectedStorageClassForm.displayName, () => { }); it('renders type-specific settings when storage type is set', () => { - expect(wrapper.find({title: 'Select AWS Type'}).exists()).toBe(false); + expect(wrapper.find({ title: 'Select AWS Type' }).exists()).toBe(false); wrapper.setState({ newStorageClass: { ...wrapper.state().newStorageClass, type: 'aws', }, }); - expect(wrapper.find({title: 'Select AWS Type'}).exists()).toBe(true); + expect(wrapper.find({ title: 'Select AWS Type' }).exists()).toBe(true); }); it('renders an error message when storage class creation fails', () => { const errorMsg = 'Storage creation failed'; - expect(wrapper.find({errorMessage: errorMsg}).exists()).toBe(false); - wrapper.setState({error: {message: errorMsg}}); - expect(wrapper.find({errorMessage: errorMsg}).exists()).toBe(true); + expect(wrapper.find({ errorMessage: errorMsg }).exists()).toBe(false); + wrapper.setState({ error: { message: errorMsg } }); + expect(wrapper.find({ errorMessage: errorMsg }).exists()).toBe(true); }); }); diff --git a/frontend/__tests__/components/utils/async.spec.tsx b/frontend/__tests__/components/utils/async.spec.tsx index b63bc419f25..e7a98cb3970 100644 --- a/frontend/__tests__/components/utils/async.spec.tsx +++ b/frontend/__tests__/components/utils/async.spec.tsx @@ -6,7 +6,7 @@ import { AsyncComponent } from '../../../public/components/utils/async'; describe('AsyncComponent', () => { let wrapper: ReactWrapper; const fooId = 'fooId'; - const Foo = (props: {className: string}) =>

; + const Foo = (props: { className: string }) =>
; const loadingBoxSelector = '.cos-status-box'; beforeEach(() => { @@ -14,33 +14,36 @@ describe('AsyncComponent', () => { }); it('calls given loader function', (done) => { - const loader = () => new Promise((resolve) => { - resolve(Foo); - done(); - }); + const loader = () => + new Promise((resolve) => { + resolve(Foo); + done(); + }); wrapper = mount(); }); it('renders `LoadingBox` before `loader` promise resolves', (done) => { - const loader = () => new Promise(() => { - setTimeout(() => { - expect(wrapper.find(loadingBoxSelector).exists()).toBe(true); - done(); - }, 10); - }); + const loader = () => + new Promise(() => { + setTimeout(() => { + expect(wrapper.find(loadingBoxSelector).exists()).toBe(true); + done(); + }, 10); + }); wrapper = mount(); }); it('continues to display `LoadingBox` if `loader` promise is rejected', (done) => { - const loader = () => new Promise((_, reject) => { - reject('epic fail'); - setTimeout(() => { - expect(wrapper.find(loadingBoxSelector).exists()).toBe(true); - done(); - }, 10); - }); + const loader = () => + new Promise((_, reject) => { + reject('epic fail'); + setTimeout(() => { + expect(wrapper.find(loadingBoxSelector).exists()).toBe(true); + done(); + }, 10); + }); wrapper = mount(); }); @@ -49,10 +52,12 @@ describe('AsyncComponent', () => { const start = Date.now(); const end = 1000; - const loader = jasmine.createSpy('loader').and.returnValue(new Promise((_, reject) => { - expect(Date.now() > (start + (100 * Math.pow(loader.calls.count(), 2)))); - reject(null); - })); + const loader = jasmine.createSpy('loader').and.returnValue( + new Promise((_, reject) => { + expect(Date.now() > start + 100 * Math.pow(loader.calls.count(), 2)); + reject(null); + }), + ); wrapper = mount(); setTimeout(() => { @@ -71,50 +76,74 @@ describe('AsyncComponent', () => { }); it('renders component resolved from `loader` promise', (done) => { - const loader = () => new Promise((resolve) => { - resolve(Foo); - setTimeout(() => { - expect(wrapper.update().find(`#${fooId}`).exists()).toBe(true); - done(); - }, 10); - }); + const loader = () => + new Promise((resolve) => { + resolve(Foo); + setTimeout(() => { + expect( + wrapper + .update() + .find(`#${fooId}`) + .exists(), + ).toBe(true); + done(); + }, 10); + }); wrapper = mount(); }); it('passes given props to rendered component', (done) => { const className = 'col-md-1'; - const loader = () => new Promise((resolve) => { - resolve(Foo); - setTimeout(() => { - expect(wrapper.update().find(`#${fooId}`).props().className).toEqual(className); - done(); - }, 10); - }); + const loader = () => + new Promise((resolve) => { + resolve(Foo); + setTimeout(() => { + expect( + wrapper + .update() + .find(`#${fooId}`) + .props().className, + ).toEqual(className); + done(); + }, 10); + }); wrapper = mount(); }); it('renders new component if `props.loader` changes', (done) => { const barId = 'barId'; - const Bar = (props: {className: string}) =>
; - - const loader1 = () => new Promise((resolve) => { - resolve(Foo); - setTimeout(() => { - expect(wrapper.update().find(`#${fooId}`).exists()).toBe(true); - }, 10); - }); - - const loader2 = () => new Promise((resolve) => { - resolve(Bar); - setTimeout(() => { - expect(wrapper.update().find(`#${barId}`).exists()).toBe(true); - done(); - }, 10); - }); + const Bar = (props: { className: string }) =>
; + + const loader1 = () => + new Promise((resolve) => { + resolve(Foo); + setTimeout(() => { + expect( + wrapper + .update() + .find(`#${fooId}`) + .exists(), + ).toBe(true); + }, 10); + }); + + const loader2 = () => + new Promise((resolve) => { + resolve(Bar); + setTimeout(() => { + expect( + wrapper + .update() + .find(`#${barId}`) + .exists(), + ).toBe(true); + done(); + }, 10); + }); wrapper = mount(); - wrapper = wrapper.setProps({loader: loader2}); + wrapper = wrapper.setProps({ loader: loader2 }); }); }); diff --git a/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx b/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx index 98b3d3233cc..5c4ac28de07 100644 --- a/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx +++ b/frontend/__tests__/components/utils/copy-to-clipboard.spec.tsx @@ -3,14 +3,20 @@ import { shallow, ShallowWrapper } from 'enzyme'; import { Tooltip } from '@patternfly/react-core'; import { CopyToClipboard as CTC } from 'react-copy-to-clipboard'; -import { CopyToClipboard, CopyToClipboardProps } from '../../../public/components/utils/copy-to-clipboard'; +import { + CopyToClipboard, + CopyToClipboardProps, +} from '../../../public/components/utils/copy-to-clipboard'; describe(CopyToClipboard.displayName, () => { let wrapper: ShallowWrapper; it('displays "Copied" message when clicked', () => { wrapper = shallow(); - wrapper.find(CTC).props().onCopy(); + wrapper + .find(CTC) + .props() + .onCopy(); wrapper.update(); expect(wrapper.find(Tooltip).props().content[0].props.children).toEqual('Copied'); @@ -18,7 +24,10 @@ describe(CopyToClipboard.displayName, () => { it('dismisses "Copied" message when mouse moves over button', () => { wrapper = shallow(); - wrapper.find(CTC).props().onCopy(); + wrapper + .find(CTC) + .props() + .onCopy(); wrapper.update(); wrapper.find('.co-copy-to-clipboard__btn').simulate('mouseenter'); diff --git a/frontend/__tests__/components/utils/datetime.spec.ts b/frontend/__tests__/components/utils/datetime.spec.ts index f1565ad851e..b25cce80a41 100644 --- a/frontend/__tests__/components/utils/datetime.spec.ts +++ b/frontend/__tests__/components/utils/datetime.spec.ts @@ -1,11 +1,25 @@ -import { fromNow, isValid, formatDuration, formatPrometheusDuration, parsePrometheusDuration } from '../../../public/components/utils/datetime'; +import { + fromNow, + isValid, + formatDuration, + formatPrometheusDuration, + parsePrometheusDuration, +} from '../../../public/components/utils/datetime'; describe('fromNow', () => { it('prints past dates correctly', () => { - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:00:02'))).toEqual('a few seconds ago'); - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:01:00'))).toEqual('a minute ago'); - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:51:00'))).toEqual('an hour ago'); - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 12:45:00'))).toEqual('13 hours ago'); + expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:00:02'))).toEqual( + 'a few seconds ago', + ); + expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:01:00'))).toEqual( + 'a minute ago', + ); + expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:51:00'))).toEqual( + 'an hour ago', + ); + expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 12:45:00'))).toEqual( + '13 hours ago', + ); expect(fromNow(new Date('Jan 01 1970'), new Date('Jan 02 1970'))).toEqual('a day ago'); expect(fromNow(new Date('Jan 01 1970'), new Date('Jan 09 1970'))).toEqual('8 days ago'); expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1970'))).toEqual('a month ago'); @@ -14,24 +28,60 @@ describe('fromNow', () => { expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1973'))).toEqual('3 years ago'); }); - it('prints past dates with no prefixes/suffixes correctly', ()=> { - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:00:02'), {omitSuffix: true})).toEqual('few seconds'); - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:01:00'), {omitSuffix: true})).toEqual('minute'); - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:51:00'), {omitSuffix: true})).toEqual('hour'); - expect(fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 12:45:00'), {omitSuffix: true})).toEqual('13 hours'); - expect(fromNow(new Date('Jan 01 1970'), new Date('Jan 02 1970'), {omitSuffix: true})).toEqual('day'); - expect(fromNow(new Date('Jan 01 1970'), new Date('Jan 09 1970'), {omitSuffix: true})).toEqual('8 days'); - expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1970'), {omitSuffix: true})).toEqual('month'); - expect(fromNow(new Date('Jan 01 1970'), new Date('Mar 02 1970'), {omitSuffix: true})).toEqual('2 months'); - expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1971'), {omitSuffix: true})).toEqual('year'); - expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1973'), {omitSuffix: true})).toEqual('3 years'); + it('prints past dates with no prefixes/suffixes correctly', () => { + expect( + fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:00:02'), { + omitSuffix: true, + }), + ).toEqual('few seconds'); + expect( + fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:01:00'), { + omitSuffix: true, + }), + ).toEqual('minute'); + expect( + fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 00:51:00'), { + omitSuffix: true, + }), + ).toEqual('hour'); + expect( + fromNow(new Date('Jan 01 1970 00:00:00'), new Date('Jan 01 1970 12:45:00'), { + omitSuffix: true, + }), + ).toEqual('13 hours'); + expect(fromNow(new Date('Jan 01 1970'), new Date('Jan 02 1970'), { omitSuffix: true })).toEqual( + 'day', + ); + expect(fromNow(new Date('Jan 01 1970'), new Date('Jan 09 1970'), { omitSuffix: true })).toEqual( + '8 days', + ); + expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1970'), { omitSuffix: true })).toEqual( + 'month', + ); + expect(fromNow(new Date('Jan 01 1970'), new Date('Mar 02 1970'), { omitSuffix: true })).toEqual( + '2 months', + ); + expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1971'), { omitSuffix: true })).toEqual( + 'year', + ); + expect(fromNow(new Date('Jan 01 1970'), new Date('Feb 02 1973'), { omitSuffix: true })).toEqual( + '3 years', + ); }); it('prints future dates correctly', () => { - expect(fromNow(new Date('Jan 01 1970 00:00:02'), new Date('Jan 01 1970 00:00:00'))).toEqual('a few seconds from now'); - expect(fromNow(new Date('Jan 01 1970 00:01:01'), new Date('Jan 01 1970 00:00:00'))).toEqual('a minute from now'); - expect(fromNow(new Date('Jan 01 1970 01:01:00'), new Date('Jan 01 1970 00:00:00'))).toEqual('an hour from now'); - expect(fromNow(new Date('Jan 01 1970 14:20:00'), new Date('Jan 01 1970 00:00:00'))).toEqual('14 hours from now'); + expect(fromNow(new Date('Jan 01 1970 00:00:02'), new Date('Jan 01 1970 00:00:00'))).toEqual( + 'a few seconds from now', + ); + expect(fromNow(new Date('Jan 01 1970 00:01:01'), new Date('Jan 01 1970 00:00:00'))).toEqual( + 'a minute from now', + ); + expect(fromNow(new Date('Jan 01 1970 01:01:00'), new Date('Jan 01 1970 00:00:00'))).toEqual( + 'an hour from now', + ); + expect(fromNow(new Date('Jan 01 1970 14:20:00'), new Date('Jan 01 1970 00:00:00'))).toEqual( + '14 hours from now', + ); expect(fromNow(new Date('Jan 02 1970'), new Date('Jan 01 1970'))).toEqual('a day from now'); expect(fromNow(new Date('Jan 09 1970'), new Date('Jan 01 1970'))).toEqual('8 days from now'); expect(fromNow(new Date('Feb 02 1970'), new Date('Jan 01 1970'))).toEqual('a month from now'); @@ -56,7 +106,7 @@ describe('isValid', () => { }); describe('formatDuration', () => { - const toMS = (h: number, m: number, s: number) => ((h * 60 * 60) + (m * 60) + s) * 1000; + const toMS = (h: number, m: number, s: number) => (h * 60 * 60 + m * 60 + s) * 1000; it('prints durations correctly', () => { expect(formatDuration(toMS(0, 0, 1))).toEqual('1s'); @@ -93,7 +143,8 @@ describe('formatDuration', () => { }); // Converts time durations to milliseconds -const ms = (s = 0, m = 0, h = 0, d = 0, w = 0) => ((((w * 7 + d) * 24 + h) * 60 + m) * 60 + s) * 1000; +const ms = (s = 0, m = 0, h = 0, d = 0, w = 0) => + ((((w * 7 + d) * 24 + h) * 60 + m) * 60 + s) * 1000; describe('formatPrometheusDuration', () => { it('formats durations correctly', () => { @@ -105,7 +156,7 @@ describe('formatPrometheusDuration', () => { }); it('handles invalid values', () => { - [null, undefined, 0, -1, -9999].forEach(v => expect(formatPrometheusDuration(v)).toEqual('')); + [null, undefined, 0, -1, -9999].forEach((v) => expect(formatPrometheusDuration(v)).toEqual('')); }); }); @@ -153,7 +204,7 @@ describe('parsePrometheusDuration', () => { '1h 0', '1h 0z', '-1h', - ].forEach(v => expect(parsePrometheusDuration(v)).toEqual(0)); + ].forEach((v) => expect(parsePrometheusDuration(v)).toEqual(0)); }); it('mirrors formatPrometheusDuration()', () => { @@ -170,6 +221,6 @@ describe('parsePrometheusDuration', () => { '5w 6d 12h 30m 1s', '999w', '', - ].forEach(v => expect(formatPrometheusDuration(parsePrometheusDuration(v))).toEqual(v)); + ].forEach((v) => expect(formatPrometheusDuration(parsePrometheusDuration(v))).toEqual(v)); }); }); diff --git a/frontend/__tests__/components/utils/download-button.spec.tsx b/frontend/__tests__/components/utils/download-button.spec.tsx index fca286ce5e3..e6a1a761c7a 100644 --- a/frontend/__tests__/components/utils/download-button.spec.tsx +++ b/frontend/__tests__/components/utils/download-button.spec.tsx @@ -4,17 +4,23 @@ import Spy = jasmine.Spy; import * as fileSaver from 'file-saver'; import { Button } from '@patternfly/react-core'; -import { DownloadButton, DownloadButtonProps } from '../../../public/components/utils/download-button'; +import { + DownloadButton, + DownloadButtonProps, +} from '../../../public/components/utils/download-button'; import * as coFetch from '../../../public/co-fetch'; describe(DownloadButton.displayName, () => { let wrapper: ReactWrapper; const url = 'http://google.com'; - const spyAndExpect = (spy: Spy) => (returnValue: any) => new Promise(resolve => spy.and.callFake((...args) => { - resolve(args); - return returnValue; - })); + const spyAndExpect = (spy: Spy) => (returnValue: any) => + new Promise((resolve) => + spy.and.callFake((...args) => { + resolve(args); + return returnValue; + }), + ); beforeEach(() => { wrapper = mount(); @@ -33,7 +39,12 @@ describe(DownloadButton.displayName, () => { it('renders "Downloading..." if download is in flight', (done) => { spyAndExpect(spyOn(coFetch, 'coFetch'))(Promise.resolve()).then(() => { - expect(wrapper.find(Button).text().trim()).toEqual('Downloading...'); + expect( + wrapper + .find(Button) + .text() + .trim(), + ).toEqual('Downloading...'); done(); }); diff --git a/frontend/__tests__/components/utils/error-boundary.spec.tsx b/frontend/__tests__/components/utils/error-boundary.spec.tsx index f80d8fa3900..d0e774b714a 100644 --- a/frontend/__tests__/components/utils/error-boundary.spec.tsx +++ b/frontend/__tests__/components/utils/error-boundary.spec.tsx @@ -1,16 +1,23 @@ import * as React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import { ErrorBoundary, ErrorBoundaryProps, ErrorBoundaryState, withFallback } from '../../../public/components/utils/error-boundary'; +import { + ErrorBoundary, + ErrorBoundaryProps, + ErrorBoundaryState, + withFallback, +} from '../../../public/components/utils/error-boundary'; describe(ErrorBoundary.name, () => { let wrapper: ShallowWrapper; const Child = () => childrens; beforeEach(() => { - wrapper = shallow( - - ); + wrapper = shallow( + + + , + ); }); it('renders child components if not in error state', () => { @@ -19,23 +26,28 @@ describe(ErrorBoundary.name, () => { it('renders fallback component if given when in error state', () => { const FallbackComponent = () =>

Custom Fallback

; - wrapper = wrapper.setProps({FallbackComponent}); - wrapper = wrapper.setState({hasError: true}); + wrapper = wrapper.setProps({ FallbackComponent }); + wrapper = wrapper.setState({ hasError: true }); expect(wrapper.find(Child).exists()).toBe(false); expect(wrapper.find(FallbackComponent).exists()).toBe(true); }); it('renders default fallback component if none given when in error state', () => { - wrapper = wrapper.setState({hasError: true}); + wrapper = wrapper.setState({ hasError: true }); expect(wrapper.find(Child).exists()).toBe(false); - expect(wrapper.at(0).shallow().text()).toEqual(''); + expect( + wrapper + .at(0) + .shallow() + .text(), + ).toEqual(''); }); }); describe('withFallback', () => { - const Component: React.SFC<{size: number}> = (props) => childrens: {props.size}; + const Component: React.SFC<{ size: number }> = (props) => childrens: {props.size}; it('returns the given component wrapped in an `ErrorBoundary`', () => { const WrappedComponent = withFallback(Component); diff --git a/frontend/__tests__/components/utils/firehose.spec.tsx b/frontend/__tests__/components/utils/firehose.spec.tsx index 35d5686965a..76c92b7b23f 100644 --- a/frontend/__tests__/components/utils/firehose.spec.tsx +++ b/frontend/__tests__/components/utils/firehose.spec.tsx @@ -19,7 +19,13 @@ type FirehoseProps = { loaded: boolean; inFlight: boolean; stopK8sWatch: (id: string) => void; - watchK8sObject: (id: string, name: string, namespace: string, query: any, k8sKind: K8sKind) => void; + watchK8sObject: ( + id: string, + name: string, + namespace: string, + query: any, + k8sKind: K8sKind, + ) => void; watchK8sList: (id: string, query: any, k8sKind: K8sKind) => void; }; @@ -33,44 +39,91 @@ describe(Firehose.displayName, () => { let watchK8sList: Spy; beforeEach(() => { - resources = [ - {kind: PodModel.kind, namespace: 'default', prop: 'Pod', isList: true}, - ]; + resources = [{ kind: PodModel.kind, namespace: 'default', prop: 'Pod', isList: true }]; k8sModels = ImmutableMap().set('Pod', PodModel); stopK8sWatch = jasmine.createSpy('stopK8sWatch'); watchK8sObject = jasmine.createSpy('watchK8sObject'); watchK8sList = jasmine.createSpy('watchK8sList'); - wrapper = shallow(); + wrapper = shallow( + , + ); }); it('returns nothing if there are no cached models and `props.inFlight` is true', () => { const noModels = ImmutableMap(); - wrapper = shallow(); + wrapper = shallow( + , + ); expect(wrapper.html()).toBeNull(); }); it('returns nothing if a required model from `props.resources` is missing and `props.inFlight` is true', () => { - const incompleteModels = ImmutableMap().set('Service', ServiceModel); - wrapper = shallow(); + const incompleteModels = ImmutableMap().set( + 'Service', + ServiceModel, + ); + wrapper = shallow( + , + ); expect(wrapper.html()).toBeNull(); }); it('renders if a cached model is available even if `props.inFlight` is true', () => { - wrapper = shallow(); + wrapper = shallow( + , + ); expect(watchK8sList.calls.count()).toBeGreaterThan(0); }); it('does not re-render when `props.inFlight` changes but Firehose data is loaded', () => { - expect(wrapper.instance().shouldComponentUpdate({inFlight: true, loaded: true} as FirehoseProps, null, null)).toBe(false); + expect( + wrapper + .instance() + .shouldComponentUpdate({ inFlight: true, loaded: true } as FirehoseProps, null, null), + ).toBe(false); }); it('clears and restarts "firehoses" when `props.resources` change', () => { - resources = resources.concat([{kind: ServiceModel.kind, namespace: 'default', prop: 'Service', isList: true}]); - wrapper = wrapper.setProps({resources}); + resources = resources.concat([ + { kind: ServiceModel.kind, namespace: 'default', prop: 'Service', isList: true }, + ]); + wrapper = wrapper.setProps({ resources }); expect(stopK8sWatch.calls.count()).toEqual(1); expect(watchK8sList.calls.count()).toEqual(2); diff --git a/frontend/__tests__/components/utils/name-value-editor.spec.tsx b/frontend/__tests__/components/utils/name-value-editor.spec.tsx index ed563b72f37..ceddf64c340 100644 --- a/frontend/__tests__/components/utils/name-value-editor.spec.tsx +++ b/frontend/__tests__/components/utils/name-value-editor.spec.tsx @@ -6,7 +6,6 @@ import HTML5Backend from 'react-dnd-html5-backend'; import { NameValueEditor } from '../../../public/components/utils/name-value-editor'; describe(NameValueEditor.displayName, () => { - const Editor = DragDropContext(HTML5Backend)(NameValueEditor); describe('When supplied with attributes nameString and valueString', () => { @@ -14,10 +13,10 @@ describe(NameValueEditor.displayName, () => { const wrapper = shallow( {}} + updateParentData={() => {}} nameString={'foo'} valueString={'bar'} - /> + />, ); expect(wrapper.html()).toContain('foo'); @@ -28,10 +27,7 @@ describe(NameValueEditor.displayName, () => { describe('When supplied with nameValuePairs', () => { it('renders PairElement correctly', () => { const wrapper = shallow( - {}} - /> + {}} />, ); expect(wrapper.html()).toContain('value="name"'); @@ -44,9 +40,9 @@ describe(NameValueEditor.displayName, () => { const wrapper = shallow( {}} + updateParentData={() => {}} readOnly={true} - /> + />, ); expect(wrapper.html()).not.toContain('pairs-list__add-icon'); @@ -56,9 +52,9 @@ describe(NameValueEditor.displayName, () => { const wrapper = shallow( {}} + updateParentData={() => {}} readOnly={true} - /> + />, ); expect(wrapper.html()).not.toContain('pairs-list__delete-icon'); expect(wrapper.html()).not.toContain('pairs-list__action-icon--reorder'); @@ -70,10 +66,10 @@ describe(NameValueEditor.displayName, () => { const wrapper = shallow( {}} + updateParentData={() => {}} readOnly={false} allowSorting={true} - /> + />, ); expect(wrapper.html()).toContain('pairs-list__add-icon'); @@ -85,10 +81,10 @@ describe(NameValueEditor.displayName, () => { const wrapper = shallow( {}} + updateParentData={() => {}} readOnly={false} allowSorting={true} - /> + />, ); expect(wrapper.html()).toContain('pairs-list__delete-icon'); @@ -101,9 +97,9 @@ describe(NameValueEditor.displayName, () => { const wrapper = shallow( {}} + updateParentData={() => {}} allowSorting={false} - /> + />, ); expect(wrapper.html()).toContain('pairs-list__delete-icon'); expect(wrapper.html()).not.toContain('pairs-list__action-icon--reorder'); diff --git a/frontend/__tests__/components/utils/page-heading.spec.tsx b/frontend/__tests__/components/utils/page-heading.spec.tsx index 82738c23800..19cb1fee566 100644 --- a/frontend/__tests__/components/utils/page-heading.spec.tsx +++ b/frontend/__tests__/components/utils/page-heading.spec.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import { shallow, ShallowWrapper } from 'enzyme'; -import { PageHeading, PageHeadingProps, BreadCrumbs, BreadCrumbsProps } from '../../../public/components/utils/headings'; +import { + PageHeading, + PageHeadingProps, + BreadCrumbs, + BreadCrumbsProps, +} from '../../../public/components/utils/headings'; import { ResourceIcon } from '../../../public/components/utils'; import { testResourceInstance } from '../../../__mocks__/k8sResourcesMocks'; @@ -11,23 +16,27 @@ describe(BreadCrumbs.displayName, () => { let breadcrumbs: BreadCrumbsProps['breadcrumbs']; beforeEach(() => { - breadcrumbs = [ - {name: 'pods', path: '/pods'}, - {name: 'containers', path: '/pods'}, - ]; + breadcrumbs = [{ name: 'pods', path: '/pods' }, { name: 'containers', path: '/pods' }]; wrapper = shallow(); }); it('renders each given breadcrumb', () => { const links: ShallowWrapper = wrapper.find(Link); - const nonLink: ShallowWrapper = wrapper.findWhere(BreadcrumbItem => BreadcrumbItem.props().isActive === true); + const nonLink: ShallowWrapper = wrapper.findWhere( + (BreadcrumbItem) => BreadcrumbItem.props().isActive === true, + ); expect(links.length + nonLink.length).toEqual(breadcrumbs.length); breadcrumbs.forEach((crumb, i) => { if (i < links.length) { expect(links.at(i).props().to).toEqual(crumb.path); - expect(links.at(i).childAt(0).text()).toEqual(crumb.name); + expect( + links + .at(i) + .childAt(0) + .text(), + ).toEqual(crumb.name); } else { expect(nonLink.render().text()).toEqual(crumb.name); } @@ -44,7 +53,7 @@ describe(PageHeading.displayName, () => { it('renders resource icon if given `kind`', () => { const kind = 'Pod'; - wrapper.setProps({kind}); + wrapper.setProps({ kind }); const icon = wrapper.find(ResourceIcon); expect(icon.exists()).toBe(true); @@ -53,21 +62,24 @@ describe(PageHeading.displayName, () => { it('renders custom title component if given', () => { const title = My Custom Title; - wrapper.setProps({title}); + wrapper.setProps({ title }); expect(wrapper.find('.co-m-pane__heading').contains(title)).toBe(true); }); it('renders breadcrumbs if given `breadcrumbsFor` function', () => { const breadcrumbs = []; - wrapper = wrapper.setProps({breadcrumbsFor: () => breadcrumbs, obj: { data: testResourceInstance, loaded: true, loadError: null }}); + wrapper = wrapper.setProps({ + breadcrumbsFor: () => breadcrumbs, + obj: { data: testResourceInstance, loaded: true, loadError: null }, + }); expect(wrapper.find(BreadCrumbs).exists()).toBe(true); expect(wrapper.find(BreadCrumbs).props().breadcrumbs).toEqual(breadcrumbs); }); it('does not render breadcrumbs if object has not loaded', () => { - wrapper = wrapper.setProps({breadcrumbsFor: () => [], obj: null}); + wrapper = wrapper.setProps({ breadcrumbsFor: () => [], obj: null }); expect(wrapper.find(BreadCrumbs).exists()).toBe(false); }); diff --git a/frontend/__tests__/components/utils/promise-component.spec.tsx b/frontend/__tests__/components/utils/promise-component.spec.tsx index 529eb4d5d32..ad85b7e3bee 100644 --- a/frontend/__tests__/components/utils/promise-component.spec.tsx +++ b/frontend/__tests__/components/utils/promise-component.spec.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; import { shallow } from 'enzyme'; -import { PromiseComponent, PromiseComponentState, withHandlePromise, HandlePromiseProps } from '../../../public/components/utils/promise-component'; +import { + PromiseComponent, + PromiseComponentState, + withHandlePromise, + HandlePromiseProps, +} from '../../../public/components/utils/promise-component'; describe('withHandlePromise', () => { type TestProps = { @@ -9,52 +14,73 @@ describe('withHandlePromise', () => { } & HandlePromiseProps; const Test = withHandlePromise((props: TestProps) => { - return
-

{props.errorMessage}

- { props.inProgress - ? Loading... - : } -
; + return ( +
+

{props.errorMessage}

+ {props.inProgress ? ( + Loading... + ) : ( + + )} +
+ ); }); it('passes `props.inProgress` as true when calling `props.handlePromise()`', () => { const wrapper = shallow(); - wrapper.dive().find('button').simulate('click'); + wrapper + .dive() + .find('button') + .simulate('click'); expect(wrapper.dive().text()).toEqual('Loading...'); }); it('passes message if an error is thrown from handling the promise', (done) => { const wrapper = shallow(); - wrapper.dive().find('button').simulate('click'); + wrapper + .dive() + .find('button') + .simulate('click'); setTimeout(() => { - expect(wrapper.dive().find('h1').text()).toEqual('An error occurred. Please try again.'); + expect( + wrapper + .dive() + .find('h1') + .text(), + ).toEqual('An error occurred. Please try again.'); done(); }, 10); }); }); describe(PromiseComponent.name, () => { - class Test extends PromiseComponent<{promise: Promise}, PromiseComponentState> { + class Test extends PromiseComponent<{ promise: Promise }, PromiseComponentState> { render() { - return this.state.inProgress - ?
Loading...
- : ; + return this.state.inProgress ? ( +
Loading...
+ ) : ( + + ); } } it('sets `inProgress` to true before resolving promise', (done) => { let wrapper = shallow(); - const promise = new Promise(resolve => { + const promise = new Promise((resolve) => { // expect(wrapper.text()).toEqual('Loading...'); resolve(42); expect(wrapper.find('button').exists()).toBe(true); done(); }); - wrapper = wrapper.setProps({promise}); + wrapper = wrapper.setProps({ promise }); wrapper.find('button').simulate('click'); }); }); diff --git a/frontend/__tests__/container.spec.ts b/frontend/__tests__/container.spec.ts index db04a6d4131..ada88a74ee1 100644 --- a/frontend/__tests__/container.spec.ts +++ b/frontend/__tests__/container.spec.ts @@ -4,37 +4,41 @@ import { getContainerStatus } from '../public/module/k8s/container'; describe('k8sDocker', () => { describe('#getContainerStatus', () => { it('returns falsy when pod has no container status with given name', () => { - expect(getContainerStatus( - { - status: { - containerStatuses: [ - {name: 'ACAEB740-01D1-4D43-BB7B-68EBFD484701'}, - {name: '06DB4D15-3A24-482B-82B1-A46337D8C2DD'}, - {name: '7AE75BB5-B562-4D5A-B880-F529797CAD5F'}, - ], - }, - } as PodKind, + expect( + getContainerStatus( + { + status: { + containerStatuses: [ + { name: 'ACAEB740-01D1-4D43-BB7B-68EBFD484701' }, + { name: '06DB4D15-3A24-482B-82B1-A46337D8C2DD' }, + { name: '7AE75BB5-B562-4D5A-B880-F529797CAD5F' }, + ], + }, + } as PodKind, - // container name - '9242B9F6-A50A-4330-8C0E-B18EA4672A89' - )).toBeFalsy(); + // container name + '9242B9F6-A50A-4330-8C0E-B18EA4672A89', + ), + ).toBeFalsy(); }); it('returns container status with given name', () => { - expect(getContainerStatus( - { - status: { - containerStatuses: [ - {name: 'ACAEB740-01D1-4D43-BB7B-68EBFD484701'}, - {name: '9242B9F6-A50A-4330-8C0E-B18EA4672A89', status: 'running'}, - {name: '7AE75BB5-B562-4D5A-B880-F529797CAD5F'}, - ], - }, - } as PodKind, + expect( + getContainerStatus( + { + status: { + containerStatuses: [ + { name: 'ACAEB740-01D1-4D43-BB7B-68EBFD484701' }, + { name: '9242B9F6-A50A-4330-8C0E-B18EA4672A89', status: 'running' }, + { name: '7AE75BB5-B562-4D5A-B880-F529797CAD5F' }, + ], + }, + } as PodKind, - // container name - '9242B9F6-A50A-4330-8C0E-B18EA4672A89' - )).toEqual({name: '9242B9F6-A50A-4330-8C0E-B18EA4672A89', status: 'running'}); + // container name + '9242B9F6-A50A-4330-8C0E-B18EA4672A89', + ), + ).toEqual({ name: '9242B9F6-A50A-4330-8C0E-B18EA4672A89', status: 'running' }); }); }); }); diff --git a/frontend/__tests__/module/k8s/k8s-models.spec.ts b/frontend/__tests__/module/k8s/k8s-models.spec.ts index 3535c4781de..be2395204c8 100644 --- a/frontend/__tests__/module/k8s/k8s-models.spec.ts +++ b/frontend/__tests__/module/k8s/k8s-models.spec.ts @@ -1,34 +1,51 @@ -import { referenceFor, referenceForCRD, referenceForOwnerRef, referenceForModel, kindForReference, versionForReference, modelsToMap } from '../../../public/module/k8s'; -import { testNamespace, testCRD, testOwnedResourceInstance } from '../../../__mocks__/k8sResourcesMocks'; -import { PodModel, DeploymentModel, ClusterResourceQuotaModel, PrometheusModel } from '../../../public/models'; +import { + referenceFor, + referenceForCRD, + referenceForOwnerRef, + referenceForModel, + kindForReference, + versionForReference, + modelsToMap, +} from '../../../public/module/k8s'; +import { + testNamespace, + testCRD, + testOwnedResourceInstance, +} from '../../../__mocks__/k8sResourcesMocks'; +import { + PodModel, + DeploymentModel, + ClusterResourceQuotaModel, + PrometheusModel, +} from '../../../public/models'; describe('referenceFor', () => { - it('returns a reference for objects without an API group', () => { expect(referenceFor(testNamespace)).toEqual('core~v1~Namespace'); }); it('returns a reference for objects with an API group', () => { - expect(referenceFor(testOwnedResourceInstance)).toEqual('testapp.coreos.com~v1alpha1~TestOwnedResource'); + expect(referenceFor(testOwnedResourceInstance)).toEqual( + 'testapp.coreos.com~v1alpha1~TestOwnedResource', + ); }); }); describe('referenceForCRD', () => { - it('returns a reference for custom resource definitions', () => { expect(referenceForCRD(testCRD)).toEqual('testapp.coreos.com~v1alpha1~TestResource'); }); }); describe('referenceForOwnerRef', () => { - it('returns a reference for an ownerRef', () => { - expect(referenceForOwnerRef(testOwnedResourceInstance.metadata.ownerReferences[0])).toEqual('testapp.coreos.com~v1alpha1~TestResource'); + expect(referenceForOwnerRef(testOwnedResourceInstance.metadata.ownerReferences[0])).toEqual( + 'testapp.coreos.com~v1alpha1~TestResource', + ); }); }); describe('referenceForModel', () => { - it('returns a reference for a legacy k8s model', () => { expect(referenceForModel(PodModel)).toEqual('core~v1~Pod'); }); @@ -39,7 +56,6 @@ describe('referenceForModel', () => { }); describe('kindForReference', () => { - it('returns the kind from a given reference', () => { expect(kindForReference(referenceFor(testNamespace))).toEqual('Namespace'); }); @@ -51,14 +67,12 @@ describe('kindForReference', () => { }); describe('versionForReference', () => { - it('returns the API version for a given reference', () => { expect(versionForReference(referenceFor(testOwnedResourceInstance))).toEqual('v1alpha1'); }); }); describe('modelsToMap', () => { - it('returns a map with keys based on model.kind for models with crd:false', () => { expect(modelsToMap([PodModel, DeploymentModel]).toObject()).toEqual({ [PodModel.kind]: PodModel, diff --git a/frontend/__tests__/module/k8s/pods.spec.ts b/frontend/__tests__/module/k8s/pods.spec.ts index 018fde853cd..40af75cd544 100644 --- a/frontend/__tests__/module/k8s/pods.spec.ts +++ b/frontend/__tests__/module/k8s/pods.spec.ts @@ -12,7 +12,7 @@ describe('podPhase', () => { it('returns empty string if given invalid pod', () => { const invalidPods: any[] = [null, undefined, {}]; - invalidPods.forEach(p => { + invalidPods.forEach((p) => { const phase: string = podPhase(p); expect(phase).toEqual(''); @@ -42,14 +42,16 @@ describe('podPhase', () => { it('returns the state reason of the last waiting or terminated container in the pod', () => { pod.status.containerStatuses = [ - {state: {waiting: {reason: 'Unschedulable'}}}, - {state: {terminated: {reason: 'Initialized'}}}, - {state: {waiting: {reason: 'Ready'}}}, - {state: {running: {}}}, + { state: { waiting: { reason: 'Unschedulable' } } }, + { state: { terminated: { reason: 'Initialized' } } }, + { state: { waiting: { reason: 'Ready' } } }, + { state: { running: {} } }, ]; const expectedPhase: string = pod.status.containerStatuses - .filter(({state}) => state.waiting !== undefined || state.terminated !== undefined) - .map(({state}) => state.waiting !== undefined ? state.waiting.reason : state.terminated.reason) + .filter(({ state }) => state.waiting !== undefined || state.terminated !== undefined) + .map(({ state }) => + state.waiting !== undefined ? state.waiting.reason : state.terminated.reason, + ) .slice(-1)[0]; const phase: string = podPhase(pod); @@ -64,9 +66,7 @@ describe('podReadiness', () => { pod = { status: { phase: 'Running', - conditions: [ - {type: 'Ready', status: 'True'}, - ], + conditions: [{ type: 'Ready', status: 'True' }], }, }; }); @@ -86,13 +86,29 @@ describe('podReadiness', () => { it('returns `reason` of the condition with the oldest `lastTransitionTime` if not all ready', () => { pod.status.conditions = pod.status.conditions.concat([ - {type: 'Initialized', status: 'False', lastTransitionTime: '2017-04-01T12:00:00Z', reason: 'Ready'}, - {type: 'PodScheduled', status: 'True', lastTransitionTime: '2017-03-01T12:00:00Z', reason: 'Initialized'}, - {type: 'Unschedulable', status: 'False', lastTransitionTime: '2017-02-01T12:00:00Z', reason: 'Unschedulable'}, + { + type: 'Initialized', + status: 'False', + lastTransitionTime: '2017-04-01T12:00:00Z', + reason: 'Ready', + }, + { + type: 'PodScheduled', + status: 'True', + lastTransitionTime: '2017-03-01T12:00:00Z', + reason: 'Initialized', + }, + { + type: 'Unschedulable', + status: 'False', + lastTransitionTime: '2017-02-01T12:00:00Z', + reason: 'Unschedulable', + }, ]); const expectedReadiness: string = pod.status.conditions - .filter(condition => condition.status !== 'True') - .sort((a, b) => new Date(a.lastTransitionTime) < new Date(b.lastTransitionTime) ? -1 : 1)[0].reason; + .filter((condition) => condition.status !== 'True') + .sort((a, b) => (new Date(a.lastTransitionTime) < new Date(b.lastTransitionTime) ? -1 : 1))[0] + .reason; const readiness = podReadiness(pod); @@ -101,13 +117,14 @@ describe('podReadiness', () => { it('returns `type` of the condition with the oldest `lastTransitionTime` if not all ready and `reason` is undefined', () => { pod.status.conditions = pod.status.conditions.concat([ - {type: 'Initialized', status: 'False', lastTransitionTime: '2017-04-01T12:00:00Z'}, - {type: 'PodScheduled', status: 'True', lastTransitionTime: '2017-03-01T12:00:00Z'}, - {type: 'Unschedulable', status: 'False', lastTransitionTime: '2017-02-01T12:00:00Z'}, + { type: 'Initialized', status: 'False', lastTransitionTime: '2017-04-01T12:00:00Z' }, + { type: 'PodScheduled', status: 'True', lastTransitionTime: '2017-03-01T12:00:00Z' }, + { type: 'Unschedulable', status: 'False', lastTransitionTime: '2017-02-01T12:00:00Z' }, ]); const expectedReadiness: string = pod.status.conditions - .filter(condition => condition.status !== 'True') - .sort((a, b) => new Date(a.lastTransitionTime) < new Date(b.lastTransitionTime) ? -1 : 1)[0].type; + .filter((condition) => condition.status !== 'True') + .sort((a, b) => (new Date(a.lastTransitionTime) < new Date(b.lastTransitionTime) ? -1 : 1))[0] + .type; const readiness = podReadiness(pod); diff --git a/frontend/__tests__/probe.spec.ts b/frontend/__tests__/probe.spec.ts index 02c4fbb4c7e..fac33f6cc6d 100644 --- a/frontend/__tests__/probe.spec.ts +++ b/frontend/__tests__/probe.spec.ts @@ -4,38 +4,53 @@ describe('k8sProbe', () => { describe('#parseCmd', () => { describe('for tcpSocket', () => { it('returns port as number when string looks like number', () => { - expect(parseCmd('tcpSocket', '8080')).toEqual({port: 8080}); + expect(parseCmd('tcpSocket', '8080')).toEqual({ port: 8080 }); }); it('returns port as string when string does not look like number', () => { - expect(parseCmd('tcpSocket', 'http')).toEqual({port: 'http'}); + expect(parseCmd('tcpSocket', 'http')).toEqual({ port: 'http' }); }); }); describe('for httpGet', () => { it('returns port as number when string looks like number', () => { - expect(parseCmd('httpGet', 'http://localhost:8080/check')) - .toEqual({ host: 'http://localhost', path: '/check', port: 8080 }); + expect(parseCmd('httpGet', 'http://localhost:8080/check')).toEqual({ + host: 'http://localhost', + path: '/check', + port: 8080, + }); }); it('returns port as string when string does not look like number', () => { - expect(parseCmd('httpGet', 'http://localhost:foo/check')) - .toEqual({ host: 'http://localhost', path: '/check', port: 'foo' }); + expect(parseCmd('httpGet', 'http://localhost:foo/check')).toEqual({ + host: 'http://localhost', + path: '/check', + port: 'foo', + }); }); it('returns default port 80 when no port is specified', () => { - expect(parseCmd('httpGet', 'http://localhost/check')) - .toEqual({ host: 'http://localhost', path: '/check', port: 80 }); + expect(parseCmd('httpGet', 'http://localhost/check')).toEqual({ + host: 'http://localhost', + path: '/check', + port: 80, + }); }); it('returns host as https when https is specified', () => { - expect(parseCmd('httpGet', 'https://localhost:1234/check')) - .toEqual({ host: 'https://localhost', path: '/check', port: 1234 }); + expect(parseCmd('httpGet', 'https://localhost:1234/check')).toEqual({ + host: 'https://localhost', + path: '/check', + port: 1234, + }); }); it('returns correct port based on scheme', () => { - expect(parseCmd('httpGet', 'https://localhost/check')) - .toEqual({ host: 'https://localhost', path: '/check', port: 443 }); + expect(parseCmd('httpGet', 'https://localhost/check')).toEqual({ + host: 'https://localhost', + path: '/check', + port: 443, + }); }); }); }); @@ -43,7 +58,7 @@ describe('k8sProbe', () => { describe('#flattenCmd', () => { describe('for tcpSocket', () => { it('casts port number to string', () => { - expect(flattenCmd('tcpSocket', {port: 8080})).toEqual('8080'); + expect(flattenCmd('tcpSocket', { port: 8080 })).toEqual('8080'); }); }); }); diff --git a/frontend/__tests__/public/co-fetch.spec.js b/frontend/__tests__/public/co-fetch.spec.js index d2cfa52c788..3408c589ac3 100644 --- a/frontend/__tests__/public/co-fetch.spec.js +++ b/frontend/__tests__/public/co-fetch.spec.js @@ -13,10 +13,16 @@ describe('coFetch', () => { }); it('does not log out users who get a 401 from chargeback', () => { - expect(shouldLogout('/api/kubernetes/api/v1/namespaces/prd354/services/chargeback/proxy/api')).toEqual(false); + expect( + shouldLogout('/api/kubernetes/api/v1/namespaces/prd354/services/chargeback/proxy/api'), + ).toEqual(false); }); it('does not log out users who get a 401 from graphs', () => { - expect(shouldLogout('/api/kubernetes/api/v1/proxy/namespaces/tectonic-system/services/prometheus:9090/api/v1/query?query=100%20-%20(sum(rate(node_cpu%7Bjob%3D%22node-exporter%22%2Cmode%3D%22idle%22%7D%5B2m%5D))%20%2F%20count(node_cpu%7Bjob%3D%22node-exporter%22%2C%20mode%3D%22idle%22%7D))%20*%20100')).toEqual(false); + expect( + shouldLogout( + '/api/kubernetes/api/v1/proxy/namespaces/tectonic-system/services/prometheus:9090/api/v1/query?query=100%20-%20(sum(rate(node_cpu%7Bjob%3D%22node-exporter%22%2Cmode%3D%22idle%22%7D%5B2m%5D))%20%2F%20count(node_cpu%7Bjob%3D%22node-exporter%22%2C%20mode%3D%22idle%22%7D))%20*%20100', + ), + ).toEqual(false); }); }); diff --git a/frontend/__tests__/reducers/dashboards.spec.ts b/frontend/__tests__/reducers/dashboards.spec.ts index 14ecf9c82ad..80e33ad84fd 100644 --- a/frontend/__tests__/reducers/dashboards.spec.ts +++ b/frontend/__tests__/reducers/dashboards.spec.ts @@ -2,7 +2,13 @@ import * as Immutable from 'immutable'; import { noop } from 'lodash-es'; import { dashboardsReducer, defaults, RESULTS_TYPE } from '../../public/reducers/dashboards'; -import { activateWatch, updateWatchTimeout, updateWatchInFlight, stopWatch, setData } from '../../public/actions/dashboards'; +import { + activateWatch, + updateWatchTimeout, + updateWatchInFlight, + stopWatch, + setData, +} from '../../public/actions/dashboards'; describe('dashboardsReducer', () => { it('returns default values if state is uninitialized', () => { @@ -28,19 +34,23 @@ describe('dashboardsReducer', () => { }); it('updates watch timeout reference', () => { - const timeout = {ref: noop, unref: noop}; + const timeout = { ref: noop, unref: noop }; const action = updateWatchTimeout(RESULTS_TYPE.URL, 'fooUrl', timeout); const initialState = Immutable.Map(defaults); const stateWithTimeout = dashboardsReducer(initialState, action); - expect(stateWithTimeout).toEqual(initialState.setIn([RESULTS_TYPE.URL, 'fooUrl', 'timeout'], timeout)); + expect(stateWithTimeout).toEqual( + initialState.setIn([RESULTS_TYPE.URL, 'fooUrl', 'timeout'], timeout), + ); - const nextTimeout = {ref: noop, unref: noop}; + const nextTimeout = { ref: noop, unref: noop }; const nextAction = updateWatchTimeout(RESULTS_TYPE.URL, 'fooUrl', nextTimeout); const nextState = dashboardsReducer(stateWithTimeout, nextAction); - expect(nextState).toEqual(stateWithTimeout.setIn([RESULTS_TYPE.URL, 'fooUrl', 'timeout'], nextTimeout)); + expect(nextState).toEqual( + stateWithTimeout.setIn([RESULTS_TYPE.URL, 'fooUrl', 'timeout'], nextTimeout), + ); }); it('updates in flight resource', () => { @@ -48,7 +58,9 @@ describe('dashboardsReducer', () => { const initialState = Immutable.Map(defaults); const stateInFlight = dashboardsReducer(initialState, action); - expect(stateInFlight).toEqual(initialState.setIn([RESULTS_TYPE.URL, 'fooUrl', 'inFlight'], true)); + expect(stateInFlight).toEqual( + initialState.setIn([RESULTS_TYPE.URL, 'fooUrl', 'inFlight'], true), + ); const nextAction = updateWatchInFlight(RESULTS_TYPE.URL, 'fooUrl', false); const nextState = dashboardsReducer(stateInFlight, nextAction); @@ -57,9 +69,11 @@ describe('dashboardsReducer', () => { }); it('stops watch', () => { - const timeout = {ref: noop, unref: noop}; + const timeout = { ref: noop, unref: noop }; const action = stopWatch(RESULTS_TYPE.URL, 'fooUrl'); - const initialState = Immutable.Map(defaults).merge({[RESULTS_TYPE.URL]: {fooUrl: {active: 2, timeout}}}); + const initialState = Immutable.Map(defaults).merge({ + [RESULTS_TYPE.URL]: { fooUrl: { active: 2, timeout } }, + }); const newState = dashboardsReducer(initialState, action); expect(newState).toEqual(initialState.setIn([RESULTS_TYPE.URL, 'fooUrl', 'active'], 1)); @@ -73,10 +87,13 @@ describe('dashboardsReducer', () => { const initialState = Immutable.Map(defaults); const newState = dashboardsReducer(initialState, action); - expect(newState).toEqual(initialState.withMutations(s => - s.setIn([RESULTS_TYPE.URL, 'fooUrl', 'data'], 'result') - .setIn([RESULTS_TYPE.URL, 'fooUrl', 'loadError'], null) - )); + expect(newState).toEqual( + initialState.withMutations((s) => + s + .setIn([RESULTS_TYPE.URL, 'fooUrl', 'data'], 'result') + .setIn([RESULTS_TYPE.URL, 'fooUrl', 'loadError'], null), + ), + ); const nextAction = setData(RESULTS_TYPE.URL, 'fooUrl', 'newResult'); const nextState = dashboardsReducer(newState, nextAction); diff --git a/frontend/__tests__/reducers/features.spec.tsx b/frontend/__tests__/reducers/features.spec.tsx index 621e193ba2b..02fb9dc5b1a 100644 --- a/frontend/__tests__/reducers/features.spec.tsx +++ b/frontend/__tests__/reducers/features.spec.tsx @@ -7,7 +7,6 @@ import { FLAGS } from '../../public/const'; import { featureReducer, defaults, connectToFlags } from '../../public/reducers/features'; describe('featureReducer', () => { - it('returns default values if state is uninitialized', () => { const newState = featureReducer(null, null); @@ -19,11 +18,11 @@ describe('featureReducer', () => { const initialState = Immutable.Map(defaults); const newState = featureReducer(initialState, action); - expect(newState).toEqual(initialState.merge({[action.payload.flag]: action.payload.value})); + expect(newState).toEqual(initialState.merge({ [action.payload.flag]: action.payload.value })); }); it('returns state if not `setFlag` action', () => { - const action = {type: 'OTHER_ACTION'} as any; + const action = { type: 'OTHER_ACTION' } as any; const initialState = Immutable.Map(defaults); const newState = featureReducer(initialState, action); @@ -31,26 +30,36 @@ describe('featureReducer', () => { }); it('sets flags when it gets CRDs', () => { - const action = receivedResources({models: [], adminResources: [], allResources: [], configResources: [], namespacedSet: null, safeResources: [], preferredVersions: []}); + const action = receivedResources({ + models: [], + adminResources: [], + allResources: [], + configResources: [], + namespacedSet: null, + safeResources: [], + preferredVersions: [], + }); const initialState = Immutable.Map(defaults); const newState = featureReducer(initialState, action); - expect(newState).toEqual(initialState.merge({ - [FLAGS.PROMETHEUS]: false, - [FLAGS.CHARGEBACK]: false, - [FLAGS.SERVICE_CATALOG]: false, - [FLAGS.CLUSTER_API]: false, - [FLAGS.MACHINE_CONFIG]: false, - [FLAGS.MACHINE_AUTOSCALER]: false, - [FLAGS.CONSOLE_CLI_DOWNLOAD]: false, - [FLAGS.CONSOLE_NOTIFICATION]: false, - [FLAGS.CONSOLE_EXTERNAL_LOG_LINK]: false, - })); + expect(newState).toEqual( + initialState.merge({ + [FLAGS.PROMETHEUS]: false, + [FLAGS.CHARGEBACK]: false, + [FLAGS.SERVICE_CATALOG]: false, + [FLAGS.CLUSTER_API]: false, + [FLAGS.MACHINE_CONFIG]: false, + [FLAGS.MACHINE_AUTOSCALER]: false, + [FLAGS.CONSOLE_CLI_DOWNLOAD]: false, + [FLAGS.CONSOLE_NOTIFICATION]: false, + [FLAGS.CONSOLE_EXTERNAL_LOG_LINK]: false, + }), + ); }); }); describe('connectToFlags', () => { - type MyComponentProps = {propA: number, propB: boolean, flags: {[key: string]: boolean}}; + type MyComponentProps = { propA: number; propB: boolean; flags: { [key: string]: boolean } }; class MyComponent extends React.Component { render() { diff --git a/frontend/__tests__/reducers/ui.spec.ts b/frontend/__tests__/reducers/ui.spec.ts index b5fd0326bae..c329e593e40 100644 --- a/frontend/__tests__/reducers/ui.spec.ts +++ b/frontend/__tests__/reducers/ui.spec.ts @@ -19,24 +19,28 @@ describe('getDefaultPerspective', () => { it('should default to perspective extension marked default', () => { // return Perspectives extension with one marked as the default - spyOn(plugins.registry, 'getPerspectives').and.returnValue([{ - type: 'Perspective', - properties: { - id: 'admin', - default: true, - }, - } as plugins.Perspective]); + spyOn(plugins.registry, 'getPerspectives').and.returnValue([ + { + type: 'Perspective', + properties: { + id: 'admin', + default: true, + }, + } as plugins.Perspective, + ]); expect(getDefaultPerspective()).toBe('admin'); }); it('should default to localStorage if perspective is a valid extension', () => { // return Perspectives extension whose id matches that in the localStorage - spyOn(plugins.registry, 'getPerspectives').and.returnValue([{ - type: 'Perspective', - properties: { - id: 'test', - }, - } as plugins.Perspective]); + spyOn(plugins.registry, 'getPerspectives').and.returnValue([ + { + type: 'Perspective', + properties: { + id: 'test', + }, + } as plugins.Perspective, + ]); localStorage.setItem(LAST_PERSPECTIVE_LOCAL_STORAGE_KEY, 'test'); expect(getDefaultPerspective()).toBe('test'); }); diff --git a/frontend/__tests__/selector-requirement.spec.js b/frontend/__tests__/selector-requirement.spec.js index 00a5e52c0d3..1d9fe6063a7 100644 --- a/frontend/__tests__/selector-requirement.spec.js +++ b/frontend/__tests__/selector-requirement.spec.js @@ -1,48 +1,52 @@ -import { createEquals, requirementFromString, requirementToString } from '../public/module/k8s/selector-requirement'; +import { + createEquals, + requirementFromString, + requirementToString, +} from '../public/module/k8s/selector-requirement'; describe('k8sSelectorRequirement', () => { describe('#requirementFromString', () => { [ { - requirement: {key: 'key1', operator: 'Equals', values: ['value1']}, - string: 'key1=value1', + requirement: { key: 'key1', operator: 'Equals', values: ['value1'] }, + string: 'key1=value1', }, { - requirement: {key: 'key1', operator: 'NotEquals', values: ['value1']}, - string: 'key1!=value1', + requirement: { key: 'key1', operator: 'NotEquals', values: ['value1'] }, + string: 'key1!=value1', }, { - requirement: {key: 'key1', operator: 'Exists', values: []}, - string: 'key1', + requirement: { key: 'key1', operator: 'Exists', values: [] }, + string: 'key1', }, { - requirement: {key: 'key1', operator: 'DoesNotExist', values: []}, - string: '!key1', + requirement: { key: 'key1', operator: 'DoesNotExist', values: [] }, + string: '!key1', }, { - requirement: {key: 'key1', operator: 'In', values: ['value1', 'value2']}, - string: 'key1 in (value1,value2)', + requirement: { key: 'key1', operator: 'In', values: ['value1', 'value2'] }, + string: 'key1 in (value1,value2)', }, { - requirement: {key: 'key1', operator: 'NotIn', values: ['value1', 'value2']}, - string: 'key1 notin (value1,value2)', + requirement: { key: 'key1', operator: 'NotIn', values: ['value1', 'value2'] }, + string: 'key1 notin (value1,value2)', }, { - requirement: {key: 'key1', operator: 'GreaterThan', values: ['666.999']}, - string: 'key1 > 666.999', + requirement: { key: 'key1', operator: 'GreaterThan', values: ['666.999'] }, + string: 'key1 > 666.999', }, { - requirement: {key: 'key1', operator: 'LessThan', values: ['666.999']}, - string: 'key1 < 666.999', + requirement: { key: 'key1', operator: 'LessThan', values: ['666.999'] }, + string: 'key1 < 666.999', }, - ].forEach(t => { + ].forEach((t) => { it('...', () => expect(requirementFromString(t.string)).toEqual(t.requirement)); }); @@ -71,69 +75,72 @@ describe('k8sSelectorRequirement', () => { 'key1<', 'key1<=', 'key1 { - it(`returns falsy for unknown/malformed string: ${s}`, () => expect(requirementFromString(s)).toBeFalsy()); + ].forEach((s) => { + it(`returns falsy for unknown/malformed string: ${s}`, () => + expect(requirementFromString(s)).toBeFalsy()); }); }); describe('#requirementToString', () => { [ { - requirement: {key: 'key1', operator: 'Equals', values: ['value1', 'value2']}, - string: 'key1=value1', + requirement: { key: 'key1', operator: 'Equals', values: ['value1', 'value2'] }, + string: 'key1=value1', }, { - requirement: {key: 'key1', operator: 'NotEquals', values: ['value1', 'value2']}, - string: 'key1!=value1', + requirement: { key: 'key1', operator: 'NotEquals', values: ['value1', 'value2'] }, + string: 'key1!=value1', }, { - requirement: {key: 'key1', operator: 'Exists', values: ['value1']}, - string: 'key1', + requirement: { key: 'key1', operator: 'Exists', values: ['value1'] }, + string: 'key1', }, { - requirement: {key: 'key1', operator: 'DoesNotExist', values: ['value1']}, - string: '!key1', + requirement: { key: 'key1', operator: 'DoesNotExist', values: ['value1'] }, + string: '!key1', }, { - requirement: {key: 'key1', operator: 'In', values: ['value1', 'value2']}, - string: 'key1 in (value1,value2)', + requirement: { key: 'key1', operator: 'In', values: ['value1', 'value2'] }, + string: 'key1 in (value1,value2)', }, { - requirement: {key: 'key1', operator: 'NotIn', values: ['value1', 'value2']}, - string: 'key1 notin (value1,value2)', + requirement: { key: 'key1', operator: 'NotIn', values: ['value1', 'value2'] }, + string: 'key1 notin (value1,value2)', }, { - requirement: {key: 'key1', operator: 'GreaterThan', values: ['666.999']}, - string: 'key1 > 666.999', + requirement: { key: 'key1', operator: 'GreaterThan', values: ['666.999'] }, + string: 'key1 > 666.999', }, { - requirement: {key: 'key1', operator: 'LessThan', values: ['666.999']}, - string: 'key1 < 666.999', + requirement: { key: 'key1', operator: 'LessThan', values: ['666.999'] }, + string: 'key1 < 666.999', }, - ].forEach(t => { + ].forEach((t) => { it(`returns string for ${JSON.stringify(t.requirement)} requirement`, () => { expect(requirementToString(t.requirement)).toEqual(t.string); }); }); it('returns falsy for unknown requirement', () => { - expect(requirementToString({key: 'key1', operator: 'Oops!', values: ['value1']})).toBeFalsy(); + expect( + requirementToString({ key: 'key1', operator: 'Oops!', values: ['value1'] }), + ).toBeFalsy(); }); }); describe('#createEquals', () => { it('returns "Equals" requirement object', () => { expect(createEquals('Key', 'Value')).toEqual({ - key: 'Key', + key: 'Key', operator: 'Equals', - values: ['Value'], + values: ['Value'], }); }); }); diff --git a/frontend/__tests__/selector.spec.js b/frontend/__tests__/selector.spec.js index c120847a9a6..f02e7f64d1a 100644 --- a/frontend/__tests__/selector.spec.js +++ b/frontend/__tests__/selector.spec.js @@ -1,16 +1,25 @@ -import { fromRequirements, selectorFromString, toRequirements, selectorToString } from '../public/module/k8s/selector'; +import { + fromRequirements, + selectorFromString, + toRequirements, + selectorToString, +} from '../public/module/k8s/selector'; describe('k8sSelector', () => { describe('#selectorFromString', () => { it('works for nullable', () => { expect(selectorFromString(null)).toEqual({ - matchLabels: {}, + matchLabels: {}, matchExpressions: [], }); }); it('works for complex expression', () => { - expect(selectorFromString('key1=value1,key2=value2,key3,!key4,key5 in (value5),key6 in (value6.1,value6.2),key7 notin (value7),key8 notin (value8.1,value8.2)')).toEqual({ + expect( + selectorFromString( + 'key1=value1,key2=value2,key3,!key4,key5 in (value5),key6 in (value6.1,value6.2),key7 notin (value7),key8 notin (value8.1,value8.2)', + ), + ).toEqual({ matchLabels: { key1: 'value1', key2: 'value2', @@ -18,39 +27,39 @@ describe('k8sSelector', () => { matchExpressions: [ { - key: 'key3', + key: 'key3', operator: 'Exists', - values: [], + values: [], }, { - key: 'key4', + key: 'key4', operator: 'DoesNotExist', - values: [], + values: [], }, { - key: 'key5', + key: 'key5', operator: 'In', - values: ['value5'], + values: ['value5'], }, { - key: 'key6', + key: 'key6', operator: 'In', - values: ['value6.1', 'value6.2'], + values: ['value6.1', 'value6.2'], }, { - key: 'key7', + key: 'key7', operator: 'NotIn', - values: ['value7'], + values: ['value7'], }, { - key: 'key8', + key: 'key8', operator: 'NotIn', - values: ['value8.1', 'value8.2'], + values: ['value8.1', 'value8.2'], }, ], }); @@ -65,61 +74,67 @@ describe('k8sSelector', () => { describe('#fromRequirements', () => { it('returns undefined given no requirements and undefinedWhenEmpty option', () => { - expect(fromRequirements([], {undefinedWhenEmpty: true})).toBeUndefined(); + expect(fromRequirements([], { undefinedWhenEmpty: true })).toBeUndefined(); }); }); describe('#selectorToString', () => { it('works when both "matchLabels" and "matchExpressions" are given', () => { - expect(selectorToString({ - matchLabels: { - key1: 'value1', - key2: 'value2', - }, - - matchExpressions: [ - { - key: 'key3', - operator: 'Exists', - }, - - { - key: 'key4', - operator: 'DoesNotExist', - }, - - { - key: 'key5', - operator: 'In', - values: 'value5', - }, - - { - key: 'key6', - operator: 'In', - values: ['value6.1', 'value6.2'], + expect( + selectorToString({ + matchLabels: { + key1: 'value1', + key2: 'value2', }, - { - key: 'key7', - operator: 'NotIn', - values: 'value7', - }, - - { - key: 'key8', - operator: 'NotIn', - values: ['value8.1', 'value8.2'], - }, - ], - })).toEqual('key1=value1,key2=value2,key3,!key4,key5 in (value5),key6 in (value6.1,value6.2),key7 notin (value7),key8 notin (value8.1,value8.2)'); + matchExpressions: [ + { + key: 'key3', + operator: 'Exists', + }, + + { + key: 'key4', + operator: 'DoesNotExist', + }, + + { + key: 'key5', + operator: 'In', + values: 'value5', + }, + + { + key: 'key6', + operator: 'In', + values: ['value6.1', 'value6.2'], + }, + + { + key: 'key7', + operator: 'NotIn', + values: 'value7', + }, + + { + key: 'key8', + operator: 'NotIn', + values: ['value8.1', 'value8.2'], + }, + ], + }), + ).toEqual( + 'key1=value1,key2=value2,key3,!key4,key5 in (value5),key6 in (value6.1,value6.2),key7 notin (value7),key8 notin (value8.1,value8.2)', + ); }); it('works when V1 selector is given', () => { - expect(selectorToString({ - key1: 'value1', - key2: 'value2', - })).toEqual('key1=value1,key2=value2'); + expect( + selectorToString({ + key1: 'value1', + key2: 'value2', + }), + ).toEqual('key1=value1,key2=value2'); }); }); }); diff --git a/frontend/__tests__/units.spec.js b/frontend/__tests__/units.spec.js index cd7e3bd3a3d..25d263b06f9 100644 --- a/frontend/__tests__/units.spec.js +++ b/frontend/__tests__/units.spec.js @@ -1,6 +1,11 @@ import * as _ from 'lodash-es'; -import { units, validate, convertToBaseValue, humanizePercentage } from '../public/components/utils/units'; +import { + units, + validate, + convertToBaseValue, + humanizePercentage, +} from '../public/components/utils/units'; describe('units', () => { describe('round', () => { @@ -11,9 +16,9 @@ describe('units', () => { }; testRound(NaN, 0); - testRound(.101010, .101); + testRound(0.10101, 0.101); testRound(0, 0); - testRound(0.727272, .727); + testRound(0.727272, 0.727); testRound(1, 1); testRound(1.727272, 1.73); testRound(9.991234, 9.99); @@ -21,7 +26,7 @@ describe('units', () => { testRound(10.72727, 10.73); testRound(99.99123, 99.99); testRound(100, 100); - testRound(100.101010, 100.10); + testRound(100.10101, 100.1); testRound(111.119, 111.1); testRound(999.999, 1000); testRound(1023.12345, 1023.1); @@ -39,8 +44,8 @@ describe('units', () => { test_('banana', 0, '0'); test_(-1, -1, '-1'); test_(-0, -0, '0'); - test_(1/0, 0, '0'); - test_(-1/0, 0, '0'); + test_(1 / 0, 0, '0'); + test_(-1 / 0, 0, '0'); test_('100$', 0, '0'); test_(Number.MIN_VALUE, Number.MIN_VALUE, '0'); test_(0, 0, '0'); @@ -78,8 +83,8 @@ describe('units', () => { test_('banana', '0%'); test_(-1, '-1%'); test_(-0, '0%'); - test_(1/0, '0%'); - test_(-1/0, '0%'); + test_(1 / 0, '0%'); + test_(-1 / 0, '0%'); test_('100$', '0%'); test_(Number.MIN_VALUE, '0%'); test_(0, '0%'); @@ -90,11 +95,11 @@ describe('units', () => { test_(123, '123%'); test_(123.123, '123.1%'); test_(999.999, '1,000%'); - test_(1.000, '1%'); + test_(1.0, '1%'); test_(1.001, '1%'); test_(1.011, '1%'); test_(5.123, '5.1%'); - test_(10.000, '10%'); + test_(10.0, '10%'); test_(10.234, '10.2%'); test_(100, '100%'); test_(1000, '1,000%'); @@ -117,8 +122,8 @@ describe('units', () => { test_('banana', '0 B'); test_(-1, '-1 B'); test_(-0, '0 B'); - test_(1/0, '0 B'); - test_(-1/0, '0 B'); + test_(1 / 0, '0 B'); + test_(-1 / 0, '0 B'); test_('100$', '0 B'); test_(Number.MIN_VALUE, '0 B'); test_(0, '0 B'); @@ -155,8 +160,8 @@ describe('units', () => { test_('banana', '0 B'); test_(-1, '-1 B'); test_(-0, '0 B'); - test_(1/0, '0 B'); - test_(-1/0, '0 B'); + test_(1 / 0, '0 B'); + test_(-1 / 0, '0 B'); test_('100$', '0 B'); test_(Number.MIN_VALUE, '0 B'); test_(0, '0 B'); @@ -193,8 +198,8 @@ describe('units', () => { test_('banana', '0 i'); test_(-1, '-1 i'); test_(-0, '0 i'); - test_(1/0, '0 i'); - test_(-1/0, '0 i'); + test_(1 / 0, '0 i'); + test_(-1 / 0, '0 i'); test_('100$', '0 i'); test_(Number.MIN_VALUE, '0 i'); test_(0, '0 i'); @@ -232,8 +237,8 @@ describe('units', () => { test_(-1, -1); test_(-0, -0); test_(0, 0); - test_(1/0, 1/0); - test_(-1/0, -1/0); + test_(1 / 0, 1 / 0); + test_(-1 / 0, -1 / 0); test_('100$', '100$'); test_(Number.MIN_VALUE, Number.MIN_VALUE); test_('0i', 0); @@ -271,8 +276,8 @@ describe('units', () => { test_(-1, -1); test_(-0, -0); test_(0, 0); - test_(1/0, 1/0); - test_(-1/0, -1/0); + test_(1 / 0, 1 / 0); + test_(-1 / 0, -1 / 0); test_(Number.MIN_VALUE, Number.MIN_VALUE); test_('0', 0); test_(NaN, NaN); @@ -283,30 +288,30 @@ describe('units', () => { describe('validate', () => { it('memory', () => { - ['32', '32M', '32Mi'].forEach(v => { + ['32', '32M', '32Mi'].forEach((v) => { expect(validate.memory(v)).toEqual(undefined); }); - ['32m','32 Mi', ' 32Mi', '32Mii', '32e6', '32m4', 'a32m4'].forEach(v => { + ['32m', '32 Mi', ' 32Mi', '32Mii', '32e6', '32m4', 'a32m4'].forEach((v) => { expect(_.isString(validate.memory(v))).toEqual(true); }); }); it('cpu', () => { - ['32', '32m'].forEach(v => { + ['32', '32m'].forEach((v) => { expect(validate.CPU(v)).toEqual(undefined); }); - ['-1', '32mi','32 m', ' 32m', '32mm', '32e6', '32m4', '32M', '32Mi'].forEach(v => { + ['-1', '32mi', '32 m', ' 32m', '32mm', '32e6', '32m4', '32M', '32Mi'].forEach((v) => { expect(_.isString(validate.CPU(v))).toEqual(true); }); }); it('time', () => { - ['32h', '32s', '32m', '1h'].forEach(v => { + ['32h', '32s', '32m', '1h'].forEach((v) => { expect(validate.time(v)).toEqual(undefined); }); - ['-1', '32mi','32 m', ' 32m', '32mm', '32e6', '32m4'].forEach(v => { + ['-1', '32mi', '32 m', ' 32m', '32mm', '32e6', '32m4'].forEach((v) => { expect(_.isString(validate.time(v))).toEqual(true); }); }); diff --git a/frontend/integration-tests/protractor.conf.ts b/frontend/integration-tests/protractor.conf.ts index c267de0fe01..8231fab708a 100644 --- a/frontend/integration-tests/protractor.conf.ts +++ b/frontend/integration-tests/protractor.conf.ts @@ -11,15 +11,32 @@ import { format } from 'util'; const tap = !!process.env.TAP; export const BROWSER_TIMEOUT = 15000; -export const appHost = `${process.env.BRIDGE_BASE_ADDRESS || 'http://localhost:9000'}${(process.env.BRIDGE_BASE_PATH || '/').replace(/\/$/, '')}`; -export const testName = `test-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 5)}`; +export const appHost = `${process.env.BRIDGE_BASE_ADDRESS || 'http://localhost:9000'}${( + process.env.BRIDGE_BASE_PATH || '/' +).replace(/\/$/, '')}`; +export const testName = `test-${Math.random() + .toString(36) + .replace(/[^a-z]+/g, '') + .substr(0, 5)}`; export const screenshotsDir = 'gui_test_screenshots'; -const htmlReporter = new HtmlScreenshotReporter({ dest: `./${screenshotsDir}`, inlineImages: true, captureOnlyFailedSpecs: true, filename: 'test-gui-report.html' }); -const junitReporter = new JUnitXmlReporter({ savePath: `./${screenshotsDir}`, consolidateAll: true }); +const htmlReporter = new HtmlScreenshotReporter({ + dest: `./${screenshotsDir}`, + inlineImages: true, + captureOnlyFailedSpecs: true, + filename: 'test-gui-report.html', +}); +const junitReporter = new JUnitXmlReporter({ + savePath: `./${screenshotsDir}`, + consolidateAll: true, +}); const browserLogs: logging.Entry[] = []; -const suite = (tests: string[]) => (!_.isNil(process.env.BRIDGE_KUBEADMIN_PASSWORD) ? ['tests/login.scenario.ts'] : []).concat(['tests/base.scenario.ts', ...tests]); +const suite = (tests: string[]) => + (!_.isNil(process.env.BRIDGE_KUBEADMIN_PASSWORD) ? ['tests/login.scenario.ts'] : []).concat([ + 'tests/base.scenario.ts', + ...tests, + ]); export const config: Config = { framework: 'jasmine', @@ -50,13 +67,16 @@ export const config: Config = { '--disable-dev-shm-usage', ], prefs: { + // eslint-disable-next-line camelcase 'profile.password_manager_enabled': false, - 'credentials_enable_service': false, - 'password_manager_enabled': false, + // eslint-disable-next-line camelcase + credentials_enable_service: false, + // eslint-disable-next-line camelcase + password_manager_enabled: false, }, }, }, - beforeLaunch: () => new Promise(resolve => htmlReporter.beforeLaunch(resolve)), + beforeLaunch: () => new Promise((resolve) => htmlReporter.beforeLaunch(resolve)), onPrepare: () => { const addReporter = (jasmine as any).getEnv().addReporter; browser.waitForAngularEnabled(false); @@ -68,9 +88,9 @@ export const config: Config = { addReporter(new ConsoleReporter()); } }, - onComplete: async() => { + onComplete: async () => { const consoleLogStream = createWriteStream(`${screenshotsDir}/browser.log`, { flags: 'a' }); - browserLogs.forEach(log => { + browserLogs.forEach((log) => { const { level, message } = log; const messageStr = _.isArray(message) ? message.join(' ') : message; consoleLogStream.write(`${format.apply(null, [`[${level.name}]`, messageStr])}\n`); @@ -81,30 +101,23 @@ export const config: Config = { // Use projects if OpenShift so non-admin users can run tests. We need the fully-qualified name // since we're using kubectl instead of oc. - const resource = browser.params.openshift === 'true' ? 'projects.project.openshift.io' : 'namespaces'; + const resource = + browser.params.openshift === 'true' ? 'projects.project.openshift.io' : 'namespaces'; await browser.close(); - execSync(`if kubectl get ${resource} ${testName} 2> /dev/null; then kubectl delete ${resource} ${testName}; fi`); + execSync( + `if kubectl get ${resource} ${testName} 2> /dev/null; then kubectl delete ${resource} ${testName}; fi`, + ); }, afterLaunch: (exitCode) => { failFast.clean(); - return new Promise(resolve => htmlReporter.afterLaunch(resolve.bind(this, exitCode))); + return new Promise((resolve) => htmlReporter.afterLaunch(resolve.bind(this, exitCode))); }, suites: { - filter: suite([ - 'tests/filter.scenario.ts', - ]), - annotation: suite([ - 'tests/modal-annotations.scenario.ts', - ]), - environment: suite([ - 'tests/environment.scenario.ts', - ]), - secrets: suite([ - 'tests/secrets.scenario.ts', - ]), - storage: suite([ - 'tests/storage.scenario.ts', - ]), + filter: suite(['tests/filter.scenario.ts']), + annotation: suite(['tests/modal-annotations.scenario.ts']), + environment: suite(['tests/environment.scenario.ts']), + secrets: suite(['tests/secrets.scenario.ts']), + storage: suite(['tests/storage.scenario.ts']), crud: suite([ 'tests/crud.scenario.ts', 'tests/secrets.scenario.ts', @@ -112,22 +125,15 @@ export const config: Config = { 'tests/modal-annotations.scenario.ts', 'tests/environment.scenario.ts', ]), - monitoring: suite([ - 'tests/monitoring.scenario.ts', - ]), - newApp: suite([ - 'tests/overview/overview.scenario.ts', - 'tests/deploy-image.scenario.ts', - ]), + monitoring: suite(['tests/monitoring.scenario.ts']), + newApp: suite(['tests/overview/overview.scenario.ts', 'tests/deploy-image.scenario.ts']), olmFull: suite([ '../packages/operator-lifecycle-manager/integration-tests/scenarios/descriptors.scenario.ts', '../packages/operator-lifecycle-manager/integration-tests/scenarios/operator-hub.scenario.ts', '../packages/operator-lifecycle-manager/integration-tests/scenarios/global-installmode.scenario.ts', '../packages/operator-lifecycle-manager/integration-tests/scenarios/single-installmode.scenario.ts', ]), - performance: suite([ - 'tests/performance.scenario.ts', - ]), + performance: suite(['tests/performance.scenario.ts']), serviceCatalog: suite([ 'tests/service-catalog/service-catalog.scenario.ts', 'tests/service-catalog/service-broker.scenario.ts', @@ -135,12 +141,8 @@ export const config: Config = { 'tests/service-catalog/service-binding.scenario.ts', 'tests/developer-catalog.scenario.ts', ]), - overview: suite([ - 'tests/overview/overview.scenario.ts', - ]), - crdExtensions: suite([ - 'tests/crd-extensions.scenario.ts', - ]), + overview: suite(['tests/overview/overview.scenario.ts']), + crdExtensions: suite(['tests/crd-extensions.scenario.ts']), e2e: suite([ 'tests/crud.scenario.ts', 'tests/secrets.scenario.ts', @@ -188,12 +190,8 @@ export const config: Config = { 'tests/devconsole/git-import-flow.scenario.ts', 'tests/crd-extensions.scenario.ts', ]), - clusterSettings: suite([ - 'tests/cluster-settings.scenario.ts', - ]), - login: [ - 'tests/login.scenario.ts', - ], + clusterSettings: suite(['tests/cluster-settings.scenario.ts']), + login: ['tests/login.scenario.ts'], devconsole: [ 'tests/devconsole/dev-perspective.scenario.ts', 'tests/devconsole/git-import-flow.scenario.ts', @@ -209,8 +207,11 @@ export const config: Config = { }, }; -export const checkLogs = async() => (await browser.manage().logs().get('browser')) - .map(log => { +export const checkLogs = async () => + (await browser + .manage() + .logs() + .get('browser')).map((log) => { browserLogs.push(log); return log; }); @@ -218,28 +219,31 @@ export const checkLogs = async() => (await browser.manage().logs().get('browser' function hasError() { return window.windowError; } -export const checkErrors = async() => await browser.executeScript(hasError).then(err => { - if (err) { - fail(`omg js error: ${err}`); - } -}); +export const checkErrors = async () => + await browser.executeScript(hasError).then((err) => { + if (err) { + fail(`omg js error: ${err}`); + } + }); export const waitForCount = (elementArrayFinder, expectedCount) => { - return async() => { + return async () => { const actualCount = await elementArrayFinder.count(); return expectedCount >= actualCount; }; }; export const waitForNone = (elementArrayFinder) => { - return async() => { + return async () => { const count = await elementArrayFinder.count(); return count === 0; }; }; export const create = (obj) => { - const filename = [screenshotsDir, `${obj.metadata.name}.${obj.kind.toLowerCase()}.json`].join('/'); + const filename = [screenshotsDir, `${obj.metadata.name}.${obj.kind.toLowerCase()}.json`].join( + '/', + ); writeFileSync(filename, JSON.stringify(obj)); execSync(`kubectl create -f ${filename}`); execSync(`rm ${filename}`); diff --git a/frontend/integration-tests/tests/base.scenario.ts b/frontend/integration-tests/tests/base.scenario.ts index e99ecf53452..d3473b2407f 100644 --- a/frontend/integration-tests/tests/base.scenario.ts +++ b/frontend/integration-tests/tests/base.scenario.ts @@ -6,7 +6,7 @@ import * as crudView from '../views/crud.view'; const BROWSER_TIMEOUT = 15000; describe('Create a test namespace', () => { - it(`creates test namespace ${testName} if necessary`, async() => { + it(`creates test namespace ${testName} if necessary`, async () => { // Use projects if OpenShift so non-admin users can run tests. const resource = browser.params.openshift === 'true' ? 'projects' : 'namespaces'; await browser.get(`${appHost}/k8s/cluster/${resource}`); @@ -15,9 +15,17 @@ describe('Create a test namespace', () => { if (!exists) { await crudView.createYAMLButton.click(); await browser.wait(until.presenceOf($('.modal-body__field'))); - await $$('.modal-body__field').get(0).$('input').sendKeys(testName); - await $$('.modal-body__field').get(1).$('input').sendKeys(`test-name=${testName}`); - await $('.modal-content').$('#confirm-action').click(); + await $$('.modal-body__field') + .get(0) + .$('input') + .sendKeys(testName); + await $$('.modal-body__field') + .get(1) + .$('input') + .sendKeys(`test-name=${testName}`); + await $('.modal-content') + .$('#confirm-action') + .click(); await browser.wait(until.urlContains(`/${testName}`), BROWSER_TIMEOUT); } expect(browser.getCurrentUrl()).toContain(appHost); diff --git a/frontend/integration-tests/tests/cluster-settings.scenario.ts b/frontend/integration-tests/tests/cluster-settings.scenario.ts index ebc982db0e3..3b3fe6f4fed 100644 --- a/frontend/integration-tests/tests/cluster-settings.scenario.ts +++ b/frontend/integration-tests/tests/cluster-settings.scenario.ts @@ -1,13 +1,12 @@ -import { browser} from 'protractor'; +import { browser } from 'protractor'; import { appHost, checkLogs, checkErrors, testName } from '../protractor.conf'; import * as crudView from '../views/crud.view'; import * as clusterSettingsView from '../views/cluster-settings.view'; import * as horizontalnavView from '../views/horizontal-nav.view'; - describe('Cluster Settings', () => { - beforeEach(async() => { + beforeEach(async () => { await browser.get(`${appHost}/settings/cluster/${testName}`); await crudView.isLoaded(); }); @@ -17,7 +16,7 @@ describe('Cluster Settings', () => { checkErrors(); }); - it('display page title, horizontal navigation tab headings and pages', async() => { + it('display page title, horizontal navigation tab headings and pages', async () => { expect(clusterSettingsView.heading.isPresent()).toBe(true); await horizontalnavView.clickHorizontalTab('Overview'); await crudView.isLoaded(); @@ -26,7 +25,7 @@ describe('Cluster Settings', () => { await horizontalnavView.clickHorizontalTab('Global Configuration'); await crudView.isLoaded(); }); - it('display overview channel update modal, select value and click cancel', async() => { + it('display overview channel update modal, select value and click cancel', async () => { await horizontalnavView.clickHorizontalTab('Overview'); await crudView.isLoaded(); expect(clusterSettingsView.channelUpdateLink.isDisplayed()).toBe(true); @@ -44,8 +43,7 @@ describe('Cluster Settings', () => { await clusterSettingsView.channelPopupCancelButton.click(); await crudView.isLoaded(); }); - it('display Cluster Operators page, click resource item link to display details. Check if the resource link title equals the details page header', async()=>{ - + it('display Cluster Operators page, click resource item link to display details. Check if the resource link title equals the details page header', async () => { await horizontalnavView.clickHorizontalTab('Cluster Operators'); await crudView.isLoaded(); @@ -58,10 +56,8 @@ describe('Cluster Settings', () => { await horizontalnavView.clickHorizontalTab('YAML'); await crudView.isLoaded(); - }); - it('display Global Configuration page, click Configuration Resource item link and display details. Check if the resource link title equals the details page header', async() => { - + it('display Global Configuration page, click Configuration Resource item link and display details. Check if the resource link title equals the details page header', async () => { await horizontalnavView.clickHorizontalTab('Global Configuration'); await crudView.isLoaded(); @@ -74,10 +70,8 @@ describe('Cluster Settings', () => { await horizontalnavView.clickHorizontalTab('YAML'); await crudView.isLoaded(); - }); - it('display Global Configuration page, click dropdown link to edit resource and display details, and check if details header is correct.', async() => { - + it('display Global Configuration page, click dropdown link to edit resource and display details, and check if details header is correct.', async () => { await horizontalnavView.clickHorizontalTab('Global Configuration'); await crudView.isLoaded(); @@ -91,8 +85,7 @@ describe('Cluster Settings', () => { expect(clusterSettingsView.clusterResourceDetailsTitle.isDisplayed()).toBe(true); expect(clusterSettingsView.clusterResourceDetailsTitle.getText()).toBe('cluster'); }); - it('display Global Configuration page, click Explore Console API in dropdown link and display details, and check if details header is correct.', async() => { - + it('display Global Configuration page, click Explore Console API in dropdown link and display details, and check if details header is correct.', async () => { await horizontalnavView.clickHorizontalTab('Global Configuration'); await crudView.isLoaded(); diff --git a/frontend/integration-tests/tests/crd-extensions.scenario.ts b/frontend/integration-tests/tests/crd-extensions.scenario.ts index 95510c2a554..d3638cd23c6 100644 --- a/frontend/integration-tests/tests/crd-extensions.scenario.ts +++ b/frontend/integration-tests/tests/crd-extensions.scenario.ts @@ -25,12 +25,13 @@ describe('CRD extensions', () => { }, spec: { displayName: name, - description: 'This is an example CLI download description that can include markdown such as paragraphs, unordered lists, code, [links](https://www.example.com), etc.', - links: [{href: 'https://www.example.com'}], + description: + 'This is an example CLI download description that can include markdown such as paragraphs, unordered lists, code, [links](https://www.example.com), etc.', + links: [{ href: 'https://www.example.com' }], }, }; - it(`displays YAML editor for creating a new ${crd} instance`, async() => { + it(`displays YAML editor for creating a new ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); await crudView.isLoaded(); await crudView.clickKebabAction(crd, 'View Instances'); @@ -41,24 +42,24 @@ describe('CRD extensions', () => { expect(yamlView.getEditorContent()).toContain(`kind: ${crd}`); }); - it(`creates a new ${crd} instance`, async() => { + it(`creates a new ${crd} instance`, async () => { await yamlView.saveButton.click(); expect(crudView.errorMessage.isPresent()).toBe(false); }); - it(`displays detail view for ${crd} instance`, async() => { + it(`displays detail view for ${crd} instance`, async () => { await browser.wait(until.presenceOf(crudView.resourceTitle)); expect(browser.getCurrentUrl()).toContain(`/${name}`); expect(crudView.resourceTitle.getText()).toEqual(name); }); - it(`displays the ${crd} instance on the Command Line Tools page`, async() => { + it(`displays the ${crd} instance on the Command Line Tools page`, async () => { await browser.get(`${appHost}/command-line-tools`); await browser.wait(until.presenceOf($(`[data-test-id=${name}]`))); expect($(`[data-test-id=${name}]`).getText()).toEqual(name); }); - it(`deletes the ${crd} instance`, async() => { + it(`deletes the ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); await crudView.isLoaded(); await crudView.clickKebabAction(crd, 'View Instances'); @@ -73,63 +74,87 @@ describe('CRD extensions', () => { describe('ConsoleLink CRD', () => { const crd = 'ConsoleLink'; const name = `${testName}-cl`; - const testObjs = [{ - name, - dropdownMenuName: 'help menu', - dropdownToggle: $('[data-test=help-dropdown-toggle] .pf-c-app-launcher__toggle'), - menuLinkLocation: 'HelpMenu', - menuLinkText: `${name} help menu link`, - }, { - name, - dropdownMenuName: 'user menu', - dropdownToggle: $('[data-test=user-dropdown] .pf-c-app-launcher__toggle'), - menuLinkLocation: 'UserMenu', - menuLinkText: `${name} user menu link`, - }]; - - testObjs.forEach(({name: instanceName, dropdownMenuName, dropdownToggle, menuLinkLocation, menuLinkText}) => { - it(`displays YAML editor for creating a new ${crd} ${dropdownMenuName} instance`, async() => { - await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); - await crudView.isLoaded(); - await crudView.clickKebabAction(crd, 'View Instances'); - await crudView.isLoaded(); - await crudView.createYAMLButton.click(); - await yamlView.isLoaded(); - const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name: instanceName}, spec: {location: menuLinkLocation, text: menuLinkText}}, safeLoad(content)); - await yamlView.setEditorContent(safeDump(newContent)); - expect(yamlView.getEditorContent()).toContain(`kind: ${crd}`); - }); - - it(`creates a new ${crd} ${dropdownMenuName} instance`, async() => { - await yamlView.saveButton.click(); - expect(crudView.errorMessage.isPresent()).toBe(false); - }); - - it(`displays detail view for ${crd} ${dropdownMenuName} instance`, async() => { - await browser.wait(until.presenceOf(crudView.resourceTitle)); - expect(browser.getCurrentUrl()).toContain(`/${instanceName}`); - expect(crudView.resourceTitle.getText()).toEqual(instanceName); - }); - - it(`displays the ${crd} instance in the ${dropdownMenuName}`, async() => { - await browser.get(`${appHost}`); - // reload the app so the new CRD link is visible - await browser.refresh(); - await browser.wait(until.presenceOf(dropdownToggle)); - await browser.wait(dropdownToggle.click()); - await browser.wait(until.presenceOf(element(by.cssContainingText('.pf-c-app-launcher__menu-item', menuLinkText)))); - expect(element(by.cssContainingText('.pf-c-app-launcher__menu-item', menuLinkText)).getText()).toContain(menuLinkText); - }); - - it(`deletes the ${crd} ${dropdownMenuName} instance`, async() => { - await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); - await crudView.isLoaded(); - await crudView.clickKebabAction(crd, 'View Instances'); - await crudView.resourceRowsPresent(); - await crudView.deleteRow(crd)(instanceName); - }); - }); + const testObjs = [ + { + name, + dropdownMenuName: 'help menu', + dropdownToggle: $('[data-test=help-dropdown-toggle] .pf-c-app-launcher__toggle'), + menuLinkLocation: 'HelpMenu', + menuLinkText: `${name} help menu link`, + }, + { + name, + dropdownMenuName: 'user menu', + dropdownToggle: $('[data-test=user-dropdown] .pf-c-app-launcher__toggle'), + menuLinkLocation: 'UserMenu', + menuLinkText: `${name} user menu link`, + }, + ]; + + testObjs.forEach( + ({ + name: instanceName, + dropdownMenuName, + dropdownToggle, + menuLinkLocation, + menuLinkText, + }) => { + it(`displays YAML editor for creating a new ${crd} ${dropdownMenuName} instance`, async () => { + await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); + await crudView.isLoaded(); + await crudView.clickKebabAction(crd, 'View Instances'); + await crudView.isLoaded(); + await crudView.createYAMLButton.click(); + await yamlView.isLoaded(); + const content = await yamlView.getEditorContent(); + const newContent = _.defaultsDeep( + {}, + { + metadata: { name: instanceName }, + spec: { location: menuLinkLocation, text: menuLinkText }, + }, + safeLoad(content), + ); + await yamlView.setEditorContent(safeDump(newContent)); + expect(yamlView.getEditorContent()).toContain(`kind: ${crd}`); + }); + + it(`creates a new ${crd} ${dropdownMenuName} instance`, async () => { + await yamlView.saveButton.click(); + expect(crudView.errorMessage.isPresent()).toBe(false); + }); + + it(`displays detail view for ${crd} ${dropdownMenuName} instance`, async () => { + await browser.wait(until.presenceOf(crudView.resourceTitle)); + expect(browser.getCurrentUrl()).toContain(`/${instanceName}`); + expect(crudView.resourceTitle.getText()).toEqual(instanceName); + }); + + it(`displays the ${crd} instance in the ${dropdownMenuName}`, async () => { + await browser.get(`${appHost}`); + // reload the app so the new CRD link is visible + await browser.refresh(); + await browser.wait(until.presenceOf(dropdownToggle)); + await browser.wait(dropdownToggle.click()); + await browser.wait( + until.presenceOf( + element(by.cssContainingText('.pf-c-app-launcher__menu-item', menuLinkText)), + ), + ); + expect( + element(by.cssContainingText('.pf-c-app-launcher__menu-item', menuLinkText)).getText(), + ).toContain(menuLinkText); + }); + + it(`deletes the ${crd} ${dropdownMenuName} instance`, async () => { + await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); + await crudView.isLoaded(); + await crudView.clickKebabAction(crd, 'View Instances'); + await crudView.resourceRowsPresent(); + await crudView.deleteRow(crd)(instanceName); + }); + }, + ); }); describe('ConsoleNotification CRD', () => { @@ -139,7 +164,7 @@ describe('CRD extensions', () => { let text = `${name} notification that appears ${location}`; let notification = $(`[data-test=${name}-${location}]`); - it(`displays YAML editor for creating a new ${crd} instance`, async() => { + it(`displays YAML editor for creating a new ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); await crudView.isLoaded(); await crudView.clickKebabAction(crd, 'View Instances'); @@ -147,34 +172,38 @@ describe('CRD extensions', () => { await crudView.createYAMLButton.click(); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name}, spec: {location, text}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name }, spec: { location, text } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); expect(yamlView.getEditorContent()).toContain(`kind: ${crd}`); }); - it(`creates a new ${crd} instance`, async() => { + it(`creates a new ${crd} instance`, async () => { await yamlView.saveButton.click(); expect(crudView.errorMessage.isPresent()).toBe(false); }); - it(`displays detail view for ${crd} instance`, async() => { + it(`displays detail view for ${crd} instance`, async () => { await browser.wait(until.presenceOf(crudView.resourceTitle)); expect(browser.getCurrentUrl()).toContain(`/${name}`); expect(crudView.resourceTitle.getText()).toEqual(name); }); - it(`displays the ${crd} instance`, async() => { + it(`displays the ${crd} instance`, async () => { await browser.wait(until.presenceOf(notification)); expect(notification.getText()).toContain(text); }); - it(`displays YAML editor for modifying the location of ${crd} instance`, async() => { + it(`displays YAML editor for modifying the location of ${crd} instance`, async () => { location = 'BannerBottom'; text = `${name} notification that appears ${location}`; await browser.getCurrentUrl().then((url) => browser.get(`${url}/yaml`)); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {spec: {location, text}}, safeLoad(content)); + const newContent = _.defaultsDeep({}, { spec: { location, text } }, safeLoad(content)); await yamlView.setEditorContent(safeDump(newContent)); expect(yamlView.getEditorContent()).toContain(`location: ${location}`); await yamlView.saveButton.click(); @@ -182,7 +211,7 @@ describe('CRD extensions', () => { expect(crudView.successMessage.isPresent()).toBe(true); }); - it(`displays the ${crd} instance in its new location`, async() => { + it(`displays the ${crd} instance in its new location`, async () => { location = 'BannerBottom'; notification = $(`[data-test=${name}-${location}]`); text = `${name} notification that appears ${location}`; @@ -190,7 +219,7 @@ describe('CRD extensions', () => { expect(notification.getText()).toContain(text); }); - it(`deletes the ${crd} instance`, async() => { + it(`deletes the ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); await crudView.isLoaded(); await crudView.clickKebabAction(crd, 'View Instances'); @@ -207,7 +236,7 @@ describe('CRD extensions', () => { const text = `${name} Logs`; const namespaceFilter = '^openshift-'; - it(`displays YAML editor for creating a new ${crd} instance`, async() => { + it(`displays YAML editor for creating a new ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); await crudView.isLoaded(); await crudView.clickKebabAction(crd, 'View Instances'); @@ -215,42 +244,50 @@ describe('CRD extensions', () => { await crudView.createYAMLButton.click(); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name}, spec: {text}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name }, spec: { text } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); expect(yamlView.getEditorContent()).toContain(`kind: ${crd}`); }); - it(`creates a new ${crd} instance`, async() => { + it(`creates a new ${crd} instance`, async () => { await yamlView.saveButton.click(); expect(crudView.errorMessage.isPresent()).toBe(false); }); - it(`displays detail view for ${crd} instance`, async() => { + it(`displays detail view for ${crd} instance`, async () => { await browser.wait(until.presenceOf(crudView.resourceTitle)); expect(browser.getCurrentUrl()).toContain(`/${name}`); expect(crudView.resourceTitle.getText()).toEqual(name); }); - it(`creates a new test pod to display the ${crd} instance`, async() => { + it(`creates a new test pod to display the ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/ns/${testName}/pods`); await crudView.isLoaded(); await crudView.createYAMLButton.click(); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name: podName, labels: {app: name}}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name: podName, labels: { app: name } } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); await yamlView.saveButton.click(); expect(crudView.errorMessage.isPresent()).toBe(false); }); - it(`displays the ${crd} instance on the test pod`, async() => { + it(`displays the ${crd} instance on the test pod`, async () => { await browser.wait(until.presenceOf(crudView.resourceTitle)); await browser.getCurrentUrl().then((url) => browser.get(`${url}/logs`)); await browser.wait(until.presenceOf(cell)); expect(cell.getText()).toContain(text); }); - it(`displays YAML editor for adding namespaceFilter to the ${crd} instance`, async() => { + it(`displays YAML editor for adding namespaceFilter to the ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); await crudView.isLoaded(); await crudView.clickKebabAction(crd, 'View Instances'); @@ -258,7 +295,7 @@ describe('CRD extensions', () => { await crudView.editRow(crd)(name); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {spec: {namespaceFilter}}, safeLoad(content)); + const newContent = _.defaultsDeep({}, { spec: { namespaceFilter } }, safeLoad(content)); await yamlView.setEditorContent(safeDump(newContent)); expect(yamlView.getEditorContent()).toContain(`namespaceFilter: ${namespaceFilter}`); await yamlView.saveButton.click(); @@ -266,19 +303,19 @@ describe('CRD extensions', () => { expect(crudView.successMessage.isPresent()).toBe(true); }); - it(`does not display the ${crd} instance on the test pod`, async() => { + it(`does not display the ${crd} instance on the test pod`, async () => { await browser.get(`${appHost}/k8s/ns/${testName}/pods/${podName}/logs`); await crudView.isLoaded(); expect(cell.isPresent()).toBe(false); }); - it('deletes the test pod', async() => { + it('deletes the test pod', async () => { await browser.get(`${appHost}/k8s/ns/${testName}/pods?name=${podName}`); await crudView.isLoaded(); await crudView.deleteRow('Pod')(podName); }); - it(`deletes the ${crd} instance`, async() => { + it(`deletes the ${crd} instance`, async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${crd}`); await crudView.isLoaded(); await crudView.clickKebabAction(crd, 'View Instances'); diff --git a/frontend/integration-tests/tests/crud.scenario.ts b/frontend/integration-tests/tests/crud.scenario.ts index f5d261fd6b9..c5f3427a935 100644 --- a/frontend/integration-tests/tests/crud.scenario.ts +++ b/frontend/integration-tests/tests/crud.scenario.ts @@ -17,36 +17,39 @@ const K8S_CREATION_TIMEOUT = 15000; describe('Kubernetes resource CRUD operations', () => { const testLabel = 'automatedTestName'; const leakedResources = new Set(); - const k8sObjs = OrderedMap() - .set('pods', {kind: 'Pod'}) - .set('services', {kind: 'Service'}) - .set('serviceaccounts', {kind: 'ServiceAccount'}) - .set('secrets', {kind: 'Secret'}) - .set('configmaps', {kind: 'ConfigMap'}) - .set('persistentvolumes', {kind: 'PersistentVolume', namespaced: false}) - .set('storageclasses', {kind: 'StorageClass', namespaced: false}) - .set('ingresses', {kind: 'Ingress'}) - .set('cronjobs', {kind: 'CronJob'}) - .set('jobs', {kind: 'Job'}) - .set('daemonsets', {kind: 'DaemonSet'}) - .set('deployments', {kind: 'Deployment'}) - .set('replicasets', {kind: 'ReplicaSet'}) - .set('replicationcontrollers', {kind: 'ReplicationController'}) - .set('persistentvolumeclaims', {kind: 'PersistentVolumeClaim'}) - .set('statefulsets', {kind: 'StatefulSet'}) - .set('resourcequotas', {kind: 'ResourceQuota'}) - .set('horizontalpodautoscalers', {kind: 'HorizontalPodAutoscaler'}) - .set('networkpolicies', {kind: 'NetworkPolicy'}) - .set('roles', {kind: 'Role'}); - const openshiftObjs = OrderedMap() - .set('deploymentconfigs', {kind: 'DeploymentConfig'}) - .set('buildconfigs', {kind: 'BuildConfig'}) - .set('imagestreams', {kind: 'ImageStream'}) - .set('routes', {kind: 'Route'}); - const serviceCatalogObjs = OrderedMap() - .set('clusterservicebrokers', {kind: 'servicecatalog.k8s.io~v1beta1~ClusterServiceBroker', namespaced: false}); + const k8sObjs = OrderedMap() + .set('pods', { kind: 'Pod' }) + .set('services', { kind: 'Service' }) + .set('serviceaccounts', { kind: 'ServiceAccount' }) + .set('secrets', { kind: 'Secret' }) + .set('configmaps', { kind: 'ConfigMap' }) + .set('persistentvolumes', { kind: 'PersistentVolume', namespaced: false }) + .set('storageclasses', { kind: 'StorageClass', namespaced: false }) + .set('ingresses', { kind: 'Ingress' }) + .set('cronjobs', { kind: 'CronJob' }) + .set('jobs', { kind: 'Job' }) + .set('daemonsets', { kind: 'DaemonSet' }) + .set('deployments', { kind: 'Deployment' }) + .set('replicasets', { kind: 'ReplicaSet' }) + .set('replicationcontrollers', { kind: 'ReplicationController' }) + .set('persistentvolumeclaims', { kind: 'PersistentVolumeClaim' }) + .set('statefulsets', { kind: 'StatefulSet' }) + .set('resourcequotas', { kind: 'ResourceQuota' }) + .set('horizontalpodautoscalers', { kind: 'HorizontalPodAutoscaler' }) + .set('networkpolicies', { kind: 'NetworkPolicy' }) + .set('roles', { kind: 'Role' }); + const openshiftObjs = OrderedMap() + .set('deploymentconfigs', { kind: 'DeploymentConfig' }) + .set('buildconfigs', { kind: 'BuildConfig' }) + .set('imagestreams', { kind: 'ImageStream' }) + .set('routes', { kind: 'Route' }); + const serviceCatalogObjs = OrderedMap().set( + 'clusterservicebrokers', + { kind: 'servicecatalog.k8s.io~v1beta1~ClusterServiceBroker', namespaced: false }, + ); let testObjs = browser.params.openshift === 'true' ? k8sObjs.merge(openshiftObjs) : k8sObjs; - testObjs = browser.params.servicecatalog === 'true' ? testObjs.merge(serviceCatalogObjs) : testObjs; + testObjs = + browser.params.servicecatalog === 'true' ? testObjs.merge(serviceCatalogObjs) : testObjs; afterEach(() => { checkLogs(); @@ -55,10 +58,13 @@ describe('Kubernetes resource CRUD operations', () => { afterAll(() => { const leakedArray: Array = [...leakedResources]; - console.error(`Leaked ${leakedArray.length} resources out of ${testObjs.size}:\n${leakedArray.join('\n')}`); - leakedArray.map(r => JSON.parse(r) as {name: string, plural: string, namespace?: string}) - .filter(r => r.namespace === undefined) - .forEach(({name, plural}) => { + console.error( + `Leaked ${leakedArray.length} resources out of ${testObjs.size}:\n${leakedArray.join('\n')}`, + ); + leakedArray + .map((r) => JSON.parse(r) as { name: string; plural: string; namespace?: string }) + .filter((r) => r.namespace === undefined) + .forEach(({ name, plural }) => { try { execSync(`kubectl delete --cascade ${plural} ${name}`); } catch (error) { @@ -67,27 +73,30 @@ describe('Kubernetes resource CRUD operations', () => { }); }); - testObjs.forEach(({kind, namespaced = true}, resource) => { - + testObjs.forEach(({ kind, namespaced = true }, resource) => { describe(kind, () => { const name = `${testName}-${kind.toLowerCase()}`; - it('displays a list view for the resource', async() => { - await browser.get(`${appHost}${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}?name=${testName}`); + it('displays a list view for the resource', async () => { + await browser.get( + `${appHost}${ + namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster' + }/${resource}?name=${testName}`, + ); await crudView.isLoaded(); }); if (namespaced) { - it('has a working namespace dropdown on namespaced objects', async() => { + it('has a working namespace dropdown on namespaced objects', async () => { await browser.wait(until.presenceOf(namespaceView.namespaceSelector)); expect(namespaceView.namespaceSelector.getText()).toContain(testName); }); } else { - it('does not have a namespace dropdown on non-namespaced objects', async() => { + it('does not have a namespace dropdown on non-namespaced objects', async () => { expect(namespaceView.namespaceSelector.isPresent()).toBe(false); }); } - it('displays a YAML editor for creating a new resource instance', async() => { + it('displays a YAML editor for creating a new resource instance', async () => { await crudView.clickListPageCreateYAMLButton(); const yamlLinkIsPresent = await crudView.createYAMLLink.isPresent(); if (yamlLinkIsPresent) { @@ -96,49 +105,70 @@ describe('Kubernetes resource CRUD operations', () => { await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name, labels: {[testLabel]: testName}}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name, labels: { [testLabel]: testName } } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); }); - it('creates a new resource instance', async() => { - leakedResources.add(JSON.stringify({name, plural: resource, namespace: namespaced ? testName : undefined})); + it('creates a new resource instance', async () => { + leakedResources.add( + JSON.stringify({ name, plural: resource, namespace: namespaced ? testName : undefined }), + ); await yamlView.saveButton.click(); expect(crudView.errorMessage.isPresent()).toBe(false); }); - it('displays detail view for new resource instance', async() => { + it('displays detail view for new resource instance', async () => { await browser.wait(until.presenceOf(crudView.resourceTitle)); expect(browser.getCurrentUrl()).toContain(`/${name}`); expect(crudView.resourceTitle.getText()).toEqual(name); }); - it('search view displays created resource instance', async() => { - await browser.get(`${appHost}/search/${namespaced ? `ns/${testName}` : 'all-namespaces'}?kind=${kind}&q=${testLabel}%3d${testName}`); + it('search view displays created resource instance', async () => { + await browser.get( + `${appHost}/search/${ + namespaced ? `ns/${testName}` : 'all-namespaces' + }?kind=${kind}&q=${testLabel}%3d${testName}`, + ); await crudView.resourceRowsPresent(); await crudView.filterForName(name); - await crudView.rowForName(name).element(by.linkText(name)).click(); + await crudView + .rowForName(name) + .element(by.linkText(name)) + .click(); await browser.wait(until.urlContains(`/${name}`)); expect(crudView.resourceTitle.getText()).toEqual(name); }); - it('edit the resource instance', async() => { + it('edit the resource instance', async () => { if (kind !== 'ServiceAccount') { - await browser.get(`${appHost}/search/${namespaced ? `ns/${testName}` : 'all-namespaces'}?kind=${kind}&q=${testLabel}%3d${testName}`); + await browser.get( + `${appHost}/search/${ + namespaced ? `ns/${testName}` : 'all-namespaces' + }?kind=${kind}&q=${testLabel}%3d${testName}`, + ); await crudView.filterForName(name); await crudView.resourceRowsPresent(); await crudView.editRow(kind)(name); } }); - it('deletes the resource instance', async() => { - await browser.get(`${appHost}${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}`); + it('deletes the resource instance', async () => { + await browser.get( + `${appHost}${namespaced ? `/k8s/ns/${testName}` : '/k8s/cluster'}/${resource}`, + ); await crudView.resourceRowsPresent(); // Filter by resource name to make sure the resource is on the first page of results. // Otherwise the tests fail since we do virtual scrolling and the element isn't found. await crudView.filterForName(name); await crudView.deleteRow(kind)(name); - leakedResources.delete(JSON.stringify({name, plural: resource, namespace: namespaced ? testName : undefined})); + leakedResources.delete( + JSON.stringify({ name, plural: resource, namespace: namespaced ? testName : undefined }), + ); }); }); }); @@ -146,14 +176,16 @@ describe('Kubernetes resource CRUD operations', () => { describe('Role Bindings', () => { const bindingName = `${testName}-cluster-admin`; const roleName = 'cluster-admin'; - it('displays "Create Role Binding" page', async() => { + it('displays "Create Role Binding" page', async () => { await browser.get(`${appHost}/k8s/all-namespaces/rolebindings`); await crudView.isLoaded(); await crudView.createYAMLButton.click(); - await browser.wait(until.textToBePresentInElement($('.co-m-pane__heading'), 'Create Role Binding')); + await browser.wait( + until.textToBePresentInElement($('.co-m-pane__heading'), 'Create Role Binding'), + ); }); - it('creates a RoleBinding', async() => { + it('creates a RoleBinding', async () => { await browser.wait(crudView.untilNoLoadersPresent); // Role Binding specific actions @@ -166,11 +198,15 @@ describe('Kubernetes resource CRUD operations', () => { await crudView.saveChangesBtn.click(); expect(crudView.errorMessage.isPresent()).toBe(false); - await browser.wait(until.presenceOf(element(by.cssContainingText('h1.co-m-pane__heading', bindingName)))); - leakedResources.add(JSON.stringify({name: bindingName, plural: 'rolebindings', namespace: testName})); + await browser.wait( + until.presenceOf(element(by.cssContainingText('h1.co-m-pane__heading', bindingName))), + ); + leakedResources.add( + JSON.stringify({ name: bindingName, plural: 'rolebindings', namespace: testName }), + ); }); - it('displays created RoleBinding in list view', async() => { + it('displays created RoleBinding in list view', async () => { await browser.get(`${appHost}/k8s/ns/${testName}/rolebindings`); await crudView.isLoaded(); await crudView.resourceRowsPresent(); @@ -180,42 +216,47 @@ describe('Kubernetes resource CRUD operations', () => { expect(crudView.rowForName(bindingName).isPresent()).toBe(true); }); - it('deletes the RoleBinding', async() => { + it('deletes the RoleBinding', async () => { await crudView.resourceRowsPresent(); await crudView.deleteRow('RoleBinding')(bindingName); - leakedResources.delete(JSON.stringify({name: bindingName, plural: 'rolebindings', namespace: testName})); + leakedResources.delete( + JSON.stringify({ name: bindingName, plural: 'rolebindings', namespace: testName }), + ); }); }); describe('Namespace', () => { const name = `${testName}-ns`; - it('displays `Namespace` list view', async() => { + it('displays `Namespace` list view', async () => { await browser.get(`${appHost}/k8s/cluster/namespaces`); await crudView.isLoaded(); expect(crudView.rowForName(name).isPresent()).toBe(false); }); - it('creates the namespace', async() => { + it('creates the namespace', async () => { await crudView.createYAMLButton.click(); await browser.wait(until.presenceOf($('.modal-body__field'))); - await $$('.modal-body__field').get(0).$('input').sendKeys(name); - leakedResources.add(JSON.stringify({name, plural: 'namespaces'})); + await $$('.modal-body__field') + .get(0) + .$('input') + .sendKeys(name); + leakedResources.add(JSON.stringify({ name, plural: 'namespaces' })); await $('#confirm-action').click(); await browser.wait(until.invisibilityOf($('.modal-content')), K8S_CREATION_TIMEOUT); expect(browser.getCurrentUrl()).toContain(`/k8s/cluster/namespaces/${testName}-ns`); }); - it('deletes the namespace', async() => { + it('deletes the namespace', async () => { await browser.get(`${appHost}/k8s/cluster/namespaces`); // Filter by resource name to make sure the resource is on the first page of results. // Otherwise the tests fail since we do virtual scrolling and the element isn't found. await crudView.filterForName(name); await crudView.resourceRowsPresent(); await crudView.deleteRow('Namespace')(name); - leakedResources.delete(JSON.stringify({name, plural: 'namespaces'})); + leakedResources.delete(JSON.stringify({ name, plural: 'namespaces' })); }); }); @@ -228,7 +269,7 @@ describe('Kubernetes resource CRUD operations', () => { kind: 'CustomResourceDefinition', metadata: { name, - labels: {[testLabel]: testName}, + labels: { [testLabel]: testName }, }, spec: { group, @@ -242,13 +283,13 @@ describe('Kubernetes resource CRUD operations', () => { }, }; - it('displays `CustomResourceDefinitions` list view', async() => { + it('displays `CustomResourceDefinitions` list view', async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions`); await crudView.isLoaded(); expect(crudView.resourceRows.count()).not.toEqual(0); }); - it('displays a YAML editor for creating a new custom resource definition', async() => { + it('displays a YAML editor for creating a new custom resource definition', async () => { await crudView.createYAMLButton.click(); await yamlView.isLoaded(); await yamlView.setEditorContent(safeDump(crd)); @@ -257,27 +298,27 @@ describe('Kubernetes resource CRUD operations', () => { expect(crudView.errorMessage.isPresent()).toBe(false); }); - it('displays YAML editor for creating a new custom resource instance', async() => { + it('displays YAML editor for creating a new custom resource instance', async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${name}`); await crudView.isLoaded(); - await crudView.clickKebabAction(crd.spec.names.kind,'View Instances'); + await crudView.clickKebabAction(crd.spec.names.kind, 'View Instances'); await crudView.isLoaded(); await crudView.createYAMLButton.click(); await yamlView.isLoaded(); expect(yamlView.getEditorContent()).toContain(`kind: CRD${testName}`); }); - it('creates a new custom resource instance', async() => { - leakedResources.add(JSON.stringify({name, plural: 'customresourcedefinitions'})); + it('creates a new custom resource instance', async () => { + leakedResources.add(JSON.stringify({ name, plural: 'customresourcedefinitions' })); await yamlView.saveButton.click(); expect(crudView.errorMessage.isPresent()).toBe(false); }); - it('deletes the `CustomResourceDefinition`', async() => { + it('deletes the `CustomResourceDefinition`', async () => { await browser.get(`${appHost}/k8s/cluster/customresourcedefinitions?name=${name}`); await crudView.resourceRowsPresent(); await crudView.deleteRow('CustomResourceDefinition')(crd.spec.names.kind); - leakedResources.delete(JSON.stringify({name, plural: 'customresourcedefinitions'})); + leakedResources.delete(JSON.stringify({ name, plural: 'customresourcedefinitions' })); }); }); @@ -287,17 +328,21 @@ describe('Kubernetes resource CRUD operations', () => { const kind = 'ConfigMap'; const labelValue = 'appblah'; - beforeAll(async() => { + beforeAll(async () => { await browser.get(`${appHost}/k8s/ns/${testName}/${plural}/~new`); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name, namespace: testName}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name, namespace: testName } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); - leakedResources.add(JSON.stringify({name, plural, namespace: testName})); + leakedResources.add(JSON.stringify({ name, plural, namespace: testName })); await yamlView.saveButton.click(); }); - it('displays modal for editing resource instance labels', async() => { + it('displays modal for editing resource instance labels', async () => { await crudView.clickDetailsPageAction(crudView.actions.labels); await browser.wait(until.presenceOf($('.tags input'))); await $('.tags input').sendKeys(labelValue, Key.ENTER); @@ -306,21 +351,29 @@ describe('Kubernetes resource CRUD operations', () => { await $('.modal-footer #confirm-action').click(); }); - it('updates the resource instance labels', async() => { + it('updates the resource instance labels', async () => { await browser.wait(until.presenceOf($('.co-m-label.co-m-label--expand'))); - expect($$('.co-m-label__key').first().getText()).toEqual(labelValue); + expect( + $$('.co-m-label__key') + .first() + .getText(), + ).toEqual(labelValue); }); - it('sees if label links still work', async() => { - await $$('.co-m-label').first().click(); - await browser.wait(until.urlContains(`/search/ns/${testName}?kind=core~v1~ConfigMap&q=${labelValue}`)); + it('sees if label links still work', async () => { + await $$('.co-m-label') + .first() + .click(); + await browser.wait( + until.urlContains(`/search/ns/${testName}?kind=core~v1~ConfigMap&q=${labelValue}`), + ); expect($('.co-text-configmap').isDisplayed()).toBe(true); }); - afterAll(async() => { + afterAll(async () => { await crudView.deleteResource(plural, kind, name); - leakedResources.delete(JSON.stringify({name, plural, namespace: testName})); + leakedResources.delete(JSON.stringify({ name, plural, namespace: testName })); }); }); @@ -334,9 +387,8 @@ describe('Kubernetes resource CRUD operations', () => { '/', '/k8s/all-namespaces/alertmanagers', '/k8s/ns/tectonic-system/alertmanagers/main', - ]).forEach(route => { - - it(`successfully displays view for route: ${route}`, async() => { + ]).forEach((route) => { + it(`successfully displays view for route: ${route}`, async () => { await browser.get(`${appHost}${route}`); await browser.sleep(5000); }); diff --git a/frontend/integration-tests/tests/deploy-image.scenario.ts b/frontend/integration-tests/tests/deploy-image.scenario.ts index 31e7e8ff7af..5c99167472c 100644 --- a/frontend/integration-tests/tests/deploy-image.scenario.ts +++ b/frontend/integration-tests/tests/deploy-image.scenario.ts @@ -11,7 +11,7 @@ describe('Deploy Image', () => { const imageName = 'mysql'; describe('Deploy Image page', () => { - it('should render project/namespace dropdown disabled when in a project context', async() => { + it('should render project/namespace dropdown disabled when in a project context', async () => { // Navigate to the deploy-image page await browser.get(`${appHost}/deploy-image/ns/${testName}?preselected-ns=${testName}`); @@ -22,8 +22,7 @@ describe('Deploy Image', () => { expect(element(by.css(dropdown)).getText()).toEqual(`Project: ${testName}`); }); - - it('should render applications dropdown disabled', async() => { + it('should render applications dropdown disabled', async () => { // Navigate to the deploy-image page await browser.get(`${appHost}/deploy-image/ns/${testName}?preselected-ns=${testName}`); @@ -34,7 +33,7 @@ describe('Deploy Image', () => { expect(element(by.css(dropdown)).getText()).toEqual('Application: all applications'); }); - it('can be used to search for an image', async() => { + it('can be used to search for an image', async () => { // Put the search term in the search field await element(by.css('[data-test-id="deploy-image-search-term"]')).sendKeys(imageName); // Click the search button @@ -47,7 +46,7 @@ describe('Deploy Image', () => { ).toBe(true); }); - it('should auto fill in the application', async() => { + it('should auto fill in the application', async () => { await browser.wait(until.presenceOf(element(by.id('form-input-application-name-field')))); // Confirm that a node is present in the topology expect(element(by.id('form-input-application-name-field')).getAttribute('value')).toEqual( @@ -55,7 +54,7 @@ describe('Deploy Image', () => { ); }); - it('should deploy the image and display it in the topology', async() => { + it('should deploy the image and display it in the topology', async () => { // Deploy the image // Wait until the button is active await browser.wait( diff --git a/frontend/integration-tests/tests/devconsole/dev-perspective.scenario.ts b/frontend/integration-tests/tests/devconsole/dev-perspective.scenario.ts index d6ffef1bfb1..3160b316ae3 100644 --- a/frontend/integration-tests/tests/devconsole/dev-perspective.scenario.ts +++ b/frontend/integration-tests/tests/devconsole/dev-perspective.scenario.ts @@ -1,9 +1,14 @@ import { browser } from 'protractor'; import { appHost, checkLogs, checkErrors } from '../../protractor.conf'; -import { switchPerspective, Perspective, pageSidebar, sideHeader } from '../../views/devconsole-view/dev-perspective.view'; +import { + switchPerspective, + Perspective, + pageSidebar, + sideHeader, +} from '../../views/devconsole-view/dev-perspective.view'; describe('Application Launcher Menu', () => { - beforeAll(async() => { + beforeAll(async () => { await browser.get(`${appHost}/k8s/cluster/projects`); }); @@ -12,7 +17,7 @@ describe('Application Launcher Menu', () => { checkErrors(); }); - it('Switch from admin to dev perspective', async() => { + it('Switch from admin to dev perspective', async () => { await switchPerspective(Perspective.Administrator); expect(sideHeader.getText()).toContain('Administrator'); await switchPerspective(Perspective.Developer); @@ -26,7 +31,7 @@ describe('Application Launcher Menu', () => { expect(pageSidebar.getText()).toContain('Search'); }); - it('Switch to dev to admin perspective', async() => { + it('Switch to dev to admin perspective', async () => { await switchPerspective(Perspective.Developer); expect(sideHeader.getText()).toContain('Developer'); await switchPerspective(Perspective.Administrator); diff --git a/frontend/integration-tests/tests/devconsole/git-import-flow.scenario.ts b/frontend/integration-tests/tests/devconsole/git-import-flow.scenario.ts index 297078e8b35..65d9480931a 100644 --- a/frontend/integration-tests/tests/devconsole/git-import-flow.scenario.ts +++ b/frontend/integration-tests/tests/devconsole/git-import-flow.scenario.ts @@ -13,14 +13,18 @@ import { builderImageVersionName, } from '../../views/devconsole-view/git-imort-flow'; import { newApplicationName, newAppName } from '../../views/devconsole-view/new-app-name.view'; -import { switchPerspective, Perspective, sideHeader } from '../../views/devconsole-view/dev-perspective.view'; +import { + switchPerspective, + Perspective, + sideHeader, +} from '../../views/devconsole-view/dev-perspective.view'; describe('git import flow', () => { let newApplication; let newApp; const importFromGitHeader = $('[data-test-id="resource-title"]'); - beforeAll(async() => { + beforeAll(async () => { await browser.get(`${appHost}/k8s/cluster/projects`); newApplication = newApplicationName(); newApp = newAppName(); @@ -31,7 +35,7 @@ describe('git import flow', () => { checkErrors(); }); - it('public git normal flow', async() => { + it('public git normal flow', async () => { await switchPerspective(Perspective.Developer); expect(sideHeader.getText()).toContain('Developer'); await navigateImportFromGit(); diff --git a/frontend/integration-tests/tests/developer-catalog.scenario.ts b/frontend/integration-tests/tests/developer-catalog.scenario.ts index 502444cf866..d07bcadf303 100644 --- a/frontend/integration-tests/tests/developer-catalog.scenario.ts +++ b/frontend/integration-tests/tests/developer-catalog.scenario.ts @@ -8,7 +8,7 @@ import * as catalogPageView from '../views/catalog-page.view'; import * as srvCatalogView from '../views/service-catalog.view'; describe('Developer Catalog', () => { - beforeEach(async() => { + beforeEach(async () => { await browser.get(`${appHost}/catalog/ns/${testName}`); await crudView.isLoaded(); }); @@ -18,7 +18,7 @@ describe('Developer Catalog', () => { checkErrors(); }); - it('clicking on Catalog Tile opens details modal', async() => { + it('clicking on Catalog Tile opens details modal', async () => { expect(catalogPageView.catalogTiles.isPresent()).toBe(true); await catalogPageView.catalogTiles.first().click(); @@ -26,7 +26,7 @@ describe('Developer Catalog', () => { expect($('.co-catalog-page__overlay-body').isPresent()).toBe(true); }); - it('filters catalog tiles by Category', async() => { + it('filters catalog tiles by Category', async () => { expect(catalogPageView.catalogTiles.isPresent()).toBe(true); const origNumItems = await catalogView.pageHeadingNumberOfItems(); @@ -40,12 +40,12 @@ describe('Developer Catalog', () => { expect(numLanguagesItems).toBeLessThan(origNumItems); }); - it('displays "Jenkins" catalog tile when filter by name: "jenkins"', async() => { + it('displays "Jenkins" catalog tile when filter by name: "jenkins"', async () => { await catalogPageView.filterByKeyword('jenkins'); expect(catalogPageView.catalogTileFor('Jenkins').isDisplayed()).toBe(true); }); - it('displays "No Filter Results" page correctly', async() => { + it('displays "No Filter Results" page correctly', async () => { await catalogPageView.filterByKeyword('NoFilterResultsTest'); await catalogPageView.clickFilterCheckbox('kind-ImageStream'); expect(catalogPageView.catalogTiles.count()).toBe(0); @@ -59,10 +59,12 @@ describe('Developer Catalog', () => { expect(catalogPageView.catalogTiles.count()).toBeGreaterThan(0); }); - it('filters catalog tiles by \'Service Class\' Type correctly', async() => { + it("filters catalog tiles by 'Service Class' Type correctly", async () => { expect(catalogPageView.catalogTiles.isPresent()).toBe(true); - const srvClassFilterCount = await catalogPageView.filterCheckboxCount('kind-ClusterServiceClass'); + const srvClassFilterCount = await catalogPageView.filterCheckboxCount( + 'kind-ClusterServiceClass', + ); // 'Node.js' is source-to-image and should be shown initially expect(catalogPageView.catalogTileFor('Node.js').isDisplayed()).toBe(true); @@ -78,7 +80,7 @@ describe('Developer Catalog', () => { expect(srvClassFilterCount).toEqual(numCatalogTiles); }); - it('filters catalog tiles by \'Source-To-Image\' Type correctly', async() => { + it("filters catalog tiles by 'Source-To-Image' Type correctly", async () => { expect(catalogPageView.catalogTiles.isPresent()).toBe(true); const srvClassFilterCount = await catalogPageView.filterCheckboxCount('kind-ImageStream'); @@ -96,7 +98,7 @@ describe('Developer Catalog', () => { expect(srvClassFilterCount).toEqual(numCatalogTiles); }); - it('creates a service instance and binding', async() => { + it('creates a service instance and binding', async () => { expect(catalogPageView.catalogTiles.isPresent()).toBe(true); await catalogPageView.clickFilterCheckbox('kind-ClusterServiceClass'); @@ -108,10 +110,18 @@ describe('Developer Catalog', () => { expect(catalogView.createServiceInstanceButton.isDisplayed()).toBe(true); await catalogView.createServiceInstanceButton.click(); - await browser.wait(until.and(crudView.untilNoLoadersPresent, until.presenceOf(catalogView.createServiceInstanceForm))); + await browser.wait( + until.and( + crudView.untilNoLoadersPresent, + until.presenceOf(catalogView.createServiceInstanceForm), + ), + ); await $('#dropdown-selectbox').click(); - await $$('.pf-c-dropdown__menu').first().$(`#${testName}-Project-link`).click(); + await $$('.pf-c-dropdown__menu') + .first() + .$(`#${testName}-Project-link`) + .click(); await srvCatalogView.createButton.click(); await crudView.isLoaded(); @@ -125,7 +135,11 @@ describe('Developer Catalog', () => { await crudView.isLoaded(); expect($('[data-test-id="resource-title"]').getText()).toBe('mongodb-persistent'); - expect($$('.co-section-heading').first().getText()).toBe('Service Binding Overview'); + expect( + $$('.co-section-heading') + .first() + .getText(), + ).toBe('Service Binding Overview'); execSync(`kubectl delete -n ${testName} servicebinding mongodb-persistent`); execSync(`kubectl delete -n ${testName} serviceinstance mongodb-persistent`); diff --git a/frontend/integration-tests/tests/environment.scenario.ts b/frontend/integration-tests/tests/environment.scenario.ts index 16c6912540a..420f467106c 100644 --- a/frontend/integration-tests/tests/environment.scenario.ts +++ b/frontend/integration-tests/tests/environment.scenario.ts @@ -6,7 +6,7 @@ import { appHost, testName, checkLogs, checkErrors } from '../protractor.conf'; import * as crudView from '../views/crud.view'; import * as environmentView from '../views/environment.view'; import * as yamlView from '../views/yaml.view'; -import {execSync} from 'child_process'; +import { execSync } from 'child_process'; const BROWSER_TIMEOUT = 15000; const WORKLOAD_NAME = `env-${testName}`; @@ -17,20 +17,27 @@ const Actions = { }; describe('Interacting with the environment variable editor', () => { - - beforeAll(async() => { + beforeAll(async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); await crudView.isLoaded(); await crudView.createYAMLButton.click(); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name: WORKLOAD_NAME, labels: {['lbl-env']: testName}}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name: WORKLOAD_NAME, labels: { ['lbl-env']: testName } } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); await crudView.saveChangesBtn.click(); // Wait until the resource is created and the details page loads before continuing. await browser.wait(until.presenceOf(crudView.actionsButton)); - execSync(`oc create cm my-config --from-literal=cmk1=config1 --from-literal=cmk2=config2 -n ${testName}`); - execSync(`oc create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret -n ${testName}`); + execSync( + `oc create cm my-config --from-literal=cmk1=config1 --from-literal=cmk2=config2 -n ${testName}`, + ); + execSync( + `oc create secret generic my-secret --from-literal=key1=supersecret --from-literal=key2=topsecret -n ${testName}`, + ); checkLogs(); checkErrors(); }); @@ -40,11 +47,14 @@ describe('Interacting with the environment variable editor', () => { checkErrors(); }); - afterAll(async() => { + afterAll(async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); await crudView.isLoaded(); await crudView.nameFilter.sendKeys(WORKLOAD_NAME); - await browser.wait(until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), + BROWSER_TIMEOUT, + ); await crudView.deleteRow('Deployment')(WORKLOAD_NAME); execSync(`oc delete cm my-config -n ${testName}`); execSync(`oc delete secret my-secret -n ${testName}`); @@ -52,38 +62,30 @@ describe('Interacting with the environment variable editor', () => { checkErrors(); }); - const validateKeyAndValue = async( - key: string, - value: string, - isPresent: boolean - ) => { + const validateKeyAndValue = async (key: string, value: string, isPresent: boolean) => { let keyFound = 0; const envKey = await environmentView.rowsKey.getAttribute('value'); const envValue = await environmentView.rowsValue.getAttribute('value'); - if (envKey === key){ + if (envKey === key) { keyFound = keyFound + 1; - expect( envValue ).toBe(value); + expect(envValue).toBe(value); } - if (isPresent){ + if (isPresent) { expect(keyFound).toEqual(1); } else { expect(keyFound).toEqual(0); } }; - const validateValueFrom = async(valueFrom: string, prefix: string) => { + const validateValueFrom = async (valueFrom: string, prefix: string) => { expect(environmentView.resources.last().getText()).toEqual(valueFrom); expect(environmentView.prefix.getAttribute('value')).toEqual(prefix); }; - const environmentEditor = async( - action: string, - key: string, - value: string - ) => { + const environmentEditor = async (action: string, key: string, value: string) => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}/environment`); switch (action) { @@ -109,7 +111,7 @@ describe('Interacting with the environment variable editor', () => { }; describe('When a variable is added', () => { - it('shows the correct variables', async() => { + it('shows the correct variables', async () => { const key = 'KEY'; const value = 'value'; await environmentEditor(Actions.add, key, value); @@ -119,7 +121,7 @@ describe('Interacting with the environment variable editor', () => { }); describe('When a variable is deleted', () => { - it('does not show any variables', async() => { + it('does not show any variables', async () => { const key = 'KEY'; const value = 'value'; await environmentEditor(Actions.delete, key, value); @@ -129,7 +131,7 @@ describe('Interacting with the environment variable editor', () => { }); describe('When a variable is added from a config map', () => { - it('shows the correct variables', async() => { + it('shows the correct variables', async () => { const resourceName = 'my-config'; const envPrefix = 'testcm'; await environmentEditor(Actions.addFrom, resourceName, envPrefix); @@ -139,7 +141,7 @@ describe('Interacting with the environment variable editor', () => { }); xdescribe('CONSOLE-1504 - When a variable is added from a secret', () => { - it('shows the correct variables', async() => { + it('shows the correct variables', async () => { const resourceName = 'my-secret'; const envPrefix = 'testsecret'; await environmentEditor(Actions.addFrom, resourceName, envPrefix); diff --git a/frontend/integration-tests/tests/filter.scenario.ts b/frontend/integration-tests/tests/filter.scenario.ts index 025f80fb660..958b9c91558 100644 --- a/frontend/integration-tests/tests/filter.scenario.ts +++ b/frontend/integration-tests/tests/filter.scenario.ts @@ -12,14 +12,17 @@ const WORKLOAD_NAME = `filter-${testName}`; const WORKLOAD_LABEL = `lbl-filter=${testName}`; describe('Filtering', () => { - - beforeAll(async() => { + beforeAll(async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); await crudView.isLoaded(); await crudView.createYAMLButton.click(); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name: WORKLOAD_NAME, labels: {['lbl-filter']: testName}}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name: WORKLOAD_NAME, labels: { ['lbl-filter']: testName } } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); await crudView.saveChangesBtn.click(); // Wait until the resource is created and the details page loads before continuing. @@ -31,27 +34,36 @@ describe('Filtering', () => { checkErrors(); }); - afterAll(async() => { + afterAll(async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); await crudView.isLoaded(); await crudView.nameFilter.sendKeys(WORKLOAD_NAME); - await browser.wait(until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), + BROWSER_TIMEOUT, + ); await crudView.deleteRow('Deployment')(WORKLOAD_NAME); }); - it('filters Pod from object detail', async() => { + it('filters Pod from object detail', async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); - await browser.wait(until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), + BROWSER_TIMEOUT, + ); expect(crudView.resourceRowNamesAndNs.first().getText()).toContain(WORKLOAD_NAME); await browser.get(`${appHost}/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}/pods`); await browser.wait(until.elementToBeClickable(crudView.nameFilter), BROWSER_TIMEOUT); await crudView.nameFilter.sendKeys(WORKLOAD_NAME); - await browser.wait(until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), + BROWSER_TIMEOUT, + ); expect(crudView.resourceRowNamesAndNs.first().getText()).toContain(WORKLOAD_NAME); }); - it('filters invalid Pod from object detail', async() => { + it('filters invalid Pod from object detail', async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}/pods`); await browser.wait(until.elementToBeClickable(crudView.nameFilter), BROWSER_TIMEOUT); await crudView.nameFilter.sendKeys('XYZ123'); @@ -59,7 +71,7 @@ describe('Filtering', () => { expect(crudView.messageLbl.isPresent()).toBe(true); }); - it('filters Deployment from other namespace', async() => { + it('filters Deployment from other namespace', async () => { await browser.get(`${appHost}/k8s/ns/kube-system/deployments`); await browser.wait(until.elementToBeClickable(crudView.nameFilter), BROWSER_TIMEOUT); await crudView.nameFilter.sendKeys(WORKLOAD_NAME); @@ -67,33 +79,42 @@ describe('Filtering', () => { expect(crudView.messageLbl.isPresent()).toBe(true); }); - it('filters from Pods list', async() => { + it('filters from Pods list', async () => { await browser.get(`${appHost}/k8s/all-namespaces/pods`); await browser.wait(until.elementToBeClickable(crudView.nameFilter), BROWSER_TIMEOUT); await crudView.nameFilter.sendKeys(WORKLOAD_NAME); - await browser.wait(until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), + BROWSER_TIMEOUT, + ); expect(crudView.resourceRowNamesAndNs.first().getText()).toContain(WORKLOAD_NAME); }); - xit('CONSOLE-1503 - searches for object by label', async() => { + xit('CONSOLE-1503 - searches for object by label', async () => { await browser.get(`${appHost}/search/ns/${testName}`); await browser.wait(until.elementToBeClickable(searchView.dropdown), BROWSER_TIMEOUT); await searchView.selectSearchType('Deployment'); await searchView.labelFilter.sendKeys(WORKLOAD_LABEL, Key.ENTER); - await browser.wait(until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), + BROWSER_TIMEOUT, + ); expect(crudView.resourceRowNamesAndNs.first().getText()).toContain(WORKLOAD_NAME); }); - it('searches for pod by label and filtering by name', async() => { + it('searches for pod by label and filtering by name', async () => { await browser.get(`${appHost}/search/all-namespaces?kind=Pod`); await crudView.isLoaded(); await crudView.nameFilter.sendKeys(WORKLOAD_NAME); await searchView.labelFilter.sendKeys('app=hello-openshift', Key.ENTER); - await browser.wait(until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(crudView.resourceRowNamesAndNs.first()), + BROWSER_TIMEOUT, + ); expect(crudView.resourceRowNamesAndNs.first().getText()).toContain(WORKLOAD_NAME); }); - it('searches for object by label using by other kind of workload', async() => { + it('searches for object by label using by other kind of workload', async () => { await browser.get(`${appHost}/search/all-namespaces?kind=ReplicationController`); await crudView.isLoaded(); await searchView.labelFilter.sendKeys(WORKLOAD_LABEL, Key.ENTER); diff --git a/frontend/integration-tests/tests/login.scenario.ts b/frontend/integration-tests/tests/login.scenario.ts index 2de60c77d3a..3d9beeee732 100644 --- a/frontend/integration-tests/tests/login.scenario.ts +++ b/frontend/integration-tests/tests/login.scenario.ts @@ -17,12 +17,12 @@ const { } = process.env; describe('Auth test', () => { - beforeAll(async() => { + beforeAll(async () => { await browser.get(appHost); await browser.sleep(3000); // Wait long enough for the login redirect to complete }); - describe('Login test', async() => { + describe('Login test', async () => { beforeAll(() => { // Extend the default jasmine timeout interval just in case it takes a while for the htpasswd idp to be ready jasmine.DEFAULT_TIMEOUT_INTERVAL = JASMINE_EXTENDED_TIMEOUT_INTERVAL; @@ -33,52 +33,60 @@ describe('Auth test', () => { jasmine.DEFAULT_TIMEOUT_INTERVAL = JASMINE_DEFAULT_TIMEOUT_INTERVAL; }); - it('logs in via htpasswd identity provider', async() => { - await loginView.login(BRIDGE_HTPASSWD_IDP, BRIDGE_HTPASSWD_USERNAME, BRIDGE_HTPASSWD_PASSWORD); + it('logs in via htpasswd identity provider', async () => { + await loginView.login( + BRIDGE_HTPASSWD_IDP, + BRIDGE_HTPASSWD_USERNAME, + BRIDGE_HTPASSWD_PASSWORD, + ); expect(browser.getCurrentUrl()).toContain(appHost); expect(loginView.userDropdown.getText()).toContain(BRIDGE_HTPASSWD_USERNAME); }); - it('does not show admin nav items in Administration to htpasswd user', async() => { + it('does not show admin nav items in Administration to htpasswd user', async () => { // Let flags resolve before checking for the presence of nav items. await browser.sleep(5000); await browser.wait(until.visibilityOf(sidenavView.navSectionFor('Administration'))); expect(sidenavView.navSectionFor('Administration')).not.toContain('Cluster Status'); expect(sidenavView.navSectionFor('Administration')).not.toContain('Cluster Settings'); expect(sidenavView.navSectionFor('Administration')).not.toContain('Namespaces'); - expect(sidenavView.navSectionFor('Administration')).not.toContain('Custom Resource Definitions'); + expect(sidenavView.navSectionFor('Administration')).not.toContain( + 'Custom Resource Definitions', + ); }); - it('does not show admin nav items in Operators to htpasswd user', async() => { + it('does not show admin nav items in Operators to htpasswd user', async () => { await browser.wait(until.visibilityOf(sidenavView.navSectionFor('Operators'))); expect(sidenavView.navSectionFor('Operators')).not.toContain('OperatorHub'); }); - it('does not show admin nav items in Storage to htpasswd user', async() => { + it('does not show admin nav items in Storage to htpasswd user', async () => { await browser.wait(until.visibilityOf(sidenavView.navSectionFor('Storage'))); expect(sidenavView.navSectionFor('Storage')).not.toContain('Persistent Volumes'); }); - it('does not show Compute or Monitoring admin nav items to htpasswd user', async() => { + it('does not show Compute or Monitoring admin nav items to htpasswd user', async () => { expect(sidenavView.navSectionFor('Compute').isPresent()).toBe(false); expect(sidenavView.navSectionFor('Monitoring').isPresent()).toBe(false); }); - it('logs out htpasswd user', async() => { + it('logs out htpasswd user', async () => { await loginView.logout(); expect(browser.getCurrentUrl()).toContain('oauth-openshift'); expect($('.login-pf').isPresent()).toBeTruthy(); }); - it('logs in as kubeadmin user', async() => { + it('logs in as kubeadmin user', async () => { await loginView.login(KUBEADMIN_IDP, KUBEADMIN_USERNAME, BRIDGE_KUBEADMIN_PASSWORD); expect(browser.getCurrentUrl()).toContain(appHost); expect(loginView.userDropdown.getText()).toContain('kube:admin'); await browser.wait(until.presenceOf($('.co-global-notification'))); - expect($('.co-global-notifications').getText()).toContain('You are logged in as a temporary administrative user. Update the cluster OAuth configuration to allow others to log in.'); + expect($('.co-global-notifications').getText()).toContain( + 'You are logged in as a temporary administrative user. Update the cluster OAuth configuration to allow others to log in.', + ); }); - it('logs out kubeadmin user', async() => { + it('logs out kubeadmin user', async () => { await loginView.logout(); expect(browser.getCurrentUrl()).toContain('oauth-openshift'); expect($('.login-pf').isPresent()).toBeTruthy(); @@ -89,7 +97,7 @@ describe('Auth test', () => { }); }); - it('is authenticated as cluster admin user', async() => { + it('is authenticated as cluster admin user', async () => { expect(await browser.getCurrentUrl()).toContain(appHost); await browser.wait(until.visibilityOf(sidenavView.navSectionFor('Compute'))); await browser.wait(until.visibilityOf(sidenavView.navSectionFor('Operators'))); diff --git a/frontend/integration-tests/tests/modal-annotations.scenario.ts b/frontend/integration-tests/tests/modal-annotations.scenario.ts index 6d1ecee5869..2ef35fb2771 100644 --- a/frontend/integration-tests/tests/modal-annotations.scenario.ts +++ b/frontend/integration-tests/tests/modal-annotations.scenario.ts @@ -1,4 +1,4 @@ -import { browser, ExpectedConditions as until} from 'protractor'; +import { browser, ExpectedConditions as until } from 'protractor'; import { safeLoad, safeDump } from 'js-yaml'; import * as _ from 'lodash'; @@ -16,14 +16,17 @@ const Actions = { }; describe('Modal Annotations', () => { - - beforeAll(async() => { + beforeAll(async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); await crudView.isLoaded(); await crudView.createYAMLButton.click(); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name: WORKLOAD_NAME, labels: {'lbl-modal': testName}}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name: WORKLOAD_NAME, labels: { 'lbl-modal': testName } } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); await crudView.saveChangesBtn.click(); // Wait until the resource is created and the details page loads before continuing. @@ -37,7 +40,7 @@ describe('Modal Annotations', () => { checkErrors(); }); - afterAll(async() => { + afterAll(async () => { await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); await crudView.resourceRowsPresent(); await crudView.nameFilter.sendKeys(WORKLOAD_NAME); @@ -46,21 +49,24 @@ describe('Modal Annotations', () => { checkErrors(); }); - const validateKeyAndValue = async function( annotationKey: string, + const validateKeyAndValue = async function( + annotationKey: string, annotationValue: string, - isPresent: boolean + isPresent: boolean, ) { let keyFound = 0; - await modalAnnotationsView.annotationRowsKey.each( async function(item, index) { + await modalAnnotationsView.annotationRowsKey.each(async function(item, index) { const annKey = await item.getAttribute('value'); - if (annKey === annotationKey){ + if (annKey === annotationKey) { keyFound = keyFound + 1; - expect( modalAnnotationsView.annotationRowsValue.get(index).getAttribute('value')).toBe(annotationValue); + expect(modalAnnotationsView.annotationRowsValue.get(index).getAttribute('value')).toBe( + annotationValue, + ); } }); - if (isPresent){ + if (isPresent) { expect(keyFound).toEqual(1); } else { expect(keyFound).toEqual(0); @@ -77,7 +83,7 @@ describe('Modal Annotations', () => { const crudAnnotationFromDetail = async function( action: string, annotationKey: string, - annotationValue: string + annotationValue: string, ) { await browser.get(`${appHost}/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}`); await clickModalAnnotationsLink(); @@ -110,7 +116,7 @@ describe('Modal Annotations', () => { action: string, annotationKey: string, annotationValue: string, - isPresent: boolean + isPresent: boolean, ) { await crudAnnotationFromDetail(action, annotationKey, annotationValue); await browser.get(`${appHost}/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}`); @@ -127,20 +133,26 @@ describe('Modal Annotations', () => { // When I delete the annotation // Then I expect that the annotation is not displayed // And I expect to see that the YAML should not contain the annotation - it('Delete Annotation', async() => { + it('Delete Annotation', async () => { const annotationKey = 'KEY_del3t3'; const annotationValue = 'delete'; await crudAnnotationFromDetail(Actions.add, annotationKey, annotationValue); await browser.get(`${appHost}/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}`); - await browser.wait(until.textToBePresentInElement(crudView.modalAnnotationsLink, '2'), BROWSER_TIMEOUT); + await browser.wait( + until.textToBePresentInElement(crudView.modalAnnotationsLink, '2'), + BROWSER_TIMEOUT, + ); await clickModalAnnotationsLink(); await modalAnnotationsView.isLoaded(); await validateKeyAndValue(annotationKey, annotationValue, true); await crudAnnotationFromDetail(Actions.delete, annotationKey, annotationValue); await browser.get(`${appHost}/k8s/ns/${testName}/deployments/${WORKLOAD_NAME}`); - await browser.wait(until.textToBePresentInElement(crudView.modalAnnotationsLink, '1'), BROWSER_TIMEOUT); + await browser.wait( + until.textToBePresentInElement(crudView.modalAnnotationsLink, '1'), + BROWSER_TIMEOUT, + ); await clickModalAnnotationsLink(); await modalAnnotationsView.isLoaded(); await validateKeyAndValue(annotationKey, annotationValue, false); @@ -156,7 +168,7 @@ describe('Modal Annotations', () => { // And I open modal annotations from gear option // Then I expect to see the annotation created // And I expect to see that the YAML should contain the annotation value with simple quotes - it('Add numeric Annotation from grid', async() => { + it('Add numeric Annotation from grid', async () => { const annotationKey = 'NUM_KEY_FROM_GRID'; const annotationValue = '2233344'; const annotationYAML = `${annotationKey}: '${annotationValue}'`; @@ -165,7 +177,7 @@ describe('Modal Annotations', () => { await crudView.isLoaded(); await crudView.clickKebabAction(WORKLOAD_NAME, crudView.actions.annotations); await modalAnnotationsView.isLoaded(); - await modalAnnotationsView.addAnnotation(annotationKey,annotationValue); + await modalAnnotationsView.addAnnotation(annotationKey, annotationValue); await modalAnnotationsView.isLoaded(); await modalAnnotationsView.confirmActionBtn.click(); await browser.get(`${appHost}/k8s/ns/${testName}/deployments`); @@ -188,7 +200,7 @@ describe('Modal Annotations', () => { // And I open modal annotations // Then I expect to see the annotation created // And I expect to see that the YAML should contain the annotation value without simple quotes - it('Add alphanumeric Annotation from object detail', async() => { + it('Add alphanumeric Annotation from object detail', async () => { const annotationKey = 'ALPHA_Num_KEY_FROM_detail-12333'; const annotationValue = 'from_dEtaIL-2'; const annotationYAML = `${annotationKey}: ${annotationValue}`; @@ -208,7 +220,7 @@ describe('Modal Annotations', () => { // When I add an annotation without value // Then I expect to see the annotation created without value // And I expect to see that the YAML should contain an empty string ('') as annotation value - it('Add Annotation without value', async() => { + it('Add Annotation without value', async () => { const annotationKey = 'KEY_without_v4lu3'; const annotationValue = ''; const annotationYAML = `${annotationKey}: ''`; @@ -228,7 +240,7 @@ describe('Modal Annotations', () => { // When I add an annotation without key // And I close the modal // Then I expect that the annotation is not displayed - it('Add annotation wihout key', async() => { + it('Add annotation wihout key', async () => { const annotationKey = ''; const annotationValue = 'value_no_key'; @@ -244,7 +256,7 @@ describe('Modal Annotations', () => { // And I update this annotation to empty value // Then I expect to see the annotation created without value // And I expect to see that the YAML should contain an empty string ('') as annotation value - xit('Update Annotation from value to empty value: CONSOLE-394', async() => { + xit('Update Annotation from value to empty value: CONSOLE-394', async () => { const annotationKey = 'KEY_UPDATE_FROM_VALUE_TO_EMPTY'; const annotationValueBeforeUpd = 'new_value_not_empty-1'; const annotationValueAfterUpd = ''; @@ -259,7 +271,7 @@ describe('Modal Annotations', () => { // And I open modal annotations from gear option // When I update this annotation // Then I expect to see the annotation - it('Update Annotation from empty value to value', async() => { + it('Update Annotation from empty value to value', async () => { const annotationKey = 'KEY_UPDATE_TO_EMPTY'; const annotationValueBeforeUpd = ''; const annotationValueAfterUpd = 'new_value_not_empty-2'; @@ -282,7 +294,7 @@ describe('Modal Annotations', () => { // And I cancel the action // And I open modal annotations from gear option // Then I expect that the annotation is not displayed - it('Cancel add Annotation', async() => { + it('Cancel add Annotation', async () => { const annotationKey = 'KEY_Cancel'; const annotationValue = 'cancel'; diff --git a/frontend/integration-tests/tests/monitoring.scenario.ts b/frontend/integration-tests/tests/monitoring.scenario.ts index afc9cc762ba..1462f9db803 100644 --- a/frontend/integration-tests/tests/monitoring.scenario.ts +++ b/frontend/integration-tests/tests/monitoring.scenario.ts @@ -23,31 +23,37 @@ describe('Monitoring: Alerts', () => { checkErrors(); }); - it('displays the Alerts list page', async() => { + it('displays the Alerts list page', async () => { await sidenavView.clickNavLink(['Monitoring', 'Alerting']); await crudView.isLoaded(); expect(monitoringView.listPageHeading.getText()).toContain('Alerting'); }); - it('does not have a namespace dropdown', async() => { + it('does not have a namespace dropdown', async () => { expect(namespaceView.namespaceSelector.isPresent()).toBe(false); }); - it('filters Alerts by name', async() => { + it('filters Alerts by name', async () => { await monitoringView.wait(until.elementToBeClickable(crudView.nameFilter)); await crudView.nameFilter.sendKeys(testAlertName); - expect(monitoringView.firstListLinkById('alert-resource-link').getText()).toContain(testAlertName); - }); - - it('displays Alert details page', async() => { - await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLinkById('alert-resource-link'))); - expect(monitoringView.firstListLinkById('alert-resource-link').getText()).toContain(testAlertName); + expect(monitoringView.firstListLinkById('alert-resource-link').getText()).toContain( + testAlertName, + ); + }); + + it('displays Alert details page', async () => { + await monitoringView.wait( + until.elementToBeClickable(monitoringView.firstListLinkById('alert-resource-link')), + ); + expect(monitoringView.firstListLinkById('alert-resource-link').getText()).toContain( + testAlertName, + ); await monitoringView.firstListLinkById('alert-resource-link').click(); await monitoringView.wait(until.presenceOf(monitoringView.detailsHeadingAlertIcon)); testDetailsPage('Alert Overview', testAlertName); }); - it('links to the Alerting Rule details page', async() => { + it('links to the Alerting Rule details page', async () => { expect(monitoringView.ruleLink.getText()).toContain(testAlertName); await monitoringView.ruleLink.click(); await monitoringView.wait(until.presenceOf(monitoringView.detailsHeadingRuleIcon)); @@ -60,7 +66,7 @@ describe('Monitoring: Alerts', () => { testDetailsPage('Alert Overview', testAlertName); }); - it('creates a new Silence from an existing alert', async() => { + it('creates a new Silence from an existing alert', async () => { await crudView.clickDetailsPageAction('Silence Alert'); await monitoringView.wait(until.presenceOf(monitoringView.saveButton)); await monitoringView.saveButton.click(); @@ -71,7 +77,7 @@ describe('Monitoring: Alerts', () => { testDetailsPage('Silence Overview', testAlertName); }); - it('shows the silenced Alert in the Silenced Alerts list', async() => { + it('shows the silenced Alert in the Silenced Alerts list', async () => { await monitoringView.wait(until.elementToBeClickable(monitoringView.firstAlertsListLink)); expect(monitoringView.firstAlertsListLink.getText()).toContain(testAlertName); @@ -81,9 +87,13 @@ describe('Monitoring: Alerts', () => { testDetailsPage('Alert Overview', testAlertName); }); - it('shows the newly created Silence in the Silenced By list', async() => { - await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLinkById('silence-resource-link'))); - expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain(testAlertName); + it('shows the newly created Silence in the Silenced By list', async () => { + await monitoringView.wait( + until.elementToBeClickable(monitoringView.firstListLinkById('silence-resource-link')), + ); + expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain( + testAlertName, + ); // Click the link to navigate back to the Silence details page await monitoringView.firstListLinkById('silence-resource-link').click(); @@ -91,7 +101,7 @@ describe('Monitoring: Alerts', () => { testDetailsPage('Silence Overview', testAlertName); }); - it('expires the Silence', async() => { + it('expires the Silence', async () => { await crudView.clickDetailsPageAction('Expire Silence'); await monitoringView.wait(until.elementToBeClickable(monitoringView.modalConfirmButton)); await monitoringView.modalConfirmButton.click(); @@ -105,18 +115,20 @@ describe('Monitoring: Silences', () => { checkErrors(); }); - it('displays the Silences list page', async() => { + it('displays the Silences list page', async () => { await sidenavView.clickNavLink(['Monitoring', 'Alerting']); await crudView.isLoaded(); await horizontalnavView.clickHorizontalTab('Silences'); - expect(monitoringView.helpText.getText()).toContain('Silences temporarily mute alerts based on a set of conditions'); + expect(monitoringView.helpText.getText()).toContain( + 'Silences temporarily mute alerts based on a set of conditions', + ); }); - it('does not have a namespace dropdown', async() => { + it('does not have a namespace dropdown', async () => { expect(namespaceView.namespaceSelector.isPresent()).toBe(false); }); - it('creates a new Silence', async() => { + it('creates a new Silence', async () => { await monitoringView.createButton.click(); await monitoringView.wait(until.presenceOf(monitoringView.matcherNameInput)); await monitoringView.matcherNameInput.sendKeys('alertname'); @@ -126,29 +138,35 @@ describe('Monitoring: Silences', () => { }); // After creating the Silence, should be redirected to its details page - it('displays detail view for new Silence', async() => { + it('displays detail view for new Silence', async () => { await monitoringView.wait(until.presenceOf(monitoringView.detailsHeadingSilenceIcon)); testDetailsPage('Silence Overview', testAlertName); }); - it('filters Silences by name', async() => { + it('filters Silences by name', async () => { await sidenavView.clickNavLink(['Monitoring', 'Alerting']); await crudView.isLoaded(); await horizontalnavView.clickHorizontalTab('Silences'); await monitoringView.wait(until.elementToBeClickable(crudView.nameFilter)); await crudView.nameFilter.sendKeys(testAlertName); - expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain(testAlertName); - }); - - it('displays Silence details page', async() => { - await monitoringView.wait(until.elementToBeClickable(monitoringView.firstListLinkById('silence-resource-link'))); - expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain(testAlertName); + expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain( + testAlertName, + ); + }); + + it('displays Silence details page', async () => { + await monitoringView.wait( + until.elementToBeClickable(monitoringView.firstListLinkById('silence-resource-link')), + ); + expect(monitoringView.firstListLinkById('silence-resource-link').getText()).toContain( + testAlertName, + ); await monitoringView.firstListLinkById('silence-resource-link').click(); await monitoringView.wait(until.presenceOf(monitoringView.detailsHeadingSilenceIcon)); testDetailsPage('Silence Overview', testAlertName); }); - it('edits the Silence', async() => { + it('edits the Silence', async () => { await crudView.clickDetailsPageAction('Edit Silence'); await monitoringView.wait(until.presenceOf(monitoringView.commentTextarea)); await monitoringView.commentTextarea.sendKeys('Test Comment'); @@ -161,7 +179,7 @@ describe('Monitoring: Silences', () => { expect(monitoringView.silenceComment.getText()).toEqual('Test Comment'); }); - it('expires the Silence', async() => { + it('expires the Silence', async () => { await sidenavView.clickNavLink(['Monitoring', 'Alerting']); await crudView.isLoaded(); await horizontalnavView.clickHorizontalTab('Silences'); @@ -181,7 +199,7 @@ describe('Monitoring: YAML', () => { checkErrors(); }); - it('displays the YAML page', async() => { + it('displays the YAML page', async () => { await sidenavView.clickNavLink(['Monitoring', 'Alerting']); await crudView.isLoaded(); await horizontalnavView.clickHorizontalTab('YAML'); @@ -189,7 +207,7 @@ describe('Monitoring: YAML', () => { expect(monitoringView.alertManagerYamlForm.isPresent()).toBe(true); }); - it('saves alert-manager.yaml', async() => { + it('saves alert-manager.yaml', async () => { expect(monitoringView.successAlert.isPresent()).toBe(false); await monitoringView.saveButton.click(); await crudView.isLoaded(); diff --git a/frontend/integration-tests/tests/overview/overview.scenario.ts b/frontend/integration-tests/tests/overview/overview.scenario.ts index 0739aa4c793..8c0b91d4e83 100644 --- a/frontend/integration-tests/tests/overview/overview.scenario.ts +++ b/frontend/integration-tests/tests/overview/overview.scenario.ts @@ -4,7 +4,12 @@ import { Set as ImmutableSet } from 'immutable'; import { appHost, testName, checkErrors, checkLogs } from '../../protractor.conf'; import * as overviewView from '../../views/overview.view'; import * as crudView from '../../views/crud.view'; -import { DeploymentModel, StatefulSetModel, DeploymentConfigModel, DaemonSetModel } from '../../../public/models'; +import { + DeploymentModel, + StatefulSetModel, + DeploymentConfigModel, + DaemonSetModel, +} from '../../../public/models'; const overviewResources = ImmutableSet([ DaemonSetModel, @@ -19,7 +24,7 @@ describe('Visiting Overview page', () => { checkLogs(); }); - beforeAll(async() => { + beforeAll(async () => { await browser.get(`${appHost}/k8s/cluster/projects/${testName}/workloads`); await crudView.isLoaded(); }); @@ -27,18 +32,20 @@ describe('Visiting Overview page', () => { overviewResources.forEach((kindModel) => { describe(kindModel.labelPlural, () => { const resourceName = `${testName}-${kindModel.kind.toLowerCase()}`; - beforeAll(async()=>{ + beforeAll(async () => { await crudView.createNamespacedTestResource(kindModel, resourceName); }); - it(`displays a ${kindModel.id} in the project overview list`, async() => { + it(`displays a ${kindModel.id} in the project overview list`, async () => { await browser.wait(until.presenceOf(overviewView.projectOverview)); await overviewView.itemsAreVisible(); - expect(overviewView.getProjectOverviewListItem(kindModel, resourceName).isPresent()).toBeTruthy(); + expect( + overviewView.getProjectOverviewListItem(kindModel, resourceName).isPresent(), + ).toBeTruthy(); }); // Disabling for now due to flake https://jira.coreos.com/browse/CONSOLE-1298 - xit(`CONSOLE-1298 - shows ${kindModel.id} details sidebar when item is clicked`, async() => { + xit(`CONSOLE-1298 - shows ${kindModel.id} details sidebar when item is clicked`, async () => { const overviewListItem = overviewView.getProjectOverviewListItem(kindModel, resourceName); expect(overviewView.detailsSidebar.isPresent()).toBeFalsy(); await browser.wait(until.elementToBeClickable(overviewListItem)); diff --git a/frontend/integration-tests/tests/performance.scenario.ts b/frontend/integration-tests/tests/performance.scenario.ts index 704859873e0..df0650864b2 100644 --- a/frontend/integration-tests/tests/performance.scenario.ts +++ b/frontend/integration-tests/tests/performance.scenario.ts @@ -8,55 +8,70 @@ import * as sidenavView from '../views/sidenav.view'; import * as crudView from '../views/crud.view'; import * as yamlView from '../views/yaml.view'; -const chunkedRoutes = OrderedMap() - .set('daemon-set', {section: 'Workloads', name: 'Daemon Sets'}) - .set('deployment', {section: 'Workloads', name: 'Deployments'}) - .set('deployment-config', {section: 'Workloads', name: 'Deployment Configs'}) - .set('replicaset', {section: 'Workloads', name: 'Replica Sets'}) // TODO should be replica-set - .set('replication-controller', {section: 'Workloads', name: 'Replication Controllers'}) - .set('stateful-set', {section: 'Workloads', name: 'Stateful Sets'}) - .set('job', {section: 'Workloads', name: 'Jobs'}) - .set('cron-job', {section: 'Workloads', name: 'Cron Jobs'}) - .set('configmap', {section: 'Workloads', name: 'Config Maps'}) - .set('hpa', {section: 'Workloads', name: 'Horizontal Pod Autoscalers'}) - .set('service', {section: 'Networking', name: 'Services'}) - .set('persistent-volume', {section: 'Storage', name: 'Persistent Volumes'}) - .set('persistent-volume-claim', {section: 'Storage', name: 'Persistent Volume Claims'}) - .set('storage-class', {section: 'Storage', name: 'Storage Classes'}) - .set('build-config', {section: 'Builds', name: 'Build Configs'}) - .set('image-stream', {section: 'Builds', name: 'Image Streams'}) - .set('node', {section: 'Compute', name: 'Nodes'}) - .set('service-account', {section: 'Administration', name: 'Service Accounts'}) - .set('limit-range', {section: 'Administration', name: 'Limit Ranges'}) - .set('custom-resource-definition', {section: 'Administration', name: 'Custom Resource Definitions'}) - .set('operator-hub', {section: 'Operators', name: 'OperatorHub'}); +const chunkedRoutes = OrderedMap() + .set('daemon-set', { section: 'Workloads', name: 'Daemon Sets' }) + .set('deployment', { section: 'Workloads', name: 'Deployments' }) + .set('deployment-config', { section: 'Workloads', name: 'Deployment Configs' }) + .set('replicaset', { section: 'Workloads', name: 'Replica Sets' }) // TODO should be replica-set + .set('replication-controller', { section: 'Workloads', name: 'Replication Controllers' }) + .set('stateful-set', { section: 'Workloads', name: 'Stateful Sets' }) + .set('job', { section: 'Workloads', name: 'Jobs' }) + .set('cron-job', { section: 'Workloads', name: 'Cron Jobs' }) + .set('configmap', { section: 'Workloads', name: 'Config Maps' }) + .set('hpa', { section: 'Workloads', name: 'Horizontal Pod Autoscalers' }) + .set('service', { section: 'Networking', name: 'Services' }) + .set('persistent-volume', { section: 'Storage', name: 'Persistent Volumes' }) + .set('persistent-volume-claim', { section: 'Storage', name: 'Persistent Volume Claims' }) + .set('storage-class', { section: 'Storage', name: 'Storage Classes' }) + .set('build-config', { section: 'Builds', name: 'Build Configs' }) + .set('image-stream', { section: 'Builds', name: 'Image Streams' }) + .set('node', { section: 'Compute', name: 'Nodes' }) + .set('service-account', { section: 'Administration', name: 'Service Accounts' }) + .set('limit-range', { section: 'Administration', name: 'Limit Ranges' }) + .set('custom-resource-definition', { + section: 'Administration', + name: 'Custom Resource Definitions', + }) + .set('operator-hub', { section: 'Operators', name: 'OperatorHub' }); describe('Performance test', () => { - - it('checks bundle size using ResourceTiming API', async() => { - const resources = await browser.executeScript(() => performance.getEntriesByType('resource') - .filter(({name}) => name.endsWith('.js') && name.indexOf('main') > -1 && name.indexOf('runtime') === -1) - .map(({name, decodedBodySize}: PerformanceResourceTiming) => ({name: name.split('/').slice(-1)[0], size: Math.floor(decodedBodySize / 1024)})) - .reduce((acc, val) => acc.concat(`${val.name.split('-')[0]}: ${val.size} KB, `), '') + it('checks bundle size using ResourceTiming API', async () => { + const resources = await browser.executeScript(() => + performance + .getEntriesByType('resource') + .filter( + ({ name }) => + name.endsWith('.js') && name.indexOf('main') > -1 && name.indexOf('runtime') === -1, + ) + .map(({ name, decodedBodySize }: PerformanceResourceTiming) => ({ + name: name.split('/').slice(-1)[0], + size: Math.floor(decodedBodySize / 1024), + })) + .reduce((acc, val) => acc.concat(`${val.name.split('-')[0]}: ${val.size} KB, `), ''), ); - writeFileSync(path.resolve(__dirname, `../../${screenshotsDir}/bundle-analysis.txt`), resources); + writeFileSync( + path.resolve(__dirname, `../../${screenshotsDir}/bundle-analysis.txt`), + resources, + ); expect(resources.length).not.toEqual(0); }); - it('downloads new bundle for YAML editor route', async() => { + it('downloads new bundle for YAML editor route', async () => { await browser.get(`${appHost}/k8s/ns/openshift-console/configmaps`); await crudView.isLoaded(); - const initialChunks = await browser.executeScript<{name: string, size: number}[]>(() => performance.getEntriesByType('resource') - .filter(({name}) => name.endsWith('.js'))); + const initialChunks = await browser.executeScript<{ name: string; size: number }[]>(() => + performance.getEntriesByType('resource').filter(({ name }) => name.endsWith('.js')), + ); await crudView.clickKebabAction('console-config', 'Edit Config Map'); await yamlView.isLoaded(); - const postChunks = await browser.executeScript<{name: string, size: number}[]>(() => performance.getEntriesByType('resource') - .filter(({name}) => name.endsWith('.js'))); + const postChunks = await browser.executeScript<{ name: string; size: number }[]>(() => + performance.getEntriesByType('resource').filter(({ name }) => name.endsWith('.js')), + ); expect(initialChunks.length).toBeLessThan(postChunks.length); }); @@ -66,10 +81,12 @@ describe('Performance test', () => { const routeChunkFor = function() { const chunkName = arguments[0]; - return performance.getEntriesByType('resource').find(({name}) => name.endsWith('.js') && name.indexOf(`/${chunkName}`) > -1); + return performance + .getEntriesByType('resource') + .find(({ name }) => name.endsWith('.js') && name.indexOf(`/${chunkName}`) > -1); }; - it(`downloads new bundle for ${routeName}`, async() => { + it(`downloads new bundle for ${routeName}`, async () => { await browser.get(`${appHost}/k8s/cluster/projects`); await browser.executeScript(() => performance.setResourceTimingBufferSize(1000)); await browser.wait(until.presenceOf(crudView.resourceTitle)); diff --git a/frontend/integration-tests/tests/secrets.scenario.ts b/frontend/integration-tests/tests/secrets.scenario.ts index baae9f304f5..88f959aad6c 100644 --- a/frontend/integration-tests/tests/secrets.scenario.ts +++ b/frontend/integration-tests/tests/secrets.scenario.ts @@ -7,7 +7,6 @@ import * as secretsView from '../views/secrets.view'; import { execSync } from 'child_process'; describe('Interacting with the create secret forms', () => { - afterEach(() => { checkLogs(); checkErrors(); @@ -17,32 +16,41 @@ describe('Interacting with the create secret forms', () => { const webhookSecretName = 'webhook-secret'; const webhookSecretValue = 'webhookValue'; - beforeAll(async() => secretsView.visitSecretsPage(appHost, testName)); + beforeAll(async () => secretsView.visitSecretsPage(appHost, testName)); - it('creates webhook secret', async() => { - await secretsView.createSecret(secretsView.createWebhookSecretLink, testName, webhookSecretName, async() => { - await secretsView.secretWebhookInput.sendKeys(webhookSecretValue); - }); + it('creates webhook secret', async () => { + await secretsView.createSecret( + secretsView.createWebhookSecretLink, + testName, + webhookSecretName, + async () => { + await secretsView.secretWebhookInput.sendKeys(webhookSecretValue); + }, + ); }); - it('check for created webhook secret value', async() => { - await secretsView.checkSecret(testName, webhookSecretName, {'WebHookSecretKey': webhookSecretValue}); + it('check for created webhook secret value', async () => { + await secretsView.checkSecret(testName, webhookSecretName, { + WebHookSecretKey: webhookSecretValue, + }); }); - it('edits webhook secret', async() => { - await secretsView.editSecret(testName, webhookSecretName, async() => { + it('edits webhook secret', async () => { + await secretsView.editSecret(testName, webhookSecretName, async () => { await element(by.buttonText('Generate')).isPresent(); await element(by.buttonText('Generate')).click(); }); }); - it('check for edited webhook secret value', async() => { - await browser.wait(until.textToBePresentInElement($('.co-m-pane__heading'), webhookSecretName)); + it('check for edited webhook secret value', async () => { + await browser.wait( + until.textToBePresentInElement($('.co-m-pane__heading'), webhookSecretName), + ); await secretsView.clickRevealValues(); expect(secretsView.pre.get(0).getText()).not.toEqual(webhookSecretValue); }); - it('deletes the webhook secret', async() => { + it('deletes the webhook secret', async () => { await crudView.deleteResource('secrets', 'Secret', webhookSecretName); }); }); @@ -54,21 +62,29 @@ describe('Interacting with the create secret forms', () => { const basicSourceSecretPassword = 'password'; const basicSourceSecretPasswordUpdated = 'passwordUpdated'; - beforeAll(async() => secretsView.visitSecretsPage(appHost, testName)); + beforeAll(async () => secretsView.visitSecretsPage(appHost, testName)); - it('creates basic source secret', async() => { - await secretsView.createSecret(secretsView.createSourceSecretLink, testName, basicSourceSecretName, async() => { - await secretsView.secretUsernameInput.sendKeys(basicSourceSecretUsername); - await secretsView.secretPasswordInput.sendKeys(basicSourceSecretPassword); - }); + it('creates basic source secret', async () => { + await secretsView.createSecret( + secretsView.createSourceSecretLink, + testName, + basicSourceSecretName, + async () => { + await secretsView.secretUsernameInput.sendKeys(basicSourceSecretUsername); + await secretsView.secretPasswordInput.sendKeys(basicSourceSecretPassword); + }, + ); }); - it('check for created basic source secret values', async() => { - await secretsView.checkSecret(testName, basicSourceSecretName, {'username': basicSourceSecretUsername, 'password': basicSourceSecretPassword}); + it('check for created basic source secret values', async () => { + await secretsView.checkSecret(testName, basicSourceSecretName, { + username: basicSourceSecretUsername, + password: basicSourceSecretPassword, + }); }); - it('edits basic source secret', async() => { - await secretsView.editSecret(testName, basicSourceSecretName, async() => { + it('edits basic source secret', async () => { + await secretsView.editSecret(testName, basicSourceSecretName, async () => { await secretsView.secretUsernameInput.clear(); await secretsView.secretUsernameInput.sendKeys(basicSourceSecretUsernameUpdated); await secretsView.secretPasswordInput.clear(); @@ -76,11 +92,14 @@ describe('Interacting with the create secret forms', () => { }); }); - it('check for edited basic source secret values', async() => { - await secretsView.checkSecret(testName, basicSourceSecretName, {'username': basicSourceSecretUsernameUpdated, 'password': basicSourceSecretPasswordUpdated}); + it('check for edited basic source secret values', async () => { + await secretsView.checkSecret(testName, basicSourceSecretName, { + username: basicSourceSecretUsernameUpdated, + password: basicSourceSecretPasswordUpdated, + }); }); - it('deletes the basic source secret', async() => { + it('deletes the basic source secret', async () => { await crudView.deleteResource('secrets', 'Secret', basicSourceSecretName); }); }); @@ -90,32 +109,46 @@ describe('Interacting with the create secret forms', () => { const sshSourceSecretSSHKey = 'sshKey'; const sshSourceSecretSSHKeUpdated = 'sshKeyUpdated'; - beforeAll(async() => secretsView.visitSecretsPage(appHost, testName)); - - it('creates SSH source secret', async() => { - await secretsView.createSecret(secretsView.createSourceSecretLink, testName, sshSourceSecretName, async() => { - await secretsView.authTypeDropdown.click().then(() => browser.actions().sendKeys(Key.ARROW_UP, Key.ENTER).perform()); - await browser.wait(until.presenceOf(secretsView.uploadFileTextArea)); - await secretsView.uploadFileTextArea.sendKeys(sshSourceSecretSSHKey); - }); + beforeAll(async () => secretsView.visitSecretsPage(appHost, testName)); + + it('creates SSH source secret', async () => { + await secretsView.createSecret( + secretsView.createSourceSecretLink, + testName, + sshSourceSecretName, + async () => { + await secretsView.authTypeDropdown.click().then(() => + browser + .actions() + .sendKeys(Key.ARROW_UP, Key.ENTER) + .perform(), + ); + await browser.wait(until.presenceOf(secretsView.uploadFileTextArea)); + await secretsView.uploadFileTextArea.sendKeys(sshSourceSecretSSHKey); + }, + ); }); - it('check for created SSH source secret values', async() => { - await secretsView.checkSecret(testName, sshSourceSecretName, {'ssh-privatekey': sshSourceSecretSSHKey}); + it('check for created SSH source secret values', async () => { + await secretsView.checkSecret(testName, sshSourceSecretName, { + 'ssh-privatekey': sshSourceSecretSSHKey, + }); }); - it('edits SSH source secret', async() => { - await secretsView.editSecret(testName, sshSourceSecretName, async() => { + it('edits SSH source secret', async () => { + await secretsView.editSecret(testName, sshSourceSecretName, async () => { await secretsView.uploadFileTextArea.clear(); await secretsView.uploadFileTextArea.sendKeys(sshSourceSecretSSHKeUpdated); }); }); - it('check for edited SSH source secret values', async() => { - await secretsView.checkSecret(testName, sshSourceSecretName, {'ssh-privatekey': sshSourceSecretSSHKeUpdated}); + it('check for edited SSH source secret values', async () => { + await secretsView.checkSecret(testName, sshSourceSecretName, { + 'ssh-privatekey': sshSourceSecretSSHKeUpdated, + }); }); - it('deletes the SSH source secret', async() => { + it('deletes the SSH source secret', async () => { await crudView.deleteResource('secrets', 'Secret', sshSourceSecretName); }); }); @@ -137,14 +170,14 @@ describe('Interacting with the create secret forms', () => { const credentialsToCheck = { '.dockerconfigjson': { - auths:{ - 'https://index.openshift.io/v0':{ + auths: { + 'https://index.openshift.io/v0': { username: username0, password: password0, auth: secretsView.encode(username0, password0), email: 'test@secret.com0', }, - 'https://index.openshift.io/v1':{ + 'https://index.openshift.io/v1': { username: username1, password: password1, auth: secretsView.encode(username1, password1), @@ -155,8 +188,8 @@ describe('Interacting with the create secret forms', () => { }; const updatedCredentialsToCheck = { '.dockerconfigjson': { - auths:{ - 'https://index.openshift.io/updated/v1':{ + auths: { + 'https://index.openshift.io/updated/v1': { username: usernameUpdated, password: passwordUpdated, auth: secretsView.encode(usernameUpdated, passwordUpdated), @@ -166,27 +199,37 @@ describe('Interacting with the create secret forms', () => { }, }; - beforeAll(async() => secretsView.visitSecretsPage(appHost, testName)); - - it('creates registry credentials image secret', async() => { - await secretsView.createSecret(secretsView.createImageSecretLink, testName, credentialsImageSecretName, async() => { - await browser.wait(until.and(crudView.untilNoLoadersPresent, until.presenceOf(secretsView.addSecretEntryLink))); - await secretsView.addSecretEntryLink.click(); - await secretsView.imageSecretForm.each(async(el, index) => { - await el.$('input[name=address]').sendKeys(address + index); - await el.$('input[name=username]').sendKeys(username + index); - await el.$('input[name=password]').sendKeys(password + index); - await el.$('input[name=email]').sendKeys(mail + index); - }); - }); + beforeAll(async () => secretsView.visitSecretsPage(appHost, testName)); + + it('creates registry credentials image secret', async () => { + await secretsView.createSecret( + secretsView.createImageSecretLink, + testName, + credentialsImageSecretName, + async () => { + await browser.wait( + until.and( + crudView.untilNoLoadersPresent, + until.presenceOf(secretsView.addSecretEntryLink), + ), + ); + await secretsView.addSecretEntryLink.click(); + await secretsView.imageSecretForm.each(async (el, index) => { + await el.$('input[name=address]').sendKeys(address + index); + await el.$('input[name=username]').sendKeys(username + index); + await el.$('input[name=password]').sendKeys(password + index); + await el.$('input[name=email]').sendKeys(mail + index); + }); + }, + ); }); - it('check for created registry credentials image secret values', async() => { + it('check for created registry credentials image secret values', async () => { await secretsView.checkSecret(testName, credentialsImageSecretName, credentialsToCheck, true); }); - it('edits registry credentials image secret', async() => { - await secretsView.editSecret(testName, credentialsImageSecretName, async() => { + it('edits registry credentials image secret', async () => { + await secretsView.editSecret(testName, credentialsImageSecretName, async () => { await secretsView.removeSecretEntryLink.click(); await secretsView.secretAddressInput.clear(); await secretsView.secretAddressInput.sendKeys(addressUpdated); @@ -199,11 +242,16 @@ describe('Interacting with the create secret forms', () => { }); }); - it('check for edited registry credentials image secret value', async() => { - await secretsView.checkSecret(testName, credentialsImageSecretName, updatedCredentialsToCheck, true); + it('check for edited registry credentials image secret value', async () => { + await secretsView.checkSecret( + testName, + credentialsImageSecretName, + updatedCredentialsToCheck, + true, + ); }); - it('deletes the registry credentials image secret', async() => { + it('deletes the registry credentials image secret', async () => { await crudView.deleteResource('secrets', 'Secret', credentialsImageSecretName); }); }); @@ -213,8 +261,8 @@ describe('Interacting with the create secret forms', () => { const username = 'username'; const password = 'password'; const configFile = { - auths:{ - 'https://index.openshift.io/v1':{ + auths: { + 'https://index.openshift.io/v1': { username, password, auth: secretsView.encode(username, password), @@ -223,21 +271,36 @@ describe('Interacting with the create secret forms', () => { }, }; - beforeAll(async() => secretsView.visitSecretsPage(appHost, testName)); - - it('creates image secret by uploading configuration file', async() => { - await secretsView.createSecret(secretsView.createImageSecretLink, testName, uploadConfigFileImageSecretName, async() => { - await secretsView.authTypeDropdown.click().then(() => browser.actions().sendKeys(Key.ARROW_UP, Key.ENTER).perform()); - await browser.wait(until.presenceOf(secretsView.uploadFileTextArea)); - await secretsView.uploadFileTextArea.sendKeys(JSON.stringify(configFile)); - }); + beforeAll(async () => secretsView.visitSecretsPage(appHost, testName)); + + it('creates image secret by uploading configuration file', async () => { + await secretsView.createSecret( + secretsView.createImageSecretLink, + testName, + uploadConfigFileImageSecretName, + async () => { + await secretsView.authTypeDropdown.click().then(() => + browser + .actions() + .sendKeys(Key.ARROW_UP, Key.ENTER) + .perform(), + ); + await browser.wait(until.presenceOf(secretsView.uploadFileTextArea)); + await secretsView.uploadFileTextArea.sendKeys(JSON.stringify(configFile)); + }, + ); }); - it('check for created image secret values from uploaded configuration file', async() => { - await secretsView.checkSecret(testName, uploadConfigFileImageSecretName, {'.dockerconfigjson': configFile}, true); + it('check for created image secret values from uploaded configuration file', async () => { + await secretsView.checkSecret( + testName, + uploadConfigFileImageSecretName, + { '.dockerconfigjson': configFile }, + true, + ); }); - it('deletes the image secret created from uploaded configuration file', async() => { + it('deletes the image secret created from uploaded configuration file', async () => { await crudView.deleteResource('secrets', 'Secret', uploadConfigFileImageSecretName); }); }); @@ -253,26 +316,39 @@ describe('Interacting with the create secret forms', () => { const keyUpdated = 'keyUpdated'; const valueUpdated = 'valueUpdated'; - beforeAll(async() => secretsView.visitSecretsPage(appHost, testName)); - - it('creates Key/Value secret', async() => { - await secretsView.createSecret(secretsView.createGenericSecretLink, testName, keyValueSecretName, async() => { - await browser.wait(until.and(crudView.untilNoLoadersPresent, until.presenceOf(secretsView.addSecretEntryLink))); - await secretsView.addSecretEntryLink.click(); - await browser.wait(waitForCount($$('.co-file-dropzone__textarea'), 2)); - await secretsView.genericSecretForm.each(async(el, index) => { - await el.$('input[name=key]').sendKeys(key + index); - await el.$('.co-file-dropzone__textarea').sendKeys(value + index); - }); - }); + beforeAll(async () => secretsView.visitSecretsPage(appHost, testName)); + + it('creates Key/Value secret', async () => { + await secretsView.createSecret( + secretsView.createGenericSecretLink, + testName, + keyValueSecretName, + async () => { + await browser.wait( + until.and( + crudView.untilNoLoadersPresent, + until.presenceOf(secretsView.addSecretEntryLink), + ), + ); + await secretsView.addSecretEntryLink.click(); + await browser.wait(waitForCount($$('.co-file-dropzone__textarea'), 2)); + await secretsView.genericSecretForm.each(async (el, index) => { + await el.$('input[name=key]').sendKeys(key + index); + await el.$('.co-file-dropzone__textarea').sendKeys(value + index); + }); + }, + ); }); - it('check for created Key/Value secret values', async() => { - await secretsView.checkSecret(testName, keyValueSecretName, {[key0]: value0, [key1]: value1}); + it('check for created Key/Value secret values', async () => { + await secretsView.checkSecret(testName, keyValueSecretName, { + [key0]: value0, + [key1]: value1, + }); }); - it('edits Key/Value secret', async() => { - await secretsView.editSecret(testName, keyValueSecretName, async() => { + it('edits Key/Value secret', async () => { + await secretsView.editSecret(testName, keyValueSecretName, async () => { await secretsView.removeSecretEntryLink.click(); await secretsView.secretKeyInput.clear(); await secretsView.secretKeyInput.sendKeys(keyUpdated); @@ -281,11 +357,11 @@ describe('Interacting with the create secret forms', () => { }); }); - it('check for edited Key/Value secret values', async() => { - await secretsView.checkSecret(testName, keyValueSecretName, {[keyUpdated]: valueUpdated}); + it('check for edited Key/Value secret values', async () => { + await secretsView.checkSecret(testName, keyValueSecretName, { [keyUpdated]: valueUpdated }); }); - it('deletes the Key/Value secret', async() => { + it('deletes the Key/Value secret', async () => { await crudView.deleteResource('secrets', 'Secret', keyValueSecretName); }); }); @@ -298,47 +374,84 @@ describe('Add Secret to Workloads', () => { const envPrefix = 'env-'; const mountPath = '/tmp/testdata'; - - beforeAll(async()=> { + beforeAll(async () => { // create deployment and secret execSync(`kubectl run ${resourceName} --image=aosqe/hello-openshift -n ${testName}`); - execSync(`kubectl create secret generic ${secretName} --from-literal=key1=supersecret -n ${testName}`); + execSync( + `kubectl create secret generic ${secretName} --from-literal=key1=supersecret -n ${testName}`, + ); }); - beforeEach(async() => secretsView.visitSecretDetailsPage(appHost, testName, secretName)); + beforeEach(async () => secretsView.visitSecretDetailsPage(appHost, testName, secretName)); afterEach(() => { checkLogs(); checkErrors(); }); - describe('Add Secret to Workloads as Enviroment Variables', ()=> { - it('Add Secret to Deployment as Env', async() => { + describe('Add Secret to Workloads as Enviroment Variables', () => { + it('Add Secret to Deployment as Env', async () => { await secretsView.addSecretToWorkloadAsEnv(resourceName, envPrefix); - await new Promise(resolve => (function checkForValues() { - const output = secretsView.getResourceJSON(resourceName, testName, resourceKind); - if ( JSON.parse(output).status.observedGeneration === 2 ) { - return resolve(); - } - setTimeout(checkForValues, 2000); - })()); - expect(secretsView.isValueInJSONPath('spec.template.spec.containers[0].envFrom[0].secretRef.name', secretName, resourceName, testName, resourceKind)).toBe(true); - expect(secretsView.isValueInJSONPath('spec.template.spec.containers[0].envFrom[0].prefix', envPrefix, resourceName, testName, resourceKind)).toBe(true); + await new Promise((resolve) => + (function checkForValues() { + const output = secretsView.getResourceJSON(resourceName, testName, resourceKind); + if (JSON.parse(output).status.observedGeneration === 2) { + return resolve(); + } + setTimeout(checkForValues, 2000); + })(), + ); + expect( + secretsView.isValueInJSONPath( + 'spec.template.spec.containers[0].envFrom[0].secretRef.name', + secretName, + resourceName, + testName, + resourceKind, + ), + ).toBe(true); + expect( + secretsView.isValueInJSONPath( + 'spec.template.spec.containers[0].envFrom[0].prefix', + envPrefix, + resourceName, + testName, + resourceKind, + ), + ).toBe(true); }); }); - describe('Add Secret to Workloads as Volume', ()=> { - it('Add Secret to Deployment as Vol', async() => { + describe('Add Secret to Workloads as Volume', () => { + it('Add Secret to Deployment as Vol', async () => { await secretsView.addSecretToWorkloadAsVol(resourceName, mountPath); - await new Promise(resolve => (function checkForValues() { - const output = secretsView.getResourceJSON(resourceName, testName, resourceKind); - if ( JSON.parse(output).status.observedGeneration === 3 ) { - return resolve(); - } - setTimeout(checkForValues, 2000); - })()); - expect(secretsView.isValueInJSONPath('spec.template.spec.containers[0].volumeMounts[0].name', secretName, resourceName, testName, resourceKind)).toBe(true); - expect(secretsView.isValueInJSONPath('spec.template.spec.containers[0].volumeMounts[0].mountPath', mountPath, resourceName, testName, resourceKind)).toBe(true); + await new Promise((resolve) => + (function checkForValues() { + const output = secretsView.getResourceJSON(resourceName, testName, resourceKind); + if (JSON.parse(output).status.observedGeneration === 3) { + return resolve(); + } + setTimeout(checkForValues, 2000); + })(), + ); + expect( + secretsView.isValueInJSONPath( + 'spec.template.spec.containers[0].volumeMounts[0].name', + secretName, + resourceName, + testName, + resourceKind, + ), + ).toBe(true); + expect( + secretsView.isValueInJSONPath( + 'spec.template.spec.containers[0].volumeMounts[0].mountPath', + mountPath, + resourceName, + testName, + resourceKind, + ), + ).toBe(true); }); }); }); diff --git a/frontend/integration-tests/tests/service-catalog/service-binding.scenario.ts b/frontend/integration-tests/tests/service-catalog/service-binding.scenario.ts index 35bf5943eb6..2f0d93b5c83 100644 --- a/frontend/integration-tests/tests/service-catalog/service-binding.scenario.ts +++ b/frontend/integration-tests/tests/service-catalog/service-binding.scenario.ts @@ -1,5 +1,5 @@ -import {execSync} from 'child_process'; -import {browser, $, $$, ExpectedConditions as until} from 'protractor'; +import { execSync } from 'child_process'; +import { browser, $, $$, ExpectedConditions as until } from 'protractor'; import { appHost, checkLogs, checkErrors, testName } from '../../protractor.conf'; import * as srvCatalogView from '../../views/service-catalog.view'; @@ -8,7 +8,7 @@ import * as crudView from '../../views/crud.view'; import * as horizontalnavView from '../../views/horizontal-nav.view'; describe('Test for Cluster Service Binding', () => { - beforeAll(async() => { + beforeAll(async () => { browser.get(`${appHost}/status/ns/${testName}`); await browser.wait(until.presenceOf($('.pf-c-nav'))); }); @@ -18,7 +18,7 @@ describe('Test for Cluster Service Binding', () => { checkErrors(); }); - it('creates a new binding for new service instance `mysql-persistent`', async() => { + it('creates a new binding for new service instance `mysql-persistent`', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Broker Management']); await crudView.isLoaded(); await horizontalnavView.clickHorizontalTab('Service Classes'); @@ -37,7 +37,10 @@ describe('Test for Cluster Service Binding', () => { // select test namespace, then submit create instance form await $('#dropdown-selectbox').click(); - await $$('.pf-c-dropdown__menu').first().$(`#${testName}-Project-link`).click(); + await $$('.pf-c-dropdown__menu') + .first() + .$(`#${testName}-Project-link`) + .click(); await srvCatalogView.createButton.click(); await crudView.isLoaded(); diff --git a/frontend/integration-tests/tests/service-catalog/service-broker.scenario.ts b/frontend/integration-tests/tests/service-catalog/service-broker.scenario.ts index 2480c320fa8..3d393415ff5 100644 --- a/frontend/integration-tests/tests/service-catalog/service-broker.scenario.ts +++ b/frontend/integration-tests/tests/service-catalog/service-broker.scenario.ts @@ -1,4 +1,4 @@ -import {browser, $, ExpectedConditions as until, by} from 'protractor'; +import { browser, $, ExpectedConditions as until, by } from 'protractor'; import { appHost, checkLogs, checkErrors, testName } from '../../protractor.conf'; import * as srvCatalogView from '../../views/service-catalog.view'; @@ -6,7 +6,7 @@ import * as sidenavView from '../../views/sidenav.view'; import * as crudView from '../../views/crud.view'; describe('Test for Cluster Service Broker', () => { - beforeAll(async() => { + beforeAll(async () => { browser.get(`${appHost}/status/ns/${testName}`); await browser.wait(until.presenceOf($('.pf-c-nav'))); }); @@ -16,11 +16,14 @@ describe('Test for Cluster Service Broker', () => { checkErrors(); }); - it('displays `MariaDB` service class for `template-service-broker`', async() => { + it('displays `MariaDB` service class for `template-service-broker`', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Broker Management']); await crudView.isLoaded(); - await crudView.rowForName('template-service-broker').element(by.linkText('template-service-broker')).click(); + await crudView + .rowForName('template-service-broker') + .element(by.linkText('template-service-broker')) + .click(); await crudView.isLoaded(); await crudView.navTabFor('Service Classes').click(); diff --git a/frontend/integration-tests/tests/service-catalog/service-catalog.scenario.ts b/frontend/integration-tests/tests/service-catalog/service-catalog.scenario.ts index b9b6ba374a6..a9868cc069d 100644 --- a/frontend/integration-tests/tests/service-catalog/service-catalog.scenario.ts +++ b/frontend/integration-tests/tests/service-catalog/service-catalog.scenario.ts @@ -1,4 +1,4 @@ -import {browser, $, ExpectedConditions as until} from 'protractor'; +import { browser, $, ExpectedConditions as until } from 'protractor'; import { appHost, checkLogs, checkErrors, testName } from '../../protractor.conf'; import * as sidenavView from '../../views/sidenav.view'; @@ -7,7 +7,7 @@ import * as crudView from '../../views/crud.view'; import * as srvCatalogView from '../../views/service-catalog.view'; describe('Test for existence of Service Catalog nav items', () => { - beforeAll(async() => { + beforeAll(async () => { browser.get(`${appHost}/status/ns/${testName}`); await browser.wait(until.presenceOf($('.pf-c-nav'))); }); @@ -17,20 +17,20 @@ describe('Test for existence of Service Catalog nav items', () => { checkErrors(); }); - it('displays `Service Catalog` nav menu item in sidebar', async() => { + it('displays `Service Catalog` nav menu item in sidebar', async () => { await browser.wait(until.presenceOf(sidenavView.navSectionFor('Service Catalog'))); expect(sidenavView.navSectionFor('Service Catalog').isDisplayed()).toBe(true); }); - it('displays `template-service-broker`', async() => { + it('displays `template-service-broker`', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Broker Management']); await crudView.isLoaded(); expect(crudView.rowForName('template-service-broker').isDisplayed()).toBe(true); }); - it('displays `MariaDB` service class', async() => { + it('displays `MariaDB` service class', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Broker Management']); await horizontalnavView.clickHorizontalTab('Service Classes'); await crudView.isLoaded(); @@ -41,14 +41,14 @@ describe('Test for existence of Service Catalog nav items', () => { expect(srvCatalogView.linkForCSC('MariaDB').isDisplayed()).toBe(true); }); - it('initially displays no service instances', async() => { + it('initially displays no service instances', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Provisioned Services']); await crudView.isLoaded(); expect(crudView.emptyState.getText()).toEqual('No Service Instances Found'); }); - it('initially displays no service bindings', async() => { + it('initially displays no service bindings', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Provisioned Services']); await horizontalnavView.clickHorizontalTab('Service Bindings'); await crudView.isLoaded(); diff --git a/frontend/integration-tests/tests/service-catalog/service-class.scenario.ts b/frontend/integration-tests/tests/service-catalog/service-class.scenario.ts index 1caaf80fcc2..0b8dd70b5e4 100644 --- a/frontend/integration-tests/tests/service-catalog/service-class.scenario.ts +++ b/frontend/integration-tests/tests/service-catalog/service-class.scenario.ts @@ -1,14 +1,14 @@ -import {browser, $, $$, ExpectedConditions as until, by} from 'protractor'; +import { browser, $, $$, ExpectedConditions as until, by } from 'protractor'; import { appHost, checkLogs, checkErrors, testName } from '../../protractor.conf'; import * as srvCatalogView from '../../views/service-catalog.view'; import * as sidenavView from '../../views/sidenav.view'; import * as horizontalnavView from '../../views/horizontal-nav.view'; import * as crudView from '../../views/crud.view'; -import {execSync} from 'child_process'; +import { execSync } from 'child_process'; describe('Test for Cluster Service Class', () => { - beforeAll(async() => { + beforeAll(async () => { browser.get(`${appHost}/status/ns/${testName}`); await browser.wait(until.presenceOf($('.pf-c-nav'))); }); @@ -18,12 +18,11 @@ describe('Test for Cluster Service Class', () => { checkErrors(); }); - it('displays `default` service plan for service class `MariaDB`', async() => { + it('displays `default` service plan for service class `MariaDB`', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Broker Management']); await crudView.isLoaded(); await horizontalnavView.clickHorizontalTab('Service Classes'); - // Filter by service class name to make sure it is on the first page of results. // Otherwise the tests fail since we do virtual scrolling and the element isn't found. await crudView.filterForName('MariaDB'); @@ -35,13 +34,16 @@ describe('Test for Cluster Service Class', () => { await crudView.navTabFor('Service Plans').click(); await crudView.isLoaded(); - await crudView.rowForName('default').element(by.linkText('default')).click(); + await crudView + .rowForName('default') + .element(by.linkText('default')) + .click(); await crudView.isLoaded(); expect(crudView.resourceTitle.getText()).toEqual('default'); }); - it('creates a new instance for service class `MariaDB`', async() => { + it('creates a new instance for service class `MariaDB`', async () => { await sidenavView.clickNavLink(['Service Catalog', 'Broker Management']); await crudView.isLoaded(); await horizontalnavView.clickHorizontalTab('Service Classes'); @@ -61,7 +63,10 @@ describe('Test for Cluster Service Class', () => { // select test namespace, then submit form await $('#dropdown-selectbox').click(); - await $$('.pf-c-dropdown__menu').first().$(`#${testName}-Project-link`).click(); + await $$('.pf-c-dropdown__menu') + .first() + .$(`#${testName}-Project-link`) + .click(); await srvCatalogView.createButton.click(); await crudView.isLoaded(); diff --git a/frontend/integration-tests/tests/storage.scenario.ts b/frontend/integration-tests/tests/storage.scenario.ts index 8c9dfd54ccf..dfd58cf64ba 100644 --- a/frontend/integration-tests/tests/storage.scenario.ts +++ b/frontend/integration-tests/tests/storage.scenario.ts @@ -5,10 +5,16 @@ import * as storageView from '../views/storage.view'; import { execSync } from 'child_process'; describe('Add storage is applicable for all workloads', () => { - - const k8sWorkloads = ['replicationcontrollers', 'daemonsets', 'deployments', 'replicasets', 'statefulsets']; + const k8sWorkloads = [ + 'replicationcontrollers', + 'daemonsets', + 'deployments', + 'replicasets', + 'statefulsets', + ]; const openshiftWorkloads = ['deploymentconfigs']; - const resourceObjs = browser.params.openshift === 'true' ? k8sWorkloads.concat(openshiftWorkloads) : k8sWorkloads; + const resourceObjs = + browser.params.openshift === 'true' ? k8sWorkloads.concat(openshiftWorkloads) : k8sWorkloads; afterEach(() => { checkLogs(); @@ -30,11 +36,11 @@ describe('Add storage is applicable for all workloads', () => { const pvcName = `${resourceType}-pvc`; const pvcSize = '1'; const mountPath = '/data'; - it(`create a ${resourceType} resource`, async() => { + it(`create a ${resourceType} resource`, async () => { await crudView.createNamespacedResourceWithDefaultYAML(resourceType); expect(crudView.errorMessage.isPresent()).toBe(false); }); - it(`add storage to ${resourceType}`, async() => { + it(`add storage to ${resourceType}`, async () => { await storageView.addNewStorageToWorkload(pvcName, pvcSize, mountPath); expect(crudView.errorMessage.isPresent()).toBe(false); @@ -42,8 +48,12 @@ describe('Add storage is applicable for all workloads', () => { await browser.wait(until.presenceOf(volumeTile)); const volumeRow = await $(`[data-id="${pvcName}-${mountPath}"]`); await browser.wait(until.presenceOf(volumeRow)); - expect($(`[data-id="${pvcName}-${mountPath}"] [data-test-id="name"]`).getText()).toContain(pvcName); - expect($(`[data-id="${pvcName}-${mountPath}"] [data-test-id="path"]`).getText()).toContain(mountPath); + expect($(`[data-id="${pvcName}-${mountPath}"] [data-test-id="name"]`).getText()).toContain( + pvcName, + ); + expect($(`[data-id="${pvcName}-${mountPath}"] [data-test-id="path"]`).getText()).toContain( + mountPath, + ); }); }); }); diff --git a/frontend/integration-tests/views/catalog-page.view.ts b/frontend/integration-tests/views/catalog-page.view.ts index d880c01d4cf..1a79665cb1e 100644 --- a/frontend/integration-tests/views/catalog-page.view.ts +++ b/frontend/integration-tests/views/catalog-page.view.ts @@ -1,7 +1,8 @@ import { $, $$, element, by } from 'protractor'; export const catalogTiles = $$('.catalog-tile-pf'); -export const catalogTileFor = (name: string) => element(by.cssContainingText('.catalog-tile-pf-title', name)); +export const catalogTileFor = (name: string) => + element(by.cssContainingText('.catalog-tile-pf-title', name)); export const catalogTileById = (id: string) => $(`[data-test=${id}]`); // FilterSidePanel views @@ -9,8 +10,12 @@ export const filterSectionFor = (group: string) => $(`[data-test-group-name=${gr export const showMoreFilters = (group: string) => $(`[data-test-group-name=${group}] .btn-link`); export const filterCheckboxFor = (id: string) => $(`input[data-test=${id}]`); export const clickFilterCheckbox = (id: string) => filterCheckboxFor(id).click(); -export const filterCheckboxCount = (id: string) => filterCheckboxFor(id).$('.item-count').getText() - .then(text => parseInt(text.substring(1, text.indexOf(')')), 10)); +export const filterCheckboxCount = (id: string) => + filterCheckboxFor(id) + .$('.item-count') + .getText() + .then((text) => parseInt(text.substring(1, text.indexOf(')')), 10)); export const filterTextbox = $('.co-catalog-page__filter input'); -export const filterByKeyword = (filter: string) => filterTextbox.clear().then(() => filterTextbox.sendKeys(filter)); +export const filterByKeyword = (filter: string) => + filterTextbox.clear().then(() => filterTextbox.sendKeys(filter)); export const clearFiltersText = $('[data-test-id="catalog-clear-filters"]'); diff --git a/frontend/integration-tests/views/catalog.view.ts b/frontend/integration-tests/views/catalog.view.ts index a69a4958c35..16ffe8999e8 100644 --- a/frontend/integration-tests/views/catalog.view.ts +++ b/frontend/integration-tests/views/catalog.view.ts @@ -3,9 +3,14 @@ import { $, $$, browser, by, ExpectedConditions as until } from 'protractor'; export const categoryTabs = $$('.vertical-tabs-pf-tab > a'); export const pageHeading = $('.co-catalog-page__heading'); export const pageNumberItemsHeading = $('.co-catalog-page__num-items'); -export const pageHeadingNumberOfItems = () => pageNumberItemsHeading.getText() - .then(text => parseInt(text.substring(0, text.indexOf(' items')), 10)); -export const catalogDetailsLoaded = () => browser.wait(until.presenceOf($('.modal-content')), 10000).then(() => browser.sleep(1000)); +export const pageHeadingNumberOfItems = () => + pageNumberItemsHeading + .getText() + .then((text) => parseInt(text.substring(0, text.indexOf(' items')), 10)); +export const catalogDetailsLoaded = () => + browser.wait(until.presenceOf($('.modal-content')), 10000).then(() => browser.sleep(1000)); export const createServiceInstanceButton = $('.co-catalog-page__overlay-create'); export const createServiceInstanceForm = $('.co-create-service-instance'); -export const createServiceBindingButton = $('.co-hint-block').element(by.buttonText('Create Service Binding')); +export const createServiceBindingButton = $('.co-hint-block').element( + by.buttonText('Create Service Binding'), +); diff --git a/frontend/integration-tests/views/cluster-settings.view.ts b/frontend/integration-tests/views/cluster-settings.view.ts index 538827d65c5..99137e2f667 100644 --- a/frontend/integration-tests/views/cluster-settings.view.ts +++ b/frontend/integration-tests/views/cluster-settings.view.ts @@ -1,13 +1,17 @@ import { element, by, browser, $$, $ } from 'protractor'; import { waitForNone } from '../protractor.conf'; -export const heading = element(by.cssContainingText('[data-test-id="cluster-settings-page-heading"]', 'Cluster Settings')); -export const isLoaded = async() => await browser.wait(waitForNone($$('.co-m-loader'))); +export const heading = element( + by.cssContainingText('[data-test-id="cluster-settings-page-heading"]', 'Cluster Settings'), +); +export const isLoaded = async () => await browser.wait(waitForNone($$('.co-m-loader'))); export const channelUpdateLink = $('[data-test-id="current-channel-update-link"]'); export const channelDropdownButton = $('[data-test-id="dropdown-button"]'); export const getSelectedChannel = $$('[data-test-id="dropdown-menu"]'); export const channelPopupCancelButton = $('[data-test-id="modal-cancel-action"]'); -export const globalConfigResourceRow = $('[data-test-action="Console"]').$$('[data-test-id="kebab-button"]'); +export const globalConfigResourceRow = $('[data-test-action="Console"]').$$( + '[data-test-id="kebab-button"]', +); export const clusterOperatorResourceLink = $('[data-test-id="console"]'); export const globalConfigResourceLink = $('[data-test-id="Console"]'); export const clusterResourceDetailsTitle = $('[data-test-id="resource-title"]'); diff --git a/frontend/integration-tests/views/create-role-binding.view.ts b/frontend/integration-tests/views/create-role-binding.view.ts index c115f69a6d0..4c5098b8117 100644 --- a/frontend/integration-tests/views/create-role-binding.view.ts +++ b/frontend/integration-tests/views/create-role-binding.view.ts @@ -1,16 +1,20 @@ import { $, browser, ExpectedConditions as until, element, by } from 'protractor'; -const selectFromDropdown = async(dropdownButton, text) => { +const selectFromDropdown = async (dropdownButton, text) => { await dropdownButton.click(); await browser.wait(until.presenceOf($('[data-test-id=dropdown-text-filter]'))); - await browser.actions().sendKeys(text).perform(); + await browser + .actions() + .sendKeys(text) + .perform(); await element(by.cssContainingText('li[role=option] a', text)).click(); await browser.wait(until.not(until.visibilityOf($('.pf-c-dropdown__menu')))); }; export const selectNamespace = (namespace) => selectFromDropdown($('#ns-dropdown'), namespace); export const selectRole = (role) => selectFromDropdown($('#role-dropdown'), role); -export const getSelectedNamespace = () => $('#ns-dropdown .co-resource-item__resource-name').getText(); +export const getSelectedNamespace = () => + $('#ns-dropdown .co-resource-item__resource-name').getText(); export const getSelectedRole = () => $('#role-dropdown .co-resource-item__resource-name').getText(); export const inputName = (name) => $('#role-binding-name').sendKeys(name); export const inputSubject = (subject) => $('#subject-name').sendKeys(subject); diff --git a/frontend/integration-tests/views/crud.view.ts b/frontend/integration-tests/views/crud.view.ts index 19ae2b5f96f..5363185b0fe 100644 --- a/frontend/integration-tests/views/crud.view.ts +++ b/frontend/integration-tests/views/crud.view.ts @@ -18,23 +18,43 @@ export const cancelBtn = $('#cancel'); */ export const untilLoadingBoxLoaded = until.presenceOf($('.loading-box__loaded')); export const untilNoLoadersPresent = waitForNone($$('.co-m-loader')); -export const isLoaded = () => browser.wait(until.and(untilNoLoadersPresent, untilLoadingBoxLoaded)).then(() => browser.sleep(1000)); -export const resourceRowsPresent = () => browser.wait(until.presenceOf($('.co-m-resource-icon + a')), 10000); +export const isLoaded = () => + browser + .wait(until.and(untilNoLoadersPresent, untilLoadingBoxLoaded)) + .then(() => browser.sleep(1000)); +export const resourceRowsPresent = () => + browser.wait(until.presenceOf($('.co-m-resource-icon + a')), 10000); export const resourceRows = $$('[data-test-rows="resource-row"]'); export const resourceRowNamesAndNs = $$('.co-m-resource-icon + a'); -export const rowForName = (name: string) => resourceRows.filter((row) => row.$$('.co-m-resource-icon + a').first().getText().then(text => text === name)).first(); -export const rowForOperator = (name: string) => resourceRows.filter((row) => row.$('.co-clusterserviceversion-logo__name__clusterserviceversion').getText().then(text => text === name)).first(); +export const rowForName = (name: string) => + resourceRows + .filter((row) => + row + .$$('.co-m-resource-icon + a') + .first() + .getText() + .then((text) => text === name), + ) + .first(); +export const rowForOperator = (name: string) => + resourceRows + .filter((row) => + row + .$('.co-clusterserviceversion-logo__name__clusterserviceversion') + .getText() + .then((text) => text === name), + ) + .first(); const navMenu = $('.co-m-horizontal-nav__menu'); const isNavLoaded = () => browser.wait(until.presenceOf(navMenu)); export const navTabFor = (name: string) => navMenu.element(by.linkText(name)); -export const clickTab = async(name: string) => { +export const clickTab = async (name: string) => { await isNavLoaded(); await navTabFor(name).click(); }; - export const labelsForRow = (name: string) => rowForName(name).$$('.co-m-label'); export const textFilter = $('.co-m-pane__filter-bar-group--filter input'); export const actions = Object.freeze({ @@ -45,16 +65,15 @@ export const actions = Object.freeze({ }); export const actionForLabel = (label: string) => $(`[data-test-action="${label}"]`); -export const filterForName = async(name: string) => { +export const filterForName = async (name: string) => { await browser.wait(until.presenceOf(textFilter)); await textFilter.sendKeys(name); }; const actionOnKind = (action: string, kind: string) => { - const humanizedKind = (kind.includes('~') - ? kind.split('~')[2] - : kind - ).split(/(?=[A-Z])/).join(' '); + const humanizedKind = (kind.includes('~') ? kind.split('~')[2] : kind) + .split(/(?=[A-Z])/) + .join(' '); return `${action} ${humanizedKind}`; }; @@ -62,7 +81,9 @@ export const editHumanizedKind = (kind: string) => actionOnKind(actions.edit, ki export const deleteHumanizedKind = (kind: string) => actionOnKind(actions.delete, kind); export const clickKebabAction = (resourceName: string, actionLabel: string) => { - return rowForName(resourceName).$('[data-test-id="kebab-button"]').click() + return rowForName(resourceName) + .$('[data-test-id="kebab-button"]') + .click() .then(() => browser.wait(until.elementToBeClickable(actionForLabel(actionLabel)))) .then(() => actionForLabel(actionLabel).click()); }; @@ -70,8 +91,8 @@ export const clickKebabAction = (resourceName: string, actionLabel: string) => { /** * Edit row from a list. */ -export const editRow = (kind: string) => (name: string) => clickKebabAction(name, editHumanizedKind(kind)) - .then(async() => { +export const editRow = (kind: string) => (name: string) => + clickKebabAction(name, editHumanizedKind(kind)).then(async () => { await browser.wait(until.presenceOf(cancelBtn)); const reloadBtnIsPresent = await reloadBtn.isPresent(); if (reloadBtnIsPresent) { @@ -83,8 +104,8 @@ export const editRow = (kind: string) => (name: string) => clickKebabAction(name /** * Deletes a row from a list. Does not wait until the row is no longer visible. */ -export const deleteRow = (kind: string) => (name: string) => clickKebabAction(name, deleteHumanizedKind(kind)) - .then(async() => { +export const deleteRow = (kind: string) => (name: string) => + clickKebabAction(name, deleteHumanizedKind(kind)).then(async () => { switch (kind) { case 'Namespace': await browser.wait(until.presenceOf($('input[placeholder="Enter name"]'))); @@ -97,15 +118,17 @@ export const deleteRow = (kind: string) => (name: string) => clickKebabAction(na await $('#confirm-action').click(); - const kebabIsDisabled = until.not(until.elementToBeClickable(rowForName(name).$('.co-kebab__button'))); + const kebabIsDisabled = until.not( + until.elementToBeClickable(rowForName(name).$('.co-kebab__button')), + ); const listIsEmpty = until.textToBePresentInElement($('.cos-status-box > .text-center'), 'No '); const rowIsGone = until.not(until.presenceOf(rowForName(name).$('.co-kebab'))); return browser.wait(until.or(kebabIsDisabled, until.or(listIsEmpty, rowIsGone))); - }); export const rowFilters = $$('.row-filter__box'); -export const rowFilterFor = (name: string) => rowFilters.filter(el => el.getText().then(text => text.includes(name))).first(); +export const rowFilterFor = (name: string) => + rowFilters.filter((el) => el.getText().then((text) => text.includes(name))).first(); export const activeRowFilters = $$('.row-filter__box--active'); export const statusMessageTitle = $('.cos-status-box__title'); @@ -119,13 +142,15 @@ export const resourceTitle = $('[data-test-id="resource-title"]'); export const nameFilter = $('.pf-c-form-control.co-text-filter'); export const messageLbl = $('.cos-status-box'); -export const modalAnnotationsLink = $('[data-test-id=resource-summary] [data-test-id=edit-annotations]'); +export const modalAnnotationsLink = $( + '[data-test-id=resource-summary] [data-test-id=edit-annotations]', +); -export const visitResource = async(resource: string, name: string) => { +export const visitResource = async (resource: string, name: string) => { await browser.get(`${appHost}/k8s/ns/${testName}/${resource}/${name}`); }; -export const clickDetailsPageAction = async(actionID: string) => { +export const clickDetailsPageAction = async (actionID: string) => { const action = actionForLabel(actionID); await browser.wait(until.presenceOf(actionsButton)); await actionsButton.click(); @@ -133,7 +158,7 @@ export const clickDetailsPageAction = async(actionID: string) => { await action.click(); }; -export const deleteResource = async(resource: string, kind: string, name: string) => { +export const deleteResource = async (resource: string, kind: string, name: string) => { await visitResource(resource, name); await isLoaded(); clickDetailsPageAction(deleteHumanizedKind(kind)); @@ -143,19 +168,23 @@ export const deleteResource = async(resource: string, kind: string, name: string // Navigates to create new resource page, creates an example resource of the specified kind, // then navigates back to the original url. -export const createNamespacedTestResource = async(kindModel, name) => { +export const createNamespacedTestResource = async (kindModel, name) => { const next = await browser.getCurrentUrl(); await browser.get(`${appHost}/k8s/ns/${testName}/${kindModel.plural}/~new`); await yamlView.isLoaded(); const content = await yamlView.getEditorContent(); - const newContent = _.defaultsDeep({}, {metadata: {name, labels: {automatedTestName: testName}}}, safeLoad(content)); + const newContent = _.defaultsDeep( + {}, + { metadata: { name, labels: { automatedTestName: testName } } }, + safeLoad(content), + ); await yamlView.setEditorContent(safeDump(newContent)); await yamlView.saveButton.click(); await browser.wait(until.presenceOf($(`.co-m-${kindModel.kind}`))); await browser.get(next); }; -export const checkResourceExists = async(resource: string, name: string) => { +export const checkResourceExists = async (resource: string, name: string) => { await visitResource(resource, name); await isLoaded(); await browser.wait(until.presenceOf(actionsButton)); @@ -167,7 +196,7 @@ export const emptyState = $('.cos-status-box').$('.text-center'); export const errorMessage = $('.pf-c-alert.pf-m-inline.pf-m-danger'); export const successMessage = $('.pf-c-alert.pf-m-inline.pf-m-success'); -export const clickListPageCreateYAMLButton = async() => { +export const clickListPageCreateYAMLButton = async () => { const createDropdownIsPresent = await createItemButton.isPresent(); if (createDropdownIsPresent) { await createItemButton.click(); @@ -176,7 +205,12 @@ export const clickListPageCreateYAMLButton = async() => { await browser.wait(until.presenceOf(createYAMLButton)); await createYAMLButton.click(); } - await browser.wait(until.and(untilNoLoadersPresent, until.presenceOf(element(by.cssContainingText('h1', 'Create'))))); + await browser.wait( + until.and( + untilNoLoadersPresent, + until.presenceOf(element(by.cssContainingText('h1', 'Create'))), + ), + ); }; export const createNamespacedResourceWithDefaultYAML = async function(resourceType: string) { diff --git a/frontend/integration-tests/views/devconsole-view/dev-perspective.view.ts b/frontend/integration-tests/views/devconsole-view/dev-perspective.view.ts index a0f09e8c06a..125865194d6 100644 --- a/frontend/integration-tests/views/devconsole-view/dev-perspective.view.ts +++ b/frontend/integration-tests/views/devconsole-view/dev-perspective.view.ts @@ -4,9 +4,13 @@ export const switcher = element(by.css('[data-test-id="perspective-switcher-togg export const switcherMenu = element(by.css('[data-test-id="perspective-switcher-menu"]')); -export const devPerspective = element(by.cssContainingText('.pf-c-dropdown__menu-item', 'Developer')); +export const devPerspective = element( + by.cssContainingText('.pf-c-dropdown__menu-item', 'Developer'), +); -export const adminPerspective = element(by.cssContainingText('.pf-c-dropdown__menu-item', 'Administrator')); +export const adminPerspective = element( + by.cssContainingText('.pf-c-dropdown__menu-item', 'Administrator'), +); export enum Perspective { Developer = 'Developer Perspective', @@ -16,7 +20,6 @@ export enum Perspective { export const pageSidebar = $('#page-sidebar .pf-c-nav .pf-c-nav__list'); export const sideHeader = $('#page-sidebar .oc-nav-header h1'); - export const switchPerspective = async function(perspective: Perspective) { await browser.wait(until.elementToBeClickable(switcher), 5000); await switcher.click(); diff --git a/frontend/integration-tests/views/environment.view.ts b/frontend/integration-tests/views/environment.view.ts index e514c276c27..76efb26768f 100644 --- a/frontend/integration-tests/views/environment.view.ts +++ b/frontend/integration-tests/views/environment.view.ts @@ -16,7 +16,7 @@ const textFilter = $('[placeholder="Config Map or Secret"]'); export const isLoaded = () => browser.wait(until.presenceOf(inputs.first()), BROWSER_TIMEOUT); -export const addVariable = async(key: string, value: string) => { +export const addVariable = async (key: string, value: string) => { await isLoaded(); await inputs.get(0).clear(); await inputs.get(0).sendKeys(key); @@ -26,7 +26,7 @@ export const addVariable = async(key: string, value: string) => { await saveBtn.click(); }; -export const addVariableFrom = async(resourceName: string, resourcePrefix: string) => { +export const addVariableFrom = async (resourceName: string, resourcePrefix: string) => { await isLoaded(); await dropDownBtn.first().click(); await textFilter.sendKeys(resourceName); @@ -37,7 +37,7 @@ export const addVariableFrom = async(resourceName: string, resourcePrefix: strin await saveBtn.click(); }; -export const deleteVariable = async() => { +export const deleteVariable = async () => { await isLoaded(); await browser.wait(until.elementToBeClickable(saveBtn), BROWSER_TIMEOUT); await deleteBtn.click(); diff --git a/frontend/integration-tests/views/horizontal-nav.view.ts b/frontend/integration-tests/views/horizontal-nav.view.ts index 682d8bd13e4..3e274460d8f 100644 --- a/frontend/integration-tests/views/horizontal-nav.view.ts +++ b/frontend/integration-tests/views/horizontal-nav.view.ts @@ -1,8 +1,11 @@ import { $$, by, browser, element, ExpectedConditions as until } from 'protractor'; -export const horizontalTabFor = (name: string) => element(by.cssContainingText('.co-m-horizontal-nav__menu-item', name)); +export const horizontalTabFor = (name: string) => + element(by.cssContainingText('.co-m-horizontal-nav__menu-item', name)); -export const clickHorizontalTab = (tabName: string) => browser.wait(until.visibilityOf(horizontalTabFor(tabName))) - .then(() => horizontalTabFor(tabName).click()); +export const clickHorizontalTab = (tabName: string) => + browser + .wait(until.visibilityOf(horizontalTabFor(tabName))) + .then(() => horizontalTabFor(tabName).click()); export const activeTab = $$('.co-m-horizontal-nav-item--active'); diff --git a/frontend/integration-tests/views/login.view.ts b/frontend/integration-tests/views/login.view.ts index 87f9f2d5a2a..be9f68e6570 100644 --- a/frontend/integration-tests/views/login.view.ts +++ b/frontend/integration-tests/views/login.view.ts @@ -7,7 +7,7 @@ export const submitButton = $('button[type=submit]'); export const logOutLink = element(by.linkText('Log out')); export const userDropdown = $('[data-test=user-dropdown] .pf-c-app-launcher__toggle'); -export const selectProvider = async(provider: string) => { +export const selectProvider = async (provider: string) => { const idpLink = element(by.cssContainingText('.idp', provider)); while (!(await idpLink.isPresent())) { await browser.get(appHost); @@ -16,7 +16,7 @@ export const selectProvider = async(provider: string) => { await idpLink.click(); }; -export const login = async(providerName: string, username: string, password: string) => { +export const login = async (providerName: string, username: string, password: string) => { if (providerName) { await selectProvider(providerName); } @@ -27,7 +27,7 @@ export const login = async(providerName: string, username: string, password: str await browser.wait(until.presenceOf(userDropdown)); }; -export const logout = async() => { +export const logout = async () => { await browser.wait(until.presenceOf(userDropdown)); await userDropdown.click(); await browser.wait(until.presenceOf(logOutLink)); diff --git a/frontend/integration-tests/views/modal-annotations.view.ts b/frontend/integration-tests/views/modal-annotations.view.ts index e3e3f63c934..1a599ac6acb 100644 --- a/frontend/integration-tests/views/modal-annotations.view.ts +++ b/frontend/integration-tests/views/modal-annotations.view.ts @@ -3,7 +3,9 @@ import { $, $$, browser, ExpectedConditions as until } from 'protractor'; const BROWSER_TIMEOUT = 15000; const addMoreBtn = $('[data-test-id="pairs-list__add-btn"]'); -export const cancelBtn = $$('.pf-m-secondary').filter(link => link.getText().then(text => text.startsWith('Cancel'))).first(); +export const cancelBtn = $$('.pf-m-secondary') + .filter((link) => link.getText().then((text) => text.startsWith('Cancel'))) + .first(); export const confirmActionBtn = $('#confirm-action'); const annotationRows = $$('.pairs-list__row'); export const annotationRowsKey = $$('[placeholder="key"]'); @@ -13,19 +15,25 @@ export const annotationDialogOverlay = $('.co-overlay'); export const isLoaded = () => browser.wait(until.presenceOf(addMoreBtn), BROWSER_TIMEOUT); -export const addAnnotation = async function( key: string, value: string) { +export const addAnnotation = async function(key: string, value: string) { const initialRowCount = await annotationRows.count(); await addMoreBtn.click(); await isLoaded(); - await browser.wait(until.elementToBeClickable(annotationRowsKey.get(initialRowCount)), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(annotationRowsKey.get(initialRowCount)), + BROWSER_TIMEOUT, + ); await annotationRowsKey.get(initialRowCount).sendKeys(key); - await browser.wait(until.elementToBeClickable(annotationRowsValue.get(initialRowCount)), BROWSER_TIMEOUT); + await browser.wait( + until.elementToBeClickable(annotationRowsValue.get(initialRowCount)), + BROWSER_TIMEOUT, + ); await annotationRowsValue.get(initialRowCount).sendKeys(value); }; -export const updateAnnotation = async function( annotationKey: string, annotationValue: string) { +export const updateAnnotation = async function(annotationKey: string, annotationValue: string) { let found = false; - await annotationRowsKey.each( async function(item, index) { + await annotationRowsKey.each(async function(item, index) { if (found) { return; } @@ -36,14 +44,14 @@ export const updateAnnotation = async function( annotationKey: string, annotatio found = true; } }); - if (!found){ + if (!found) { throw new Error(`Key not found [${annotationKey}]`); } }; -export const deleteAnnotation = async function( annotationKey: string) { +export const deleteAnnotation = async function(annotationKey: string) { let found = false; - await annotationRowsKey.each( async function(item, index) { + await annotationRowsKey.each(async function(item, index) { if (found) { return; } diff --git a/frontend/integration-tests/views/monitoring.view.ts b/frontend/integration-tests/views/monitoring.view.ts index e59b9bfc978..1139de51ee0 100644 --- a/frontend/integration-tests/views/monitoring.view.ts +++ b/frontend/integration-tests/views/monitoring.view.ts @@ -1,6 +1,6 @@ import { $, $$, browser } from 'protractor'; -export const wait = async(condition) => await browser.wait(condition, 15000); +export const wait = async (condition) => await browser.wait(condition, 15000); // List pages export const listPageHeading = $('.co-m-pane__heading'); diff --git a/frontend/integration-tests/views/search.view.ts b/frontend/integration-tests/views/search.view.ts index bba1c66e67d..6aa63927862 100644 --- a/frontend/integration-tests/views/search.view.ts +++ b/frontend/integration-tests/views/search.view.ts @@ -5,9 +5,9 @@ const BROWSER_TIMEOUT = 15000; export const dropdown = $('.co-type-selector .pf-c-dropdown__toggle'); export const dropdownLinks = $$('.pf-c-dropdown__menu a'); export const labelFilter = $('.co-search-input input'); -export const linkForType = type => $(`#${type}-link`); +export const linkForType = (type) => $(`#${type}-link`); -export const selectSearchType = async(objectType: string) => { +export const selectSearchType = async (objectType: string) => { const isPresent = await dropdownLinks.isPresent(); if (!isPresent) { await dropdown.click(); diff --git a/frontend/integration-tests/views/secrets.view.ts b/frontend/integration-tests/views/secrets.view.ts index c35fdcfbdcc..d07a1432d2c 100644 --- a/frontend/integration-tests/views/secrets.view.ts +++ b/frontend/integration-tests/views/secrets.view.ts @@ -1,4 +1,12 @@ -import { $, $$, browser, by, ExpectedConditions as until, element, ElementFinder } from 'protractor'; +import { + $, + $$, + browser, + by, + ExpectedConditions as until, + element, + ElementFinder, +} from 'protractor'; import { Base64 } from 'js-base64'; import { execSync } from 'child_process'; import * as _ from 'lodash'; @@ -27,7 +35,9 @@ export const createImageSecretLink = $('#image-link'); export const createGenericSecretLink = $('#generic-link'); export const addSecretEntryLink = $('.co-create-secret-form__link--add-entry'); -export const removeSecretEntryLink = $$('.co-create-secret-form__link--remove-entry .pf-m-link').first(); +export const removeSecretEntryLink = $$( + '.co-create-secret-form__link--remove-entry .pf-m-link', +).first(); export const imageSecretForm = $$('.co-create-image-secret__form'); export const genericSecretForm = $$('.co-create-generic-secret__form'); @@ -40,29 +50,34 @@ const addSecretAsVol = $('#co-add-secret-to-workload__volume'); const addSecretAsEnvPrefixInput = $('#co-add-secret-to-workload__prefix'); const addSecretAsVolMntPathInput = $('#co-add-secret-to-workload__mountpath'); -export const visitSecretsPage = async(appHost: string, ns: string) => { +export const visitSecretsPage = async (appHost: string, ns: string) => { await browser.get(`${appHost}/k8s/ns/${ns}/secrets`); await crudView.isLoaded(); }; -export const visitSecretDetailsPage = async(appHost: string, ns: string, secretname: string) => { +export const visitSecretDetailsPage = async (appHost: string, ns: string, secretname: string) => { await browser.get(`${appHost}/k8s/ns/${ns}/secrets/${secretname}`); await crudView.isLoaded(); }; -export const clickAddSecretToWorkload = async() => { +export const clickAddSecretToWorkload = async () => { await browser.wait(until.presenceOf(addSecrettoWorkloadButton)); await addSecrettoWorkloadButton.click(); }; -export const clickRevealValues = async() => { +export const clickRevealValues = async () => { await browser.wait(until.presenceOf(revealValuesButton)); await revealValuesButton.click(); }; export const encode = (username, password) => Base64.encode(`${username}:${password}`); -export const createSecret = async(linkElement: ElementFinder, ns: string, name: string, updateForm: Function) => { +export const createSecret = async ( + linkElement: ElementFinder, + ns: string, + name: string, + updateForm: Function, +) => { await crudView.createItemButton.click(); await linkElement.click(); await browser.wait(until.presenceOf(secretNameInput)); @@ -72,11 +87,16 @@ export const createSecret = async(linkElement: ElementFinder, ns: string, name: expect(crudView.errorMessage.isPresent()).toBe(false); }; -export const checkSecret = async(ns: string, name: string, keyValuesToCheck: Object, jsonOutput: boolean = false) => { +export const checkSecret = async ( + ns: string, + name: string, + keyValuesToCheck: Object, + jsonOutput: boolean = false, +) => { await browser.wait(until.urlContains(`/k8s/ns/${ns}/secrets/${name}`)); await browser.wait(until.textToBePresentInElement($('.co-m-pane__heading'), name)); await clickRevealValues(); - const renderedKeyValues = await dt.reduce(async(acc, el, index) => { + const renderedKeyValues = await dt.reduce(async (acc, el, index) => { const key = await el.getAttribute('textContent'); const value = await pre.get(index).getText(); acc[key] = jsonOutput ? JSON.parse(value) : value; @@ -85,7 +105,7 @@ export const checkSecret = async(ns: string, name: string, keyValuesToCheck: Obj expect(renderedKeyValues).toEqual(keyValuesToCheck); }; -export const editSecret = async(ns: string, name: string, updateForm: Function) => { +export const editSecret = async (ns: string, name: string, updateForm: Function) => { await crudView.clickDetailsPageAction('Edit Secret'); await browser.wait(until.urlContains(`/k8s/ns/${ns}/secrets/${name}/edit`)); await browser.wait(until.and(crudView.untilNoLoadersPresent, until.presenceOf(secretNameInput))); @@ -94,7 +114,7 @@ export const editSecret = async(ns: string, name: string, updateForm: Function) expect(crudView.errorMessage.isPresent()).toBe(false); }; -export const addSecretToWorkloadAsEnv = async(workloadname: string, EnvPrefix: string) => { +export const addSecretToWorkloadAsEnv = async (workloadname: string, EnvPrefix: string) => { await clickAddSecretToWorkload(); await browser.wait(until.presenceOf(selectWorkloadBtn)); await selectWorkloadBtn.click(); @@ -105,7 +125,7 @@ export const addSecretToWorkloadAsEnv = async(workloadname: string, EnvPrefix: s await $('#confirm-action').click(); }; -export const addSecretToWorkloadAsVol = async(workloadname: string, MntPath: string) => { +export const addSecretToWorkloadAsVol = async (workloadname: string, MntPath: string) => { await clickAddSecretToWorkload(); await browser.wait(until.presenceOf(selectWorkloadBtn)); await selectWorkloadBtn.click(); @@ -120,7 +140,13 @@ export const getResourceJSON = (name: string, namespace: string, kind: string) = return execSync(`kubectl get -o json -n ${namespace} ${kind} ${name}`).toString(); }; -export const isValueInJSONPath = (propertyPath: string, value: string, name: string, namespace: string, kind: string): boolean => { +export const isValueInJSONPath = ( + propertyPath: string, + value: string, + name: string, + namespace: string, + kind: string, +): boolean => { const resource = JSON.parse(getResourceJSON(name, namespace, kind)); const result = _.get(resource, propertyPath, undefined); return result === value; diff --git a/frontend/integration-tests/views/service-catalog.view.ts b/frontend/integration-tests/views/service-catalog.view.ts index a0ff2b99714..98ece379c63 100644 --- a/frontend/integration-tests/views/service-catalog.view.ts +++ b/frontend/integration-tests/views/service-catalog.view.ts @@ -1,9 +1,16 @@ -import {browser, $, ExpectedConditions as until, by, element} from 'protractor'; +import { browser, $, ExpectedConditions as until, by, element } from 'protractor'; export const linkForCSC = (name: string) => element(by.linkText(name)); -export const cscLinksPresent = () => browser.wait(until.presenceOf($('.co-cluster-service-class-link')), 10000); +export const cscLinksPresent = () => + browser.wait(until.presenceOf($('.co-cluster-service-class-link')), 10000); export const createInstanceButton = $('.pf-m-primary.co-action-buttons__btn'); -export const createInstanceFormIsLoaded = () => browser.wait(until.presenceOf($('.co-create-service-instance__namespace')), 10000).then(() => browser.sleep(500)); -export const createBindingFormIsLoaded = () => browser.wait(until.presenceOf($('.co-create-service-binding__name')), 10000).then(() => browser.sleep(500)); +export const createInstanceFormIsLoaded = () => + browser + .wait(until.presenceOf($('.co-create-service-instance__namespace')), 10000) + .then(() => browser.sleep(500)); +export const createBindingFormIsLoaded = () => + browser + .wait(until.presenceOf($('.co-create-service-binding__name')), 10000) + .then(() => browser.sleep(500)); export const createButton = $('.co-m-btn-bar').element(by.buttonText('Create')); diff --git a/frontend/integration-tests/views/sidenav.view.ts b/frontend/integration-tests/views/sidenav.view.ts index b14b7a19330..ca911611678 100644 --- a/frontend/integration-tests/views/sidenav.view.ts +++ b/frontend/integration-tests/views/sidenav.view.ts @@ -1,10 +1,19 @@ import { $$, by, browser, element, ExpectedConditions as until } from 'protractor'; -export const navSectionFor = (name: string) => element(by.cssContainingText('.pf-c-nav > .pf-c-nav__list > .pf-c-nav__item', name)); +export const navSectionFor = (name: string) => + element(by.cssContainingText('.pf-c-nav > .pf-c-nav__list > .pf-c-nav__item', name)); -export const clickNavLink = (path: [string, string]) => browser.wait(until.visibilityOf(navSectionFor(path[0]))) - .then(() => navSectionFor(path[0]).click()) - .then(() => browser.wait(until.visibilityOf(navSectionFor(path[0]).element(by.linkText(path[1]))))) - .then(() => navSectionFor(path[0]).element(by.linkText(path[1])).click()); +export const clickNavLink = (path: [string, string]) => + browser + .wait(until.visibilityOf(navSectionFor(path[0]))) + .then(() => navSectionFor(path[0]).click()) + .then(() => + browser.wait(until.visibilityOf(navSectionFor(path[0]).element(by.linkText(path[1])))), + ) + .then(() => + navSectionFor(path[0]) + .element(by.linkText(path[1])) + .click(), + ); export const activeLink = $$('.pf-c-nav__link.pf-m-current'); diff --git a/frontend/integration-tests/views/storage.view.ts b/frontend/integration-tests/views/storage.view.ts index 982d3c76a0c..eb104d87d2c 100644 --- a/frontend/integration-tests/views/storage.view.ts +++ b/frontend/integration-tests/views/storage.view.ts @@ -6,7 +6,11 @@ export const inputPVCName = $('#pvc-name'); export const inputPVCSize = $('[name=requestSizeValue]'); export const inputMountPath = $('#mount-path'); -export const addNewStorageToWorkload = async function(pvcName: string, pvcSize: string, mountPath: string) { +export const addNewStorageToWorkload = async function( + pvcName: string, + pvcSize: string, + mountPath: string, +) { await crudView.clickDetailsPageAction('Add Storage'); await browser.wait(until.presenceOf(createNewClaim)); await createNewClaim.click(); diff --git a/frontend/integration-tests/views/yaml.view.ts b/frontend/integration-tests/views/yaml.view.ts index b752444333f..a0a1a25bc49 100644 --- a/frontend/integration-tests/views/yaml.view.ts +++ b/frontend/integration-tests/views/yaml.view.ts @@ -3,12 +3,16 @@ import { waitForNone } from '../protractor.conf'; export const saveButton = $('.yaml-editor__buttons').$('#save-changes'); export const cancelButton = $('.yaml-editor__buttons').element(by.buttonText('Cancel')); -export const isLoaded = () => browser.wait(until.and(waitForNone($$('.co-m-loader')), until.visibilityOf(saveButton))).then(() => browser.sleep(1000)); +export const isLoaded = () => + browser + .wait(until.and(waitForNone($$('.co-m-loader')), until.visibilityOf(saveButton))) + .then(() => browser.sleep(1000)); const getValue = () => (window as any).monaco.editor.getModels()[0].getValue(); -export const getEditorContent = async(): Promise => await browser.executeScript(getValue); +export const getEditorContent = async (): Promise => + await browser.executeScript(getValue); const setValue = (text) => (window as any).monaco.editor.getModels()[0].setValue(text); -export const setEditorContent = async(text: string) => { +export const setEditorContent = async (text: string) => { await browser.executeScript(setValue, text); }; diff --git a/frontend/plugin-stats.js b/frontend/plugin-stats.js index dd7a705dc68..e088ddca702 100644 --- a/frontend/plugin-stats.js +++ b/frontend/plugin-stats.js @@ -6,14 +6,12 @@ require('ts-node').register({ ...require('./tsconfig.json').compilerOptions, module: 'commonjs', }, - ignore: [ - /node_modules\/(?!lodash-es|@console|@spice-project)/, - ], + ignore: [/node_modules\/(?!lodash-es|@console|@spice-project)/], }); // When an extension is unknown to Node.js, ts-node handles the file as ".js". // https://github.com/TypeStrong/ts-node/issues/175#issuecomment-455429261 -['.css', '.scss'].forEach(ext => { +['.css', '.scss'].forEach((ext) => { require.extensions[ext] = () => undefined; }); diff --git a/frontend/public/actions/dashboards.ts b/frontend/public/actions/dashboards.ts index 9f585b58900..caf96d672dd 100644 --- a/frontend/public/actions/dashboards.ts +++ b/frontend/public/actions/dashboards.ts @@ -19,13 +19,18 @@ const REFRESH_TIMEOUT = 5000; export const ALERTS_KEY = 'alerts'; -export const stopWatch = (type: RESULTS_TYPE, key: string) => action(ActionType.StopWatch, { type, key }); -export const setData = (type: RESULTS_TYPE, key: string, data) => action(ActionType.SetData, { type, key, data }); -export const activateWatch = (type: RESULTS_TYPE, key: string) => action(ActionType.ActivateWatch, { type, key }); -export const updateWatchTimeout = (type: RESULTS_TYPE, key: string, timeout: NodeJS.Timer) => action(ActionType.UpdateWatchTimeout, { type, key, timeout}); -export const updateWatchInFlight = (type: RESULTS_TYPE, key: string, inFlight: boolean) => action(ActionType.UpdateWatchInFlight, { type, key, inFlight }); -export const setError = (type: RESULTS_TYPE, key: string, error) => action(ActionType.SetError, { type, key, error }); - +export const stopWatch = (type: RESULTS_TYPE, key: string) => + action(ActionType.StopWatch, { type, key }); +export const setData = (type: RESULTS_TYPE, key: string, data) => + action(ActionType.SetData, { type, key, data }); +export const activateWatch = (type: RESULTS_TYPE, key: string) => + action(ActionType.ActivateWatch, { type, key }); +export const updateWatchTimeout = (type: RESULTS_TYPE, key: string, timeout: NodeJS.Timer) => + action(ActionType.UpdateWatchTimeout, { type, key, timeout }); +export const updateWatchInFlight = (type: RESULTS_TYPE, key: string, inFlight: boolean) => + action(ActionType.UpdateWatchInFlight, { type, key, inFlight }); +export const setError = (type: RESULTS_TYPE, key: string, error) => + action(ActionType.SetError, { type, key, error }); const dashboardsActions = { stopWatch, @@ -36,7 +41,7 @@ const dashboardsActions = { setError, }; -const fetchPeriodically: FetchPeriodically = async(dispatch, type, key, url, getState, fetch) => { +const fetchPeriodically: FetchPeriodically = async (dispatch, type, key, url, getState, fetch) => { if (!isWatchActive(getState().dashboards, type, key)) { return; } @@ -48,18 +53,23 @@ const fetchPeriodically: FetchPeriodically = async(dispatch, type, key, url, get dispatch(setError(type, key, error)); } finally { dispatch(updateWatchInFlight(type, key, false)); - const timeout = setTimeout(() => fetchPeriodically(dispatch, type, key, url, getState, fetch), REFRESH_TIMEOUT); + const timeout = setTimeout( + () => fetchPeriodically(dispatch, type, key, url, getState, fetch), + REFRESH_TIMEOUT, + ); dispatch(updateWatchTimeout(type, key, timeout)); } }; -export const watchPrometheusQuery: WatchPrometheusQueryAction = query => (dispatch, getState) => { +export const watchPrometheusQuery: WatchPrometheusQueryAction = (query) => (dispatch, getState) => { const isActive = isWatchActive(getState().dashboards, RESULTS_TYPE.PROMETHEUS, query); dispatch(activateWatch(RESULTS_TYPE.PROMETHEUS, query)); if (!isActive) { const prometheusBaseURL = window.SERVER_FLAGS.prometheusBaseURL; if (!prometheusBaseURL) { - dispatch(setError(RESULTS_TYPE.PROMETHEUS, query, new Error('Prometheus URL is not available'))); + dispatch( + setError(RESULTS_TYPE.PROMETHEUS, query, new Error('Prometheus URL is not available')), + ); } else { const url = `${prometheusBaseURL}/api/v1/query?query=${encodeURIComponent(query)}`; fetchPeriodically(dispatch, RESULTS_TYPE.PROMETHEUS, query, url, getState, coFetchJSON); @@ -82,15 +92,25 @@ export const watchAlerts: WatchAlertsAction = () => (dispatch, getState) => { if (!isActive) { const alertManagerBaseURL = window.SERVER_FLAGS.alertManagerBaseURL; if (!alertManagerBaseURL) { - dispatch(setError(RESULTS_TYPE.ALERTS, ALERTS_KEY, new Error('AlertManager URL is not available'))); + dispatch( + setError(RESULTS_TYPE.ALERTS, ALERTS_KEY, new Error('AlertManager URL is not available')), + ); } else { const alertManagerURL = `${alertManagerBaseURL}/api/v2/alerts?silenced=false&inhibited=false`; - fetchPeriodically(dispatch, RESULTS_TYPE.ALERTS, ALERTS_KEY, alertManagerURL, getState, coFetchJSON); + fetchPeriodically( + dispatch, + RESULTS_TYPE.ALERTS, + ALERTS_KEY, + alertManagerURL, + getState, + coFetchJSON, + ); } } }; -export const stopWatchPrometheusQuery = (query: string) => stopWatch(RESULTS_TYPE.PROMETHEUS, query); +export const stopWatchPrometheusQuery = (query: string) => + stopWatch(RESULTS_TYPE.PROMETHEUS, query); export const stopWatchURL = (url: string) => stopWatch(RESULTS_TYPE.URL, url); export const stopWatchAlerts = () => stopWatch(RESULTS_TYPE.ALERTS, ALERTS_KEY); @@ -105,7 +125,13 @@ export type StopWatchAlertsAction = () => void; export type Fetch = (url: string) => Promise; -type FetchPeriodically = - (dispatch: Dispatch, type: RESULTS_TYPE, key: string, url: string, getState: () => RootState, fetch: Fetch) => void; +type FetchPeriodically = ( + dispatch: Dispatch, + type: RESULTS_TYPE, + key: string, + url: string, + getState: () => RootState, + fetch: Fetch, +) => void; export type DashboardsAction = Action; diff --git a/frontend/public/actions/features.ts b/frontend/public/actions/features.ts index 7f074a9697a..03dfc393266 100644 --- a/frontend/public/actions/features.ts +++ b/frontend/public/actions/features.ts @@ -15,14 +15,13 @@ export enum ActionType { SetFlag = 'setFlag', } -export const defaults = _.mapValues(FLAGS, flag => flag === FLAGS.AUTH_ENABLED - ? !window.SERVER_FLAGS.authDisabled - : undefined +export const defaults = _.mapValues(FLAGS, (flag) => + flag === FLAGS.AUTH_ENABLED ? !window.SERVER_FLAGS.authDisabled : undefined, ); -export const setFlag = (flag: FLAGS, value: boolean) => action(ActionType.SetFlag, {flag, value}); +export const setFlag = (flag: FLAGS, value: boolean) => action(ActionType.SetFlag, { flag, value }); -const featureActions = {setFlag}; +const featureActions = { setFlag }; export type FeatureAction = Action; @@ -41,45 +40,48 @@ const handleError = (res, flag, dispatch, cb) => { }; const openshiftPath = `${k8sBasePath}/apis/apps.openshift.io/v1`; -const detectOpenShift = dispatch => coFetchJSON(openshiftPath) - .then( - res => dispatch(setFlag(FLAGS.OPENSHIFT, _.size(res.resources) > 0)), - err => _.get(err, 'response.status') === 404 - ? dispatch(setFlag(FLAGS.OPENSHIFT, false)) - : handleError(err, FLAGS.OPENSHIFT, dispatch, detectOpenShift) +const detectOpenShift = (dispatch) => + coFetchJSON(openshiftPath).then( + (res) => dispatch(setFlag(FLAGS.OPENSHIFT, _.size(res.resources) > 0)), + (err) => + _.get(err, 'response.status') === 404 + ? dispatch(setFlag(FLAGS.OPENSHIFT, false)) + : handleError(err, FLAGS.OPENSHIFT, dispatch, detectOpenShift), ); -const detectBaremetalPlatform = dispatch => - k8sGet(InfrastructureModel, 'cluster') - .then( - (infra: K8sResourceKind) => dispatch(setFlag(FLAGS.BAREMETAL, getInfrastructurePlatform(infra) === 'BareMetal')), - err => { - _.get(err, 'response.status') === 404 - ? dispatch(setFlag(FLAGS.BAREMETAL, false)) - : handleError(err, FLAGS.BAREMETAL, dispatch, detectBaremetalPlatform); - }); +const detectBaremetalPlatform = (dispatch) => + k8sGet(InfrastructureModel, 'cluster').then( + (infra: K8sResourceKind) => + dispatch(setFlag(FLAGS.BAREMETAL, getInfrastructurePlatform(infra) === 'BareMetal')), + (err) => { + _.get(err, 'response.status') === 404 + ? dispatch(setFlag(FLAGS.BAREMETAL, false)) + : handleError(err, FLAGS.BAREMETAL, dispatch, detectBaremetalPlatform); + }, + ); const clusterVersionPath = `${k8sBasePath}/apis/config.openshift.io/v1/clusterversions/version`; -const detectClusterVersion = dispatch => coFetchJSON(clusterVersionPath) - .then( +const detectClusterVersion = (dispatch) => + coFetchJSON(clusterVersionPath).then( (clusterVersion: ClusterVersionKind) => { const hasClusterVersion = !_.isEmpty(clusterVersion); dispatch(setFlag(FLAGS.CLUSTER_VERSION, hasClusterVersion)); dispatch(setClusterID(clusterVersion.spec.clusterID)); }, - err => { + (err) => { if (_.includes([403, 404], _.get(err, 'response.status'))) { dispatch(setFlag(FLAGS.CLUSTER_VERSION, false)); } else { handleError(err, FLAGS.OPENSHIFT, dispatch, detectOpenShift); } - }); + }, + ); const projectRequestPath = `${k8sBasePath}/apis/project.openshift.io/v1/projectrequests`; -const detectCanCreateProject = dispatch => coFetchJSON(projectRequestPath) - .then( - res => dispatch(setFlag(FLAGS.CAN_CREATE_PROJECT, res.status === 'Success')), - err => { +const detectCanCreateProject = (dispatch) => + coFetchJSON(projectRequestPath).then( + (res) => dispatch(setFlag(FLAGS.CAN_CREATE_PROJECT, res.status === 'Success')), + (err) => { const status = _.get(err, 'response.status'); if (status === 403) { dispatch(setFlag(FLAGS.CAN_CREATE_PROJECT, false)); @@ -87,14 +89,14 @@ const detectCanCreateProject = dispatch => coFetchJSON(projectRequestPath) } else if (!_.includes([400, 404, 500], status)) { retryFlagDetection(dispatch, detectCanCreateProject); } - } + }, ); const monitoringConfigMapPath = `${k8sBasePath}/api/v1/namespaces/openshift-monitoring/configmaps/sharing-config`; -const detectMonitoringURLs = dispatch => coFetchJSON(monitoringConfigMapPath) - .then( - res => { - const {alertmanagerURL, grafanaURL, prometheusURL} = res.data; +const detectMonitoringURLs = (dispatch) => + coFetchJSON(monitoringConfigMapPath).then( + (res) => { + const { alertmanagerURL, grafanaURL, prometheusURL } = res.data; if (!_.isEmpty(alertmanagerURL)) { dispatch(setMonitoringURL(MonitoringRoutes.AlertManager, alertmanagerURL)); } @@ -105,7 +107,7 @@ const detectMonitoringURLs = dispatch => coFetchJSON(monitoringConfigMapPath) dispatch(setMonitoringURL(MonitoringRoutes.Prometheus, prometheusURL)); } }, - err => { + (err) => { if (!_.includes([401, 403, 404, 500], _.get(err, 'response.status'))) { setTimeout(() => detectMonitoringURLs(dispatch), 15000); } @@ -113,39 +115,39 @@ const detectMonitoringURLs = dispatch => coFetchJSON(monitoringConfigMapPath) ); const loggingConfigMapPath = `${k8sBasePath}/api/v1/namespaces/openshift-logging/configmaps/sharing-config`; -const detectLoggingURL = dispatch => coFetchJSON(loggingConfigMapPath) - .then( - res => { - const {kibanaAppURL} = res.data; +const detectLoggingURL = (dispatch) => + coFetchJSON(loggingConfigMapPath).then( + (res) => { + const { kibanaAppURL } = res.data; if (!_.isEmpty(kibanaAppURL)) { dispatch(setMonitoringURL(MonitoringRoutes.Kibana, kibanaAppURL)); } }, - err => { + (err) => { if (!_.includes([401, 403, 404, 500], _.get(err, 'response.status'))) { setTimeout(() => detectLoggingURL(dispatch), 15000); } }, ); -const detectUser = dispatch => coFetchJSON('api/kubernetes/apis/user.openshift.io/v1/users/~') - .then( +const detectUser = (dispatch) => + coFetchJSON('api/kubernetes/apis/user.openshift.io/v1/users/~').then( (user) => { dispatch(setUser(user)); }, - err => { + (err) => { if (!_.includes([401, 403, 404, 500], _.get(err, 'response.status'))) { setTimeout(() => detectUser(dispatch), 15000); } }, ); -const detectConsoleLinks = dispatch => coFetchJSON('api/kubernetes/apis/console.openshift.io/v1/consolelinks') - .then( +const detectConsoleLinks = (dispatch) => + coFetchJSON('api/kubernetes/apis/console.openshift.io/v1/consolelinks').then( (consoleLinks) => { dispatch(setConsoleLinks(_.get(consoleLinks, 'items'))); }, - err => { + (err) => { if (!_.includes([401, 403, 404, 500], _.get(err, 'response.status'))) { setTimeout(() => detectConsoleLinks(dispatch), 15000); } @@ -168,62 +170,87 @@ const detectShowOpenShiftStartGuide = (dispatch, canListNS: boolean = false) => return; } - coFetchJSON(projectListPath) - .then( - res => dispatch(setFlag(FLAGS.SHOW_OPENSHIFT_START_GUIDE, _.isEmpty(res.items))), - err => _.get(err, 'response.status') === 404 + coFetchJSON(projectListPath).then( + (res) => dispatch(setFlag(FLAGS.SHOW_OPENSHIFT_START_GUIDE, _.isEmpty(res.items))), + (err) => + _.get(err, 'response.status') === 404 ? dispatch(setFlag(FLAGS.SHOW_OPENSHIFT_START_GUIDE, false)) - : handleError(err, FLAGS.SHOW_OPENSHIFT_START_GUIDE, dispatch, detectShowOpenShiftStartGuide) - ); + : handleError( + err, + FLAGS.SHOW_OPENSHIFT_START_GUIDE, + dispatch, + detectShowOpenShiftStartGuide, + ), + ); }; // Check the user's access to some resources. -const ssarChecks = [{ - flag: FLAGS.CAN_GET_NS, - resourceAttributes: { resource: 'namespaces', verb: 'get' }, -}, { - flag: FLAGS.CAN_LIST_NS, - resourceAttributes: { resource: 'namespaces', verb: 'list' }, - after: detectShowOpenShiftStartGuide, -}, { - flag: FLAGS.CAN_LIST_NODE, - resourceAttributes: { resource: 'nodes', verb: 'list' }, -}, { - flag: FLAGS.CAN_LIST_PV, - resourceAttributes: { resource: 'persistentvolumes', verb: 'list' }, -}, { - flag: FLAGS.CAN_LIST_CRD, - resourceAttributes:{ group: 'apiextensions.k8s.io', resource: 'customresourcedefinitions', verb: 'list' }, -}, { - flag: FLAGS.CAN_LIST_CHARGEBACK_REPORTS, - resourceAttributes:{ group: 'metering.openshift.io', resource: 'reports', namespace: 'openshift-metering', verb: 'list' }, -}]; - -const ssarCheckActions = ssarChecks.map(({flag, resourceAttributes, after}) => { +const ssarChecks = [ + { + flag: FLAGS.CAN_GET_NS, + resourceAttributes: { resource: 'namespaces', verb: 'get' }, + }, + { + flag: FLAGS.CAN_LIST_NS, + resourceAttributes: { resource: 'namespaces', verb: 'list' }, + after: detectShowOpenShiftStartGuide, + }, + { + flag: FLAGS.CAN_LIST_NODE, + resourceAttributes: { resource: 'nodes', verb: 'list' }, + }, + { + flag: FLAGS.CAN_LIST_PV, + resourceAttributes: { resource: 'persistentvolumes', verb: 'list' }, + }, + { + flag: FLAGS.CAN_LIST_CRD, + resourceAttributes: { + group: 'apiextensions.k8s.io', + resource: 'customresourcedefinitions', + verb: 'list', + }, + }, + { + flag: FLAGS.CAN_LIST_CHARGEBACK_REPORTS, + resourceAttributes: { + group: 'metering.openshift.io', + resource: 'reports', + namespace: 'openshift-metering', + verb: 'list', + }, + }, +]; + +const ssarCheckActions = ssarChecks.map(({ flag, resourceAttributes, after }) => { const req = { spec: { resourceAttributes }, }; const fn = (dispatch) => { - return k8sCreate(SelfSubjectAccessReviewModel, req) .then((res) => { - const allowed: boolean = res.status.allowed; - dispatch(setFlag(flag, allowed)); - if (after) { - after(dispatch, allowed); - } - }, (err) => handleError(err, flag, dispatch, fn)); + return k8sCreate(SelfSubjectAccessReviewModel, req).then( + (res) => { + const allowed: boolean = res.status.allowed; + dispatch(setFlag(flag, allowed)); + if (after) { + after(dispatch, allowed); + } + }, + (err) => handleError(err, flag, dispatch, fn), + ); }; return fn; }); -export const detectFeatures = () => (dispatch: Dispatch) => [ - detectOpenShift, - // TODO(vojtech): move this flag definition to metal3-plugin via ActionFeatureFlag extension - detectBaremetalPlatform, - detectCanCreateProject, - detectMonitoringURLs, - detectClusterVersion, - detectUser, - detectLoggingURL, - detectConsoleLinks, - ...ssarCheckActions, -].forEach(detect => detect(dispatch)); +export const detectFeatures = () => (dispatch: Dispatch) => + [ + detectOpenShift, + // TODO(vojtech): move this flag definition to metal3-plugin via ActionFeatureFlag extension + detectBaremetalPlatform, + detectCanCreateProject, + detectMonitoringURLs, + detectClusterVersion, + detectUser, + detectLoggingURL, + detectConsoleLinks, + ...ssarCheckActions, + ].forEach((detect) => detect(dispatch)); diff --git a/frontend/public/actions/k8s.ts b/frontend/public/actions/k8s.ts index 11dd2b0a7af..df886b7fef6 100644 --- a/frontend/public/actions/k8s.ts +++ b/frontend/public/actions/k8s.ts @@ -2,7 +2,11 @@ import * as _ from 'lodash-es'; import { Dispatch } from 'react-redux'; import { ActionType as Action, action } from 'typesafe-actions'; -import { cacheResources, getResources as getResources_, DiscoveryResources } from '../module/k8s/get-resources'; +import { + cacheResources, + getResources as getResources_, + DiscoveryResources, +} from '../module/k8s/get-resources'; import { k8sList, k8sWatch, k8sGet } from '../module/k8s/resource'; import { makeReduxID } from '../components/utils/k8s-watcher'; import { APIServiceModel } from '../models'; @@ -27,7 +31,7 @@ export enum ActionType { UpdateListFromWS = 'updateListFromWS', } -const WS = {} as {[id: string]: WebSocket & any}; +const WS = {} as { [id: string]: WebSocket & any }; const POLLs = {}; const REF_COUNTS = {}; @@ -35,34 +39,47 @@ const nop = () => {}; const paginationLimit = 250; const apiGroups = 'apiGroups'; -type K8sEvent = {type: 'ADDED' | 'DELETED' | 'MODIFIED', object: K8sResourceKind}; +type K8sEvent = { type: 'ADDED' | 'DELETED' | 'MODIFIED'; object: K8sResourceKind }; -export const updateListFromWS = (id: string, k8sObjects: K8sEvent[]) => action(ActionType.UpdateListFromWS, {id, k8sObjects}); -export const bulkAddToList = (id: string, k8sObjects: K8sResourceKind[]) => action(ActionType.BulkAddToList, {id, k8sObjects}); -export const loaded = (id: string, k8sObjects: K8sResourceKind | K8sResourceKind[]) => action(ActionType.Loaded, {id, k8sObjects}); -export const errored = (id: string, k8sObjects: any) => action(ActionType.Errored, {id, k8sObjects}); -export const modifyObject = (id: string, k8sObjects: K8sResourceKind) => action(ActionType.ModifyObject, {id, k8sObjects}); +export const updateListFromWS = (id: string, k8sObjects: K8sEvent[]) => + action(ActionType.UpdateListFromWS, { id, k8sObjects }); +export const bulkAddToList = (id: string, k8sObjects: K8sResourceKind[]) => + action(ActionType.BulkAddToList, { id, k8sObjects }); +export const loaded = (id: string, k8sObjects: K8sResourceKind | K8sResourceKind[]) => + action(ActionType.Loaded, { id, k8sObjects }); +export const errored = (id: string, k8sObjects: any) => + action(ActionType.Errored, { id, k8sObjects }); +export const modifyObject = (id: string, k8sObjects: K8sResourceKind) => + action(ActionType.ModifyObject, { id, k8sObjects }); export const getResourcesInFlight = () => action(ActionType.GetResourcesInFlight); -export const receivedResources = (resources: DiscoveryResources) => action(ActionType.ReceivedResources, {resources}); +export const receivedResources = (resources: DiscoveryResources) => + action(ActionType.ReceivedResources, { resources }); export const getResources = () => (dispatch: Dispatch) => { dispatch(getResourcesInFlight()); getResources_() - .then(resources => { + .then((resources) => { // Cache the resources whenever discovery completes to improve console load times. cacheResources(resources); dispatch(receivedResources(resources)); }) // eslint-disable-next-line no-console - .catch(err => console.error(err)); + .catch((err) => console.error(err)); }; -export const filterList = (id: string, name: string, value: string) => action(ActionType.FilterList, {id, name, value}); +export const filterList = (id: string, name: string, value: string) => + action(ActionType.FilterList, { id, name, value }); -export const startWatchK8sObject = (id: string) => action(ActionType.StartWatchK8sObject, {id}); +export const startWatchK8sObject = (id: string) => action(ActionType.StartWatchK8sObject, { id }); -export const watchK8sObject = (id: string, name: string, namespace: string, query: {[key: string]: string}, k8sType: K8sKind) => (dispatch: Dispatch, getState) => { +export const watchK8sObject = ( + id: string, + name: string, + namespace: string, + query: { [key: string]: string }, + k8sType: K8sKind, +) => (dispatch: Dispatch, getState) => { if (id in REF_COUNTS) { REF_COUNTS[id] += 1; return nop; @@ -76,11 +93,10 @@ export const watchK8sObject = (id: string, name: string, namespace: string, quer } const poller = () => { - k8sGet(k8sType, name, namespace) - .then( - o => dispatch(modifyObject(id, o)), - e => dispatch(errored(id, e)) - ); + k8sGet(k8sType, name, namespace).then( + (o) => dispatch(modifyObject(id, o)), + (e) => dispatch(errored(id, e)), + ); }; POLLs[id] = setInterval(poller, 30 * 1000); poller(); @@ -91,14 +107,14 @@ export const watchK8sObject = (id: string, name: string, namespace: string, quer return; } - const {subprotocols} = getState().UI.get('impersonate', {}); + const { subprotocols } = getState().UI.get('impersonate', {}); - WS[id] = k8sWatch(k8sType, {...query, subprotocols}).onbulkmessage(events => - events.forEach(e => dispatch(modifyObject(id, e.object))) + WS[id] = k8sWatch(k8sType, { ...query, subprotocols }).onbulkmessage((events) => + events.forEach((e) => dispatch(modifyObject(id, e.object))), ); }; -export const stopWatchK8s = (id: string) => action(ActionType.StopWatchK8s, {id}); +export const stopWatchK8s = (id: string) => action(ActionType.StopWatchK8s, { id }); export const stopK8sWatch = (id: string) => (dispatch: Dispatch) => { REF_COUNTS[id] -= 1; @@ -118,9 +134,15 @@ export const stopK8sWatch = (id: string) => (dispatch: Dispatch) => { dispatch(stopWatchK8s(id)); }; -export const startWatchK8sList = (id: string, query: {[key: string]: string}) => action(ActionType.StartWatchK8sList, {id, query}); +export const startWatchK8sList = (id: string, query: { [key: string]: string }) => + action(ActionType.StartWatchK8sList, { id, query }); -export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind: K8sKind, extraAction?) => (dispatch, getState) => { +export const watchK8sList = ( + id: string, + query: { [key: string]: string }, + k8skind: K8sKind, + extraAction?, +) => (dispatch, getState) => { // Only one watch per unique list ID if (id in REF_COUNTS) { REF_COUNTS[id] += 1; @@ -130,21 +152,25 @@ export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind dispatch(startWatchK8sList(id, query)); REF_COUNTS[id] = 1; - const incrementallyLoad = async(continueToken = ''): Promise => { + const incrementallyLoad = async (continueToken = ''): Promise => { // the list may not still be around... if (!REF_COUNTS[id]) { // let .then handle the cleanup return; } - const response = await k8sList(k8skind, {...query, limit: paginationLimit, ...(continueToken ? {continue: continueToken} : {})}, true); + const response = await k8sList( + k8skind, + { ...query, limit: paginationLimit, ...(continueToken ? { continue: continueToken } : {}) }, + true, + ); if (!REF_COUNTS[id]) { return; } if (!continueToken) { - [loaded, extraAction].forEach(f => f && dispatch(f(id, response.items))); + [loaded, extraAction].forEach((f) => f && dispatch(f(id, response.items))); } else { dispatch(bulkAddToList(id, response.items)); } @@ -161,7 +187,7 @@ export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind * 1. the WS closes abnormally * 2. the WS can not establish a connection within $TIMEOUT */ - const pollAndWatch = async() => { + const pollAndWatch = async () => { delete POLLs[id]; try { @@ -177,7 +203,9 @@ export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind if (WS[id]) { // eslint-disable-next-line no-console - console.warn(`Attempted to create multiple websockets for ${id}. This should never happen.`); + console.warn( + `Attempted to create multiple websockets for ${id}. This should never happen.`, + ); return; } @@ -187,8 +215,12 @@ export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind return; } - const {subprotocols} = getState().UI.get('impersonate', {}); - WS[id] = k8sWatch(k8skind, {...query, resourceVersion}, {subprotocols, timeout: 60 * 1000}); + const { subprotocols } = getState().UI.get('impersonate', {}); + WS[id] = k8sWatch( + k8skind, + { ...query, resourceVersion }, + { subprotocols, timeout: 60 * 1000 }, + ); } catch (e) { if (!REF_COUNTS[id]) { // eslint-disable-next-line no-console @@ -207,7 +239,7 @@ export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind } WS[id] - .onclose(event => { + .onclose((event) => { // Close Frame Status Codes: https://tools.ietf.org/html/rfc6455#section-7.4.1 if (event.code !== 1006) { return; @@ -218,7 +250,7 @@ export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind const timedOut = true; ws && ws.destroy(timedOut); }) - .ondestroy(timedOut => { + .ondestroy((timedOut) => { if (!timedOut) { return; } @@ -235,28 +267,33 @@ export const watchK8sList = (id: string, query: {[key: string]: string}, k8skind POLLs[id] = setTimeout(pollAndWatch, 15 * 1000); }) - .onbulkmessage(events => [updateListFromWS, extraAction].forEach(f => f && dispatch(f(id, events)))); + .onbulkmessage((events) => + [updateListFromWS, extraAction].forEach((f) => f && dispatch(f(id, events))), + ); }; pollAndWatch(); }; -export const setAPIGroups = (value: number) => action(ActionType.SetAPIGroups, {value}); +export const setAPIGroups = (value: number) => action(ActionType.SetAPIGroups, { value }); export const watchAPIServices = () => (dispatch, getState) => { if (getState().k8s.has('apiservices') || POLLs[apiGroups]) { return; } - dispatch({type: ActionType.GetResourcesInFlight}); + dispatch({ type: ActionType.GetResourcesInFlight }); k8sList(APIServiceModel, {}) - .then(() => dispatch(watchK8sList(makeReduxID(APIServiceModel, {}), {}, APIServiceModel, getResources))) + .then(() => + dispatch(watchK8sList(makeReduxID(APIServiceModel, {}), {}, APIServiceModel, getResources)), + ) .catch(() => { - const poller = () => coFetchJSON('api/kubernetes/apis').then(d => { - if (d.groups.length !== getState().k8s.getIn(['RESOURCES', apiGroups], 0)) { - dispatch(getResources()); - } - dispatch(setAPIGroups(d.groups.length)); - }); + const poller = () => + coFetchJSON('api/kubernetes/apis').then((d) => { + if (d.groups.length !== getState().k8s.getIn(['RESOURCES', apiGroups], 0)) { + dispatch(getResources()); + } + dispatch(setAPIGroups(d.groups.length)); + }); POLLs[apiGroups] = setInterval(poller, 30 * 1000); poller(); diff --git a/frontend/public/actions/monitoring.ts b/frontend/public/actions/monitoring.ts index 223cbcac33f..43c6f0395df 100644 --- a/frontend/public/actions/monitoring.ts +++ b/frontend/public/actions/monitoring.ts @@ -4,8 +4,9 @@ export enum ActionType { SetMonitoringURL = 'setMonitoringURL', } -export const setMonitoringURL = (name: string, url: string) => action(ActionType.SetMonitoringURL, {name, url}); +export const setMonitoringURL = (name: string, url: string) => + action(ActionType.SetMonitoringURL, { name, url }); -const monitoringActions = {setMonitoringURL}; +const monitoringActions = { setMonitoringURL }; export type MonitoringAction = Action; diff --git a/frontend/public/actions/ui.ts b/frontend/public/actions/ui.ts index ad713783668..2bdcf2efacd 100644 --- a/frontend/public/actions/ui.ts +++ b/frontend/public/actions/ui.ts @@ -66,7 +66,10 @@ allModels().forEach((v, k) => { export const getActiveNamespace = (): string => store.getState().UI.get('activeNamespace'); -export const formatNamespacedRouteForResource = (resource, activeNamespace = getActiveNamespace()) => { +export const formatNamespacedRouteForResource = ( + resource, + activeNamespace = getActiveNamespace(), +) => { return activeNamespace === ALL_NAMESPACES_KEY ? `/k8s/all-namespaces/${resource}` : `/k8s/ns/${activeNamespace}/${resource}`; @@ -75,7 +78,7 @@ export const formatNamespacedRouteForResource = (resource, activeNamespace = get export const formatNamespaceRoute = (activeNamespace, originalPath, location?) => { let path = originalPath.substr(window.SERVER_FLAGS.basePath.length); - let parts = path.split('/').filter(p => p); + let parts = path.split('/').filter((p) => p); const prefix = parts.shift(); let previousNS; @@ -91,12 +94,17 @@ export const formatNamespaceRoute = (activeNamespace, originalPath, location?) = return originalPath; } - if ((previousNS !== activeNamespace && (parts[1] !== 'new' || activeNamespace !== ALL_NAMESPACES_KEY)) || activeNamespace === ALL_NAMESPACES_KEY && parts[1] === 'new') { + if ( + (previousNS !== activeNamespace && + (parts[1] !== 'new' || activeNamespace !== ALL_NAMESPACES_KEY)) || + (activeNamespace === ALL_NAMESPACES_KEY && parts[1] === 'new') + ) { // a given resource will not exist when we switch namespaces, so pop off the tail end parts = parts.slice(0, 1); } - const namespacePrefix = activeNamespace === ALL_NAMESPACES_KEY ? 'all-namespaces' : `ns/${activeNamespace}`; + const namespacePrefix = + activeNamespace === ALL_NAMESPACES_KEY ? 'all-namespaces' : `ns/${activeNamespace}`; path = `/${prefix}/${namespacePrefix}`; if (parts.length) { @@ -110,10 +118,11 @@ export const formatNamespaceRoute = (activeNamespace, originalPath, location?) = return path; }; -export const setCurrentLocation = (location: string) => action(ActionType.SetCurrentLocation, {location}); +export const setCurrentLocation = (location: string) => + action(ActionType.SetCurrentLocation, { location }); export const setActiveApplication = (application: string) => { - return action(ActionType.SetActiveApplication, {application}); + return action(ActionType.SetActiveApplication, { application }); }; export const setActiveNamespace = (namespace: string = '') => { @@ -132,19 +141,20 @@ export const setActiveNamespace = (namespace: string = '') => { localStorage.setItem(LAST_NAMESPACE_NAME_LOCAL_STORAGE_KEY, namespace); } - return action(ActionType.SetActiveNamespace, {namespace}); + return action(ActionType.SetActiveNamespace, { namespace }); }; export const setActivePerspective = (perspective: string) => { // remember the most recently-viewed perspective, which is automatically // selected when returning to the console localStorage.setItem(LAST_PERSPECTIVE_LOCAL_STORAGE_KEY, perspective); - return action(ActionType.SetActivePerspective, {perspective}); + return action(ActionType.SetActivePerspective, { perspective }); }; -export const beginImpersonate = (kind: string, name: string, subprotocols: string[]) => action(ActionType.BeginImpersonate, {kind, name, subprotocols}); +export const beginImpersonate = (kind: string, name: string, subprotocols: string[]) => + action(ActionType.BeginImpersonate, { kind, name, subprotocols }); export const endImpersonate = () => action(ActionType.EndImpersonate); -export const startImpersonate = (kind: string, name: string) => async(dispatch, getState) => { +export const startImpersonate = (kind: string, name: string) => async (dispatch, getState) => { let textEncoder; try { textEncoder = new TextEncoder(); @@ -154,7 +164,7 @@ export const startImpersonate = (kind: string, name: string) => async(dispatch, } if (!textEncoder) { - textEncoder = await import('text-encoding').then(module => new module.TextEncoder('utf-8')); + textEncoder = await import('text-encoding').then((module) => new module.TextEncoder('utf-8')); } const imp = getState().UI.get('impersonate', {}); @@ -173,7 +183,7 @@ export const startImpersonate = (kind: string, name: string) => async(dispatch, encodedName = encodedName.replace(/=/g, '_').replace(/\//g, '-'); let subprotocols; - if (kind === 'User' ) { + if (kind === 'User') { subprotocols = [`Impersonate-User.${encodedName}`]; } if (kind === 'Group') { @@ -189,50 +199,80 @@ export const stopImpersonate = () => (dispatch) => { dispatch(detectFeatures()); history.push(window.SERVER_FLAGS.basePath); }; -export const sortList = (listId: string, field: string, func: string, sortAsNumber: boolean, orderBy: string, column: string) => { +export const sortList = ( + listId: string, + field: string, + func: string, + sortAsNumber: boolean, + orderBy: string, + column: string, +) => { const url = new URL(window.location.href); const sp = new URLSearchParams(window.location.search); sp.set('orderBy', orderBy); sp.set('sortBy', column); history.replace(`${url.pathname}?${sp.toString()}${url.hash}`); - return action(ActionType.SortList, {listId, field, func, sortAsNumber, orderBy}); + return action(ActionType.SortList, { listId, field, func, sortAsNumber, orderBy }); }; -export const setCreateProjectMessage = (message: string) => action(ActionType.SetCreateProjectMessage, {message}); -export const setClusterID = (clusterID: string) => action(ActionType.SetClusterID, {clusterID}); -export const setUser = (user: any) => action(ActionType.SetUser, {user}); -export const selectOverviewItem = (uid: string) => action(ActionType.SelectOverviewItem, {uid}); -export const selectOverviewDetailsTab = (tab: string) => action(ActionType.SelectOverviewDetailsTab, {tab}); -export const updateOverviewMetrics = (metrics: any) => action(ActionType.UpdateOverviewMetrics, {metrics}); -export const updateOverviewResources = (resources: OverviewItem[]) => action(ActionType.UpdateOverviewResources, {resources}); -export const updateTimestamps = (lastTick: number) => action(ActionType.UpdateTimestamps, {lastTick}); +export const setCreateProjectMessage = (message: string) => + action(ActionType.SetCreateProjectMessage, { message }); +export const setClusterID = (clusterID: string) => action(ActionType.SetClusterID, { clusterID }); +export const setUser = (user: any) => action(ActionType.SetUser, { user }); +export const selectOverviewItem = (uid: string) => action(ActionType.SelectOverviewItem, { uid }); +export const selectOverviewDetailsTab = (tab: string) => + action(ActionType.SelectOverviewDetailsTab, { tab }); +export const updateOverviewMetrics = (metrics: any) => + action(ActionType.UpdateOverviewMetrics, { metrics }); +export const updateOverviewResources = (resources: OverviewItem[]) => + action(ActionType.UpdateOverviewResources, { resources }); +export const updateTimestamps = (lastTick: number) => + action(ActionType.UpdateTimestamps, { lastTick }); export const dismissOverviewDetails = () => action(ActionType.DismissOverviewDetails); -export const updateOverviewSelectedGroup = (group: OverviewSpecialGroup | string) => action(ActionType.UpdateOverviewSelectedGroup, {group}); -export const updateOverviewLabels = (labels: string[]) => action(ActionType.UpdateOverviewLabels, {labels}); -export const updateOverviewFilterValue = (value: string) => action(ActionType.UpdateOverviewFilterValue, {value}); -export const monitoringLoading = (key: 'alerts' | 'silences') => action(ActionType.SetMonitoringData, {key, data: {loaded: false, loadError: null, data: null}}); -export const monitoringLoaded = (key: 'alerts' | 'silences', data: any) => action(ActionType.SetMonitoringData, {key, data: {loaded: true, loadError: null, data}}); -export const monitoringErrored = (key: 'alerts' | 'silences', loadError: any) => action(ActionType.SetMonitoringData, {key, data: {loaded: true, loadError, data: null}}); +export const updateOverviewSelectedGroup = (group: OverviewSpecialGroup | string) => + action(ActionType.UpdateOverviewSelectedGroup, { group }); +export const updateOverviewLabels = (labels: string[]) => + action(ActionType.UpdateOverviewLabels, { labels }); +export const updateOverviewFilterValue = (value: string) => + action(ActionType.UpdateOverviewFilterValue, { value }); +export const monitoringLoading = (key: 'alerts' | 'silences') => + action(ActionType.SetMonitoringData, { + key, + data: { loaded: false, loadError: null, data: null }, + }); +export const monitoringLoaded = (key: 'alerts' | 'silences', data: any) => + action(ActionType.SetMonitoringData, { key, data: { loaded: true, loadError: null, data } }); +export const monitoringErrored = (key: 'alerts' | 'silences', loadError: any) => + action(ActionType.SetMonitoringData, { key, data: { loaded: true, loadError, data: null } }); export const monitoringToggleGraphs = () => action(ActionType.ToggleMonitoringGraphs); export const queryBrowserAddQuery = () => action(ActionType.QueryBrowserAddQuery); export const queryBrowserDeleteAllQueries = () => action(ActionType.QueryBrowserDeleteAllQueries); -export const queryBrowserDeleteQuery = (index: number) => action(ActionType.QueryBrowserDeleteQuery, {index}); -export const queryBrowserInsertText = (index: number, newText: string, replaceFrom: number, replaceTo: number) => { - return action(ActionType.QueryBrowserInsertText, {index, newText, replaceFrom, replaceTo}); +export const queryBrowserDeleteQuery = (index: number) => + action(ActionType.QueryBrowserDeleteQuery, { index }); +export const queryBrowserInsertText = ( + index: number, + newText: string, + replaceFrom: number, + replaceTo: number, +) => { + return action(ActionType.QueryBrowserInsertText, { index, newText, replaceFrom, replaceTo }); }; -export const queryBrowserPatchQuery = (index: number, patch: {[key: string]: unknown}) => { - return action(ActionType.QueryBrowserPatchQuery, {index, patch}); +export const queryBrowserPatchQuery = (index: number, patch: { [key: string]: unknown }) => { + return action(ActionType.QueryBrowserPatchQuery, { index, patch }); }; export const queryBrowserRunQueries = () => action(ActionType.QueryBrowserRunQueries); export const queryBrowserSetAllExpanded = (isExpanded: boolean) => { - return action(ActionType.QueryBrowserSetAllExpanded, {isExpanded}); + return action(ActionType.QueryBrowserSetAllExpanded, { isExpanded }); }; -export const queryBrowserSetMetrics = (metrics: string[]) => action(ActionType.QueryBrowserSetMetrics, {metrics}); -export const queryBrowserToggleIsEnabled = (index: number) => action(ActionType.QueryBrowserToggleIsEnabled, {index}); -export const queryBrowserToggleSeries = (index: number, labels: {[key: string]: unknown}) => { - return action(ActionType.QueryBrowserToggleSeries, {index, labels}); +export const queryBrowserSetMetrics = (metrics: string[]) => + action(ActionType.QueryBrowserSetMetrics, { metrics }); +export const queryBrowserToggleIsEnabled = (index: number) => + action(ActionType.QueryBrowserToggleIsEnabled, { index }); +export const queryBrowserToggleSeries = (index: number, labels: { [key: string]: unknown }) => { + return action(ActionType.QueryBrowserToggleSeries, { index, labels }); }; -export const setConsoleLinks = (consoleLinks: string[]) => action(ActionType.SetConsoleLinks, {consoleLinks}); +export const setConsoleLinks = (consoleLinks: string[]) => + action(ActionType.SetConsoleLinks, { consoleLinks }); // TODO(alecmerdler): Implement all actions using `typesafe-actions` and add them to this export const uiActions = { diff --git a/frontend/public/co-fetch.js b/frontend/public/co-fetch.js index a073654bf4e..adb9c17a1cf 100644 --- a/frontend/public/co-fetch.js +++ b/frontend/public/co-fetch.js @@ -10,7 +10,7 @@ const initDefaults = { }; // TODO: url can be url or path, but shouldLogout only handles paths -export const shouldLogout = url => { +export const shouldLogout = (url) => { const k8sRegex = new RegExp(`^${window.SERVER_FLAGS.basePath}api/kubernetes/`); // 401 from k8s. show logout screen if (k8sRegex.test(url)) { @@ -19,7 +19,9 @@ export const shouldLogout = url => { if (proxyRegex.test(url)) { return false; } - const serviceRegex = new RegExp(`^${window.SERVER_FLAGS.basePath}api/kubernetes/api/v1/namespaces/\\w+/services/\\w+/proxy/`); + const serviceRegex = new RegExp( + `^${window.SERVER_FLAGS.basePath}api/kubernetes/api/v1/namespaces/\\w+/services/\\w+/proxy/`, + ); if (serviceRegex.test(url)) { return false; } @@ -45,7 +47,7 @@ const validateStatus = (response, url) => { } if (response.status === 403) { - return response.json().then(json => { + return response.json().then((json) => { const error = new Error(json.message || 'Access denied due to cluster policy.'); error.response = response; error.json = json; @@ -53,7 +55,7 @@ const validateStatus = (response, url) => { }); } - return response.json().then(json => { + return response.json().then((json) => { const cause = _.get(json, 'details.causes[0]'); let reason; if (cause) { @@ -84,12 +86,17 @@ export class TimeoutError extends Error { } const cookiePrefix = 'csrf-token='; -const getCSRFToken = () => document && document.cookie && document.cookie.split(';') - .map(c => _.trim(c)) - .filter(c => c.startsWith(cookiePrefix)) - .map(c => c.slice(cookiePrefix.length)).pop(); - -export const coFetch = (url, options = {}, timeout=20000) => { +const getCSRFToken = () => + document && + document.cookie && + document.cookie + .split(';') + .map((c) => _.trim(c)) + .filter((c) => c.startsWith(cookiePrefix)) + .map((c) => c.slice(cookiePrefix.length)) + .pop(); + +export const coFetch = (url, options = {}, timeout = 20000) => { const allOptions = _.defaultsDeep({}, initDefaults, options); if (allOptions.method !== 'GET') { allOptions.headers['X-CSRFToken'] = getCSRFToken(); @@ -104,8 +111,10 @@ export const coFetch = (url, options = {}, timeout=20000) => { // Initiate both the fetch promise and a timeout promise return Promise.race([ - fetch(url, allOptions).then(response => validateStatus(response, url)), - new Promise((unused, reject) => setTimeout(() => reject(new TimeoutError(url, timeout)), timeout)), + fetch(url, allOptions).then((response) => validateStatus(response, url)), + new Promise((unused, reject) => + setTimeout(() => reject(new TimeoutError(url, timeout)), timeout), + ), ]); }; @@ -116,8 +125,8 @@ export const coFetchUtils = { }; export const coFetchJSON = (url, method = 'GET', options = {}) => { - const headers = {Accept: 'application/json'}; - const {kind, name} = store.getState().UI.get('impersonate', {}); + const headers = { Accept: 'application/json' }; + const { kind, name } = store.getState().UI.get('impersonate', {}); if ((kind === 'User' || kind === 'Group') && name) { // Even if we are impersonating a group, we still need to set Impersonate-User to something or k8s will complain headers['Impersonate-User'] = name; @@ -126,8 +135,8 @@ export const coFetchJSON = (url, method = 'GET', options = {}) => { } } // Pass headers last to let callers to override Accept. - const allOptions = _.defaultsDeep({method}, options, {headers}); - return coFetch(url, allOptions).then(response => { + const allOptions = _.defaultsDeep({ method }, options, { headers }); + return coFetch(url, allOptions).then((response) => { if (!response.ok) { return response.text(); } @@ -144,7 +153,9 @@ const coFetchSendJSON = (url, method, json = null, options = {}) => { const allOptions = { headers: { Accept: 'application/json', - 'Content-Type': `application/${method === 'PATCH' ? 'json-patch+json' : 'json'};charset=UTF-8`, + 'Content-Type': `application/${ + method === 'PATCH' ? 'json-patch+json' : 'json' + };charset=UTF-8`, }, }; if (json) { diff --git a/frontend/public/components/RBAC/bindings.jsx b/frontend/public/components/RBAC/bindings.jsx index 34dbd9570bd..b1a32cfb338 100644 --- a/frontend/public/components/RBAC/bindings.jsx +++ b/frontend/public/components/RBAC/bindings.jsx @@ -32,33 +32,39 @@ import { isSystemRole } from './index'; import { connectToFlags, flagPending } from '../../reducers/features'; import { FLAGS } from '../../const'; -const bindingKind = binding => binding.metadata.namespace ? 'RoleBinding' : 'ClusterRoleBinding'; +const bindingKind = (binding) => + binding.metadata.namespace ? 'RoleBinding' : 'ClusterRoleBinding'; // Split each binding into one row per subject -export const flatten = resources => _.flatMap(resources, resource => { - const ret = []; +export const flatten = (resources) => + _.flatMap(resources, (resource) => { + const ret = []; - _.each(resource.data, binding => { - if (!binding) { - return undefined; - } - if (_.isEmpty(binding.subjects)) { - const subject = {kind: '-', name: '-'}; - return ret.push(Object.assign({}, binding, {subject})); - } - _.each(binding.subjects, (subject, subjectIndex) => { - ret.push(Object.assign({}, binding, { - subject, - subjectIndex, - rowKey: `${getQN(binding)}|${subject.kind}|${subject.name}${subject.namespace ? `|${subject.namespace}` : ''}`, - })); + _.each(resource.data, (binding) => { + if (!binding) { + return undefined; + } + if (_.isEmpty(binding.subjects)) { + const subject = { kind: '-', name: '-' }; + return ret.push(Object.assign({}, binding, { subject })); + } + _.each(binding.subjects, (subject, subjectIndex) => { + ret.push( + Object.assign({}, binding, { + subject, + subjectIndex, + rowKey: `${getQN(binding)}|${subject.kind}|${subject.name}${ + subject.namespace ? `|${subject.namespace}` : '' + }`, + }), + ); + }); }); - }); - return ret; -}); + return ret; + }); -const menuActions = ({subjectIndex, subjects}, startImpersonate) => { +const menuActions = ({ subjectIndex, subjects }, startImpersonate) => { const subject = subjects[subjectIndex]; const actions = [ @@ -67,7 +73,9 @@ const menuActions = ({subjectIndex, subjects}, startImpersonate) => { href: `${resourceObjPath(obj, kind.kind)}/copy?subjectIndex=${subjectIndex}`, // Only perform access checks when duplicating cluster role bindings. // It's not practical to check namespace role bindings since we don't know what namespace the user will pick in the form. - accessReview: _.get(obj, 'metadata.namespace') ? null : { group: kind.apiGroup, resource: kind.plural, verb: 'create' }, + accessReview: _.get(obj, 'metadata.namespace') + ? null + : { group: kind.apiGroup, resource: kind.plural, verb: 'create' }, }), (kind, obj) => ({ label: `Edit ${kind.label} Subject`, @@ -80,22 +88,28 @@ const menuActions = ({subjectIndex, subjects}, startImpersonate) => { verb: 'update', }, }), - subjects.length === 1 ? Kebab.factory.Delete : (kind, binding) => ({ - label: `Delete ${kind.label} Subject`, - callback: () => confirmModal({ - title: `Delete ${kind.label} Subject`, - message: `Are you sure you want to delete subject ${subject.name} of type ${subject.kind}?`, - btnText: 'Delete Subject', - executeFn: () => k8sPatch(kind, binding, [{op: 'remove', path: `/subjects/${subjectIndex}`}]), - }), - accessReview: { - group: kind.apiGroup, - resource: kind.plural, - name: binding.metadata.name, - namespace: binding.metadata.namespace, - verb: 'patch', - }, - }), + subjects.length === 1 + ? Kebab.factory.Delete + : (kind, binding) => ({ + label: `Delete ${kind.label} Subject`, + callback: () => + confirmModal({ + title: `Delete ${kind.label} Subject`, + message: `Are you sure you want to delete subject ${subject.name} of type ${ + subject.kind + }?`, + btnText: 'Delete Subject', + executeFn: () => + k8sPatch(kind, binding, [{ op: 'remove', path: `/subjects/${subjectIndex}` }]), + }), + accessReview: { + group: kind.apiGroup, + resource: kind.plural, + name: binding.metadata.name, + namespace: binding.metadata.namespace, + verb: 'patch', + }, + }), ]; if (subject.kind === 'User' || subject.kind === 'Group') { @@ -120,41 +134,66 @@ const tableColumnClasses = [ const RoleBindingsTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Role Ref', sortField: 'roleRef.name', transforms: [sortable], + title: 'Role Ref', + sortField: 'roleRef.name', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Subject Kind', sortField: 'subject.kind', transforms: [sortable], + title: 'Subject Kind', + sortField: 'subject.kind', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Subject Name', sortField: 'subject.name', transforms: [sortable], + title: 'Subject Name', + sortField: 'subject.name', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; RoleBindingsTableHeader.displayName = 'RoleBindingsTableHeader'; -export const BindingName = ({binding}) => { - ; +export const BindingName = ({ binding }) => { + ; }; -export const BindingKebab = connect(null, {startImpersonate: UIActions.startImpersonate})( - ({binding, startImpersonate}) => binding.subjects ? : null +export const BindingKebab = connect( + null, + { startImpersonate: UIActions.startImpersonate }, +)(({ binding, startImpersonate }) => + binding.subjects ? ( + + ) : null, ); -export const RoleLink = ({binding}) => { +export const RoleLink = ({ binding }) => { const kind = binding.roleRef.kind; // Cluster Roles have no namespace and for Roles, the Role's namespace matches the Role Binding's namespace @@ -162,11 +201,16 @@ export const RoleLink = ({binding}) => { return ; }; -const RoleBindingsTableRow = ({obj: binding, index, key, style}) => { +const RoleBindingsTableRow = ({ obj: binding, index, key, style }) => { return ( - + @@ -178,7 +222,11 @@ const RoleBindingsTableRow = ({obj: binding, index, key, style}) => { {binding.subject.name} - {binding.metadata.namespace ? : 'all'} + {binding.metadata.namespace ? ( + + ) : ( + 'all' + )} @@ -188,11 +236,25 @@ const RoleBindingsTableRow = ({obj: binding, index, key, style}) => { }; RoleBindingsTableRow.displayName = 'RoleBindingsTableRow'; -const EmptyMsg = () => ; +const EmptyMsg = () => ( + +); -export const BindingsList = props => ; +export const BindingsList = (props) => ( +
+); -export const bindingType = binding => { +export const bindingType = (binding) => { if (!binding) { return undefined; } @@ -203,48 +265,52 @@ export const bindingType = binding => { }; const roleResources = [ - {kind: 'RoleBinding', namespaced: true}, - {kind: 'ClusterRoleBinding', namespaced: false, optional: true}, + { kind: 'RoleBinding', namespaced: true }, + { kind: 'ClusterRoleBinding', namespaced: false, optional: true }, ]; -export const RoleBindingsPage = ({namespace, showTitle=true, mock}) => { - const items = [ - {id: 'namespace', title: 'Namespace Role Bindings'}, - {id: 'system', title: 'System Role Bindings'}, - ]; - if (data && data.loaded && !data.loadError) { - items.unshift({id: 'cluster', title: 'Cluster-wide Role Bindings'}); - } - return items; - }, - }]} - showTitle={showTitle} - textFilter="role-binding" - title="Role Bindings" -/>; +export const RoleBindingsPage = ({ namespace, showTitle = true, mock }) => ( + { + const items = [ + { id: 'namespace', title: 'Namespace Role Bindings' }, + { id: 'system', title: 'System Role Bindings' }, + ]; + if (data && data.loaded && !data.loadError) { + items.unshift({ id: 'cluster', title: 'Cluster-wide Role Bindings' }); + } + return items; + }, + }, + ]} + showTitle={showTitle} + textFilter="role-binding" + title="Role Bindings" + /> +); -const NsRoleDropdown_ = props => { +const NsRoleDropdown_ = (props) => { const openshiftFlag = props.flags[FLAGS.OPENSHIFT]; if (flagPending(openshiftFlag)) { return null; } - const roleFilter = role => !isSystemRole(role); + const roleFilter = (role) => !isSystemRole(role); let kinds; if (props.fixed) { @@ -254,50 +320,73 @@ const NsRoleDropdown_ = props => { } else { kinds = ['ClusterRole']; } - const resourceForKind = kind => ({ kind, namespace: kind === 'Role' ? props.namespace : null }); + const resourceForKind = (kind) => ({ kind, namespace: kind === 'Role' ? props.namespace : null }); const resources = _.map(kinds, resourceForKind); - return ; + return ( + + ); }; const NsRoleDropdown = connectToFlags(FLAGS.OPENSHIFT)(NsRoleDropdown_); -const ClusterRoleDropdown = props => !isSystemRole(role)} - desc="Cluster-wide Roles (ClusterRole)" - resources={[{ kind: 'ClusterRole' }]} - placeholder="Select role name" -/>; +const ClusterRoleDropdown = (props) => ( + !isSystemRole(role)} + desc="Cluster-wide Roles (ClusterRole)" + resources={[{ kind: 'ClusterRole' }]} + placeholder="Select role name" + /> +); const bindingKinds = [ - {value: 'RoleBinding', title: 'Namespace Role Binding (RoleBinding)', desc: 'Grant the permissions to a user or set of users within the selected namespace.'}, - {value: 'ClusterRoleBinding', title: 'Cluster-wide Role Binding (ClusterRoleBinding)', desc: 'Grant the permissions to a user or set of users at the cluster level and in all namespaces.'}, + { + value: 'RoleBinding', + title: 'Namespace Role Binding (RoleBinding)', + desc: 'Grant the permissions to a user or set of users within the selected namespace.', + }, + { + value: 'ClusterRoleBinding', + title: 'Cluster-wide Role Binding (ClusterRoleBinding)', + desc: + 'Grant the permissions to a user or set of users at the cluster level and in all namespaces.', + }, ]; const subjectKinds = [ - {value: 'User', title: 'User'}, - {value: 'Group', title: 'Group'}, - {value: 'ServiceAccount', title: 'Service Account'}, + { value: 'User', title: 'User' }, + { value: 'Group', title: 'Group' }, + { value: 'ServiceAccount', title: 'Service Account' }, ]; -const Section = ({label, children}) =>
-
{label}
-
{children}
-
; +const Section = ({ label, children }) => ( +
+
{label}
+
{children}
+
+); -const BaseEditRoleBinding = connect(null, {setActiveNamespace: UIActions.setActiveNamespace})( +const BaseEditRoleBinding = connect( + null, + { setActiveNamespace: UIActions.setActiveNamespace }, +)( class BaseEditRoleBinding_ extends React.Component { constructor(props) { super(props); this.subjectIndex = props.subjectIndex || 0; - const existingData = _.pick(props.obj, ['metadata.name', 'metadata.namespace', 'roleRef', 'subjects']); + const existingData = _.pick(props.obj, [ + 'metadata.name', + 'metadata.namespace', + 'roleRef', + 'subjects', + ]); existingData.kind = props.kind; const data = _.defaultsDeep({}, props.fixed, existingData, { apiVersion: 'rbac.authorization.k8s.io/v1', @@ -308,32 +397,34 @@ const BaseEditRoleBinding = connect(null, {setActiveNamespace: UIActions.setActi roleRef: { apiGroup: 'rbac.authorization.k8s.io', }, - subjects: [{ - apiGroup: 'rbac.authorization.k8s.io', - kind: 'User', - name: '', - }], + subjects: [ + { + apiGroup: 'rbac.authorization.k8s.io', + kind: 'User', + name: '', + }, + ], }); - this.state = {data, inProgress: false}; + this.state = { data, inProgress: false }; this.setKind = this.setKind.bind(this); this.setSubject = this.setSubject.bind(this); this.save = this.save.bind(this); - this.setData = patch => this.setState({data: _.defaultsDeep({}, patch, this.state.data)}); - this.changeName = e => this.setData({metadata: {name: e.target.value}}); - this.changeNamespace = namespace => this.setData({metadata: {namespace}}); - this.changeRoleRef = (name, kindId) => this.setData({roleRef: {name, kind: kindId}}); - this.changeSubjectKind = e => this.setSubject({kind: e.target.value}); - this.changeSubjectName = e => this.setSubject({name: e.target.value}); - this.changeSubjectNamespace = namespace => this.setSubject({namespace}); + this.setData = (patch) => this.setState({ data: _.defaultsDeep({}, patch, this.state.data) }); + this.changeName = (e) => this.setData({ metadata: { name: e.target.value } }); + this.changeNamespace = (namespace) => this.setData({ metadata: { namespace } }); + this.changeRoleRef = (name, kindId) => this.setData({ roleRef: { name, kind: kindId } }); + this.changeSubjectKind = (e) => this.setSubject({ kind: e.target.value }); + this.changeSubjectName = (e) => this.setSubject({ name: e.target.value }); + this.changeSubjectNamespace = (namespace) => this.setSubject({ namespace }); } setKind(e) { const kind = e.target.value; - const patch = {kind}; + const patch = { kind }; if (kind === 'ClusterRoleBinding') { - patch.metadata = {namespace: null}; + patch.metadata = { namespace: null }; } this.setData(patch); } @@ -343,150 +434,209 @@ const BaseEditRoleBinding = connect(null, {setActiveNamespace: UIActions.setActi } setSubject(patch) { - const {kind, name, namespace} = Object.assign({}, this.getSubject(), patch); + const { kind, name, namespace } = Object.assign({}, this.getSubject(), patch); const data = Object.assign({}, this.state.data); - data.subjects[this.subjectIndex] = kind === 'ServiceAccount' ? {kind, name, namespace} : {apiGroup: 'rbac.authorization.k8s.io', kind, name}; - this.setState({data}); + data.subjects[this.subjectIndex] = + kind === 'ServiceAccount' + ? { kind, name, namespace } + : { apiGroup: 'rbac.authorization.k8s.io', kind, name }; + this.setState({ data }); } save(e) { e.preventDefault(); - const {kind, metadata, roleRef} = this.state.data; + const { kind, metadata, roleRef } = this.state.data; const subject = this.getSubject(); - if (!kind || !metadata.name || !roleRef.kind || !roleRef.name || !subject.kind || !subject.name || - (kind === 'RoleBinding' && !metadata.namespace) || - (subject.kind === 'ServiceAccount') && !subject.namespace) { - this.setState({error: 'Please complete all fields.'}); + if ( + !kind || + !metadata.name || + !roleRef.kind || + !roleRef.name || + !subject.kind || + !subject.name || + (kind === 'RoleBinding' && !metadata.namespace) || + (subject.kind === 'ServiceAccount' && !subject.namespace) + ) { + this.setState({ error: 'Please complete all fields.' }); return; } - this.setState({inProgress: true}); + this.setState({ inProgress: true }); const ko = kindObj(kind); (this.props.isCreate ? k8sCreate(ko, this.state.data) - : k8sPatch(ko, {metadata}, [{op: 'replace', path: `/subjects/${this.subjectIndex}`, value: subject}]) - ).then(obj => { - this.setState({inProgress: false}); - if (metadata.namespace) { - this.props.setActiveNamespace(metadata.namespace); - } - history.push(resourceObjPath(obj, referenceFor(obj))); - }, err => this.setState({error: err.message, inProgress: false})); + : k8sPatch(ko, { metadata }, [ + { op: 'replace', path: `/subjects/${this.subjectIndex}`, value: subject }, + ]) + ).then( + (obj) => { + this.setState({ inProgress: false }); + if (metadata.namespace) { + this.props.setActiveNamespace(metadata.namespace); + } + history.push(resourceObjPath(obj, referenceFor(obj))); + }, + (err) => this.setState({ error: err.message, inProgress: false }), + ); } render() { - const {kind, metadata, roleRef} = this.state.data; + const { kind, metadata, roleRef } = this.state.data; const subject = this.getSubject(); - const {fixed, saveButtonText} = this.props; + const { fixed, saveButtonText } = this.props; const RoleDropdown = kind === 'RoleBinding' ? NsRoleDropdown : ClusterRoleDropdown; const title = `${this.props.titleVerb} ${kindObj(kind).label}`; - return
- - {title} - -
-

{title}

-

Associate a user/group to the selected role to define the type of access and resources that are allowed.

- - {!_.get(fixed, 'kind') &&
- -
} - -
- -
-
- - {_.get(fixed, 'metadata.name') - ? - : } -
- {kind === 'RoleBinding' &&
- - -
} -
- -
- -
-
- - -
-
- -
- -
-
- -
- {subject.kind === 'ServiceAccount' &&
- - -
} -
- - -
-
- -
- - - - - - - - -
; + return ( +
+ + {title} + +
+

{title}

+

+ Associate a user/group to the selected role to define the type of access and resources + that are allowed. +

+ + {!_.get(fixed, 'kind') && ( +
+ +
+ )} + +
+ +
+
+ + {_.get(fixed, 'metadata.name') ? ( + + ) : ( + + )} +
+ {kind === 'RoleBinding' && ( +
+ + +
+ )} +
+ +
+ +
+
+ + +
+
+ +
+ +
+
+ +
+ {subject.kind === 'ServiceAccount' && ( +
+ + +
+ )} +
+ + +
+
+ +
+ + + + + + + + +
+ ); } - }); + }, +); -export const CreateRoleBinding = ({match: {params}, location}) => { +export const CreateRoleBinding = ({ match: { params }, location }) => { const searchParams = new URLSearchParams(location.search); const roleKind = searchParams.get('rolekind'); const roleName = searchParams.get('rolename'); - const metadata = {namespace: UIActions.getActiveNamespace()}; + const metadata = { namespace: UIActions.getActiveNamespace() }; const clusterAllowed = useAccessReview({ group: ClusterRoleBindingModel.apiGroup, resource: ClusterRoleBindingModel.plural, verb: 'create', }); const fixed = { - kind: (params.ns || roleKind === 'Role' || !clusterAllowed) ? 'RoleBinding' : undefined, - metadata: {namespace: params.ns}, - roleRef: {kind: roleKind, name: roleName}, + kind: params.ns || roleKind === 'Role' || !clusterAllowed ? 'RoleBinding' : undefined, + metadata: { namespace: params.ns }, + roleRef: { kind: roleKind, name: roleName }, }; - return ; + return ( + + ); }; const getSubjectIndex = () => { @@ -494,18 +644,38 @@ const getSubjectIndex = () => { return parseInt(subjectIndex, 10); }; -const BindingLoadingWrapper = props => { +const BindingLoadingWrapper = (props) => { const fixed = {}; - _.each(props.fixedKeys, k => fixed[k] = _.get(props.obj.data, k)); - return - - ; + _.each(props.fixedKeys, (k) => (fixed[k] = _.get(props.obj.data, k))); + return ( + + + + ); }; -export const EditRoleBinding = ({match: {params}, kind}) => - -; +export const EditRoleBinding = ({ match: { params }, kind }) => ( + + + +); -export const CopyRoleBinding = ({match: {params}, kind}) => - -; +export const CopyRoleBinding = ({ match: { params }, kind }) => ( + + + +); diff --git a/frontend/public/components/RBAC/edit-rule.jsx b/frontend/public/components/RBAC/edit-rule.jsx index f81431c2706..3d4752b9ac5 100644 --- a/frontend/public/components/RBAC/edit-rule.jsx +++ b/frontend/public/components/RBAC/edit-rule.jsx @@ -7,14 +7,32 @@ import { ActionGroup, Button } from '@patternfly/react-core'; import { k8sGet, k8sUpdate } from '../../module/k8s'; import { RoleModel, ClusterRoleModel } from '../../models'; import { errorModal } from '../modals'; -import { SectionHeading, history, ResourceIcon, resourceObjPath, PromiseComponent, ButtonBar, LoadingBox } from '../utils'; +import { + SectionHeading, + history, + ResourceIcon, + resourceObjPath, + PromiseComponent, + ButtonBar, + LoadingBox, +} from '../utils'; import * as k8sActions from '../../actions/k8s'; const NON_RESOURCE_VERBS = ['get', 'post', 'put', 'delete']; const READ_VERBS = new Set(['get', 'list', 'proxy', 'redirect', 'watch']); -const READ_WRITE_VERBS = new Set([...READ_VERBS].concat(['create', 'delete', 'deletecollection', 'patch', 'post', 'put', 'update'])); - -const API_VERBS = [...READ_WRITE_VERBS].filter(x => !_.includes(NON_RESOURCE_VERBS, x)); +const READ_WRITE_VERBS = new Set( + [...READ_VERBS].concat([ + 'create', + 'delete', + 'deletecollection', + 'patch', + 'post', + 'put', + 'update', + ]), +); + +const API_VERBS = [...READ_WRITE_VERBS].filter((x) => !_.includes(NON_RESOURCE_VERBS, x)); const ALL_VERBS = [...READ_WRITE_VERBS]; const VERBS_ENUM = { @@ -30,35 +48,51 @@ const RESOURCE_ENUM = { CUSTOM: 'R-CUSTOM', }; -const HelpText = (props) => - {props.children} -; - -const Checkbox = ({value, checked, onChange}) =>
- -
; - -const RadioButton = ({name, value, label, text, onChange, activeValue}) =>
- -
; +const HelpText = (props) => {props.children}; + +const Checkbox = ({ value, checked, onChange }) => ( +
+ +
+); + +const RadioButton = ({ name, value, label, text, onChange, activeValue }) => ( +
+ +
+); const HRMinor = () =>
; const HRMajor = () =>
; -const stateToProps = state => { +const stateToProps = (state) => { const resourceMap = state.k8s.get('RESOURCES'); return resourceMap ? resourceMap.toObject() : {}; }; -const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) ( +const EditRule = connect( + stateToProps, + { getResources: k8sActions.getResources }, +)( class EditRule_ extends PromiseComponent { constructor(props) { super(props); @@ -76,11 +110,11 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) this.save = this.save.bind(this); this.set = (name, value) => { - this.setState({[name]: value}); + this.setState({ [name]: value }); }; - this.isVerbSelected = v => this.isVerbSelected_(v); - this.isResourceSelected = r => this.isResourceSelected_(r); + this.isVerbSelected = (v) => this.isVerbSelected_(v); + this.isResourceSelected = (r) => this.isResourceSelected_(r); this.toggleVerb = (name, checked) => this.toggleVerb_(name, checked); this.toggleResource = (name, checked) => this.toggleResource_(name, checked); @@ -94,12 +128,12 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) } getResource() { - const {name, namespace} = this.props; + const { name, namespace } = this.props; k8sGet(this.kind, name, namespace) - .then(role => { - const {props} = this; - this.setState({role}); + .then((role) => { + const { props } = this; + this.setState({ role }); if (!props.rule) { return; @@ -117,7 +151,7 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) }; let all = false; - rule.verbs.forEach(v => { + rule.verbs.forEach((v) => { if (v === '*') { all = true; return false; @@ -129,7 +163,7 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) all = false; if (_.has(rule, 'resources')) { - rule.resources.forEach(r => { + rule.resources.forEach((r) => { if (r === '*') { all = true; return false; @@ -142,18 +176,22 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) state.APIGroups = rule.apiGroups ? rule.apiGroups.join(', ') : ''; state.nonResourceURLs = rule.nonResourceURLs ? rule.nonResourceURLs.join(', ') : ''; this.setState(state); - }).catch(this.errorModal.bind(this)); + }) + .catch(this.errorModal.bind(this)); } save() { - const {APIGroups, verbControl, resourceControl, nonResourceURLs} = this.state; + const { APIGroups, verbControl, resourceControl, nonResourceURLs } = this.state; const allResources = this.props.allResources || []; const rule = { apiGroups: APIGroups ? APIGroups.split(',') : [''], nonResourceURLs: nonResourceURLs ? nonResourceURLs.split(',') : [], verbs: verbControl === VERBS_ENUM.ALL ? ['*'] : ALL_VERBS.filter(this.isVerbSelected), - resources: resourceControl === RESOURCE_ENUM.ALL ? ['*'] : allResources.filter(this.isResourceSelected), + resources: + resourceControl === RESOURCE_ENUM.ALL + ? ['*'] + : allResources.filter(this.isResourceSelected), }; const role = _.cloneDeep(this.state.role); role.rules = role.rules || []; @@ -162,14 +200,13 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) } else { role.rules.push(rule); } - this.handlePromise(k8sUpdate(this.kind, role)) - .then(() => { - history.push(`${resourceObjPath(role, this.kind.kind)}`); - }); + this.handlePromise(k8sUpdate(this.kind, role)).then(() => { + history.push(`${resourceObjPath(role, this.kind.kind)}`); + }); } isVerbSelected_(v) { - const {verbsSet, verbControl} = this.state; + const { verbsSet, verbControl } = this.state; switch (verbControl) { case VERBS_ENUM.CUSTOM: return verbsSet.has(v); @@ -186,8 +223,8 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) } isResourceSelected_(r) { - const {adminResources} = this.props; - const {resourceControl, resourceSet} = this.state; + const { adminResources } = this.props; + const { resourceControl, resourceSet } = this.state; switch (resourceControl) { case RESOURCE_ENUM.CUSTOM: return resourceSet.has(r); @@ -203,7 +240,7 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) } setNonResourceURL(nonResourceURLs) { - const state = {nonResourceURLs}; + const state = { nonResourceURLs }; if (this.state.resourceControl !== RESOURCE_ENUM.NON) { state.resourceControl = RESOURCE_ENUM.NON; @@ -219,7 +256,7 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) verbsSet = this.state.verbsSet; } else { verbsSet = new Set(); - ALL_VERBS.filter(this.isVerbSelected).forEach(r => verbsSet.add(r)); + ALL_VERBS.filter(this.isVerbSelected).forEach((r) => verbsSet.add(r)); } if (isSelected) { @@ -228,7 +265,7 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) verbsSet.delete(name); } - this.setState({verbsSet, verbControl: VERBS_ENUM.CUSTOM}); + this.setState({ verbsSet, verbControl: VERBS_ENUM.CUSTOM }); } toggleResource_(name, isSelected) { @@ -238,8 +275,9 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) resourceSet = this.state.resourceSet; } else { resourceSet = new Set(); - const {allResources} = this.props; - allResources && allResources.filter(this.isResourceSelected).forEach(r => resourceSet.add(r)); + const { allResources } = this.props; + allResources && + allResources.filter(this.isResourceSelected).forEach((r) => resourceSet.add(r)); } if (isSelected) { @@ -248,21 +286,21 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) resourceSet.delete(name); } - this.setState({resourceSet, resourceControl: RESOURCE_ENUM.CUSTOM}); + this.setState({ resourceSet, resourceControl: RESOURCE_ENUM.CUSTOM }); } setApiGroups(APIGroups) { - this.setState({APIGroups}); + this.setState({ APIGroups }); } errorModal(error) { const message = _.get(error, 'data.message') || error.statusText; - errorModal({error: message}); + errorModal({ error: message }); } render() { - const {name, namespace, namespacedSet, safeResources, adminResources, rule} = this.props; - const {verbControl, resourceControl, nonResourceURLs, APIGroups, role} = this.state; + const { name, namespace, namespacedSet, safeResources, adminResources, rule } = this.props; + const { verbControl, resourceControl, nonResourceURLs, APIGroups, role } = this.state; const heading = `${rule === undefined ? 'Create' : 'Edit'} Access Rule`; return ( @@ -275,31 +313,31 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources})

- Each role is made up of a set of rules, which defines the type of access and resources that are allowed to be manipulated. + Each role is made up of a set of rules, which defines the type of access and + resources that are allowed to be manipulated.

- { this.kind.label } Name + {this.kind.label} Name
{name}
- { - namespace && -
-
- Namespace -
-
- {namespace} + {namespace && ( +
+
+ Namespace +
+
+ {namespace} +
-
- } + )}
@@ -307,26 +345,48 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources}) Type of Access
- - - + + +
-
-
+

Actions

- { - (namespace ? API_VERBS : ALL_VERBS).map(verb => ) - } + {(namespace ? API_VERBS : ALL_VERBS).map((verb) => ( + + ))}
@@ -339,74 +399,127 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources})
- - - - - - - { - !namespace &&
- + + + + + + + {!namespace && ( +
+ - this.setNonResourceURL(e.target.value)} placeholder="Comma separated list of non-resource urls (/apis/extensions/v1beta1)" /> + this.setNonResourceURL(e.target.value)} + placeholder="Comma separated list of non-resource urls (/apis/extensions/v1beta1)" + />
- } + )}
-
-
+
-

Safe Resources

+

+ Safe Resources +

- { safeResources - ? safeResources - .filter(r=> namespace ? namespacedSet.has(r) : true ) - .map(r => ) - : - } + {safeResources ? ( + safeResources + .filter((r) => (namespace ? namespacedSet.has(r) : true)) + .map((r) => ( + + )) + ) : ( + + )}
-
-
+

Admin Resources

- { adminResources - ? adminResources - .filter(r=> namespace ? namespacedSet.has(r) : true) - .map(r => ) - : - } + {adminResources ? ( + adminResources + .filter((r) => (namespace ? namespacedSet.has(r) : true)) + .map((r) => ( + + )) + ) : ( + + )}
-
-
+
-

Restrict this role to a subset of API URLs that don’t correspond to objects.

+

+ Restrict this role to a subset of API URLs that don’t correspond to objects. +

- this.setApiGroups(e.target.value)} placeholder="Comma separated list of the api groups for the selected resources." /> + this.setApiGroups(e.target.value)} + placeholder="Comma separated list of the api groups for the selected resources." + />
@@ -415,22 +528,19 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources})
- + - - {role && - - } + )}
@@ -439,10 +549,9 @@ const EditRule = connect(stateToProps, {getResources: k8sActions.getResources})
); } - }); + }, +); -export const EditRulePage = ({match: {params}}) => ; +export const EditRulePage = ({ match: { params } }) => ( + +); diff --git a/frontend/public/components/RBAC/role.jsx b/frontend/public/components/RBAC/role.jsx index 408b8892a4a..194698e000a 100644 --- a/frontend/public/components/RBAC/role.jsx +++ b/frontend/public/components/RBAC/role.jsx @@ -8,13 +8,21 @@ import { sortable } from '@patternfly/react-table'; import { flatten as bindingsFlatten } from './bindings'; import { BindingName, BindingsList, RulesList } from './index'; import { DetailsPage, MultiListPage, TextFilter, Table, TableRow, TableData } from '../factory'; -import { Kebab, SectionHeading, MsgBox, navFactory, ResourceKebab, ResourceLink, Timestamp } from '../utils'; +import { + Kebab, + SectionHeading, + MsgBox, + navFactory, + ResourceKebab, + ResourceLink, + Timestamp, +} from '../utils'; -export const isSystemRole = role => _.startsWith(role.metadata.name, 'system:'); +export const isSystemRole = (role) => _.startsWith(role.metadata.name, 'system:'); // const addHref = (name, ns) => ns ? `/k8s/ns/${ns}/roles/${name}/add-rule` : `/k8s/cluster/clusterroles/${name}/add-rule`; -export const roleKind = role => role.metadata.namespace ? 'Role' : 'ClusterRole'; +export const roleKind = (role) => (role.metadata.namespace ? 'Role' : 'ClusterRole'); const menuActions = [ // This page is temporarily disabled until we update the safe resources list. @@ -24,42 +32,51 @@ const menuActions = [ // }), (kind, role) => ({ label: 'Add Role Binding', - href: `/k8s/cluster/rolebindings/~new?rolekind=${roleKind(role)}&rolename=${role.metadata.name}`, + href: `/k8s/cluster/rolebindings/~new?rolekind=${roleKind(role)}&rolename=${ + role.metadata.name + }`, }), Kebab.factory.Edit, Kebab.factory.Delete, ]; -const roleColumnClasses = [ - classNames('col-xs-6'), - classNames('col-xs-6'), - Kebab.columnClass, -]; - +const roleColumnClasses = [classNames('col-xs-6'), classNames('col-xs-6'), Kebab.columnClass]; const RolesTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], - props: { className: roleColumnClasses[0]}, + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], + props: { className: roleColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], - props: { className: roleColumnClasses[1]}, + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], + props: { className: roleColumnClasses[1] }, }, - { title: '', props: { className: roleColumnClasses[2]}}, + { title: '', props: { className: roleColumnClasses[2] } }, ]; }; RolesTableHeader.displayName = 'RolesTableHeader'; -const RolesTableRow = ({obj: role, index, key, style}) => { +const RolesTableRow = ({ obj: role, index, key, style }) => { return ( - + - {role.metadata.namespace ? : 'all'} + {role.metadata.namespace ? ( + + ) : ( + 'all' + )} @@ -73,60 +90,70 @@ class Details extends React.Component { constructor(props) { super(props); this.state = {}; - this.changeFilter = e => this.setState({ruleFilter: e.target.value}); + this.changeFilter = (e) => this.setState({ ruleFilter: e.target.value }); } render() { const ruleObj = this.props.obj; - const {creationTimestamp, name, namespace} = ruleObj.metadata; - const {ruleFilter} = this.state; + const { creationTimestamp, name, namespace } = ruleObj.metadata; + const { ruleFilter } = this.state; let rules = ruleObj.rules; if (ruleFilter) { const fuzzyCaseInsensitive = (a, b) => fuzzy(_.toLower(a), _.toLower(b)); const searchKeys = ['nonResourceURLs', 'resources', 'verbs']; - rules = rules.filter(rule => searchKeys.some(k => _.some(rule[k], v => fuzzyCaseInsensitive(ruleFilter, v)))); + rules = rules.filter((rule) => + searchKeys.some((k) => _.some(rule[k], (v) => fuzzyCaseInsensitive(ruleFilter, v))), + ); } - return
-
- -
-
-
-
Role Name
-
{name}
- {namespace &&
-
Namespace
-
-
} -
-
-
-
-
Created At
-
-
+ return ( +
+
+ +
+
+
+
Role Name
+
{name}
+ {namespace && ( +
+
Namespace
+
+ +
+
+ )} +
+
+
+
+
Created At
+
+ +
+
+
-
-
- -
- {/* This page is temporarily disabled until we update the safe resources list. +
+ +
+ {/* This page is temporarily disabled until we update the safe resources list.
*/} -
- +
+ +
+
-
-
; + ); } } @@ -140,77 +167,115 @@ const bindingsColumnClasses = [ const BindingsTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], - props: { className: bindingsColumnClasses[0]}, + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], + props: { className: bindingsColumnClasses[0] }, }, { - title: 'Subject Kind', sortField: 'subject.kind', transforms: [sortable], - props: { className: bindingsColumnClasses[1]}, + title: 'Subject Kind', + sortField: 'subject.kind', + transforms: [sortable], + props: { className: bindingsColumnClasses[1] }, }, { - title: 'Subject Name', sortField: 'subject.name', transforms: [sortable], - props: { className: bindingsColumnClasses[2]}, + title: 'Subject Name', + sortField: 'subject.name', + transforms: [sortable], + props: { className: bindingsColumnClasses[2] }, }, - { title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], - props: { className: bindingsColumnClasses[3]}, + { + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], + props: { className: bindingsColumnClasses[3] }, }, ]; }; BindingsTableHeader.displayName = 'BindingsTableHeader'; -const BindingsTableRow = ({obj: binding, index, key, style}) => { +const BindingsTableRow = ({ obj: binding, index, key, style }) => { return ( - - {binding.subject.kind} - - - {binding.subject.name} - - - {binding.namespace || 'all'} - + {binding.subject.kind} + {binding.subject.name} + {binding.namespace || 'all'} ); }; BindingsTableRow.displayName = 'BindingsTableRow'; -const BindingsListComponent = props => ; +const BindingsListComponent = (props) => ( + +); export const BindingsForRolePage = (props) => { - const {match: {params: {name, ns}}, obj:{kind}} = props; - const resources = [{kind: 'RoleBinding', namespaced: true}]; + const { + match: { + params: { name, ns }, + }, + obj: { kind }, + } = props; + const resources = [{ kind: 'RoleBinding', namespaced: true }]; if (!ns) { - resources.push({kind: 'ClusterRoleBinding', namespaced: false, optional: true}); + resources.push({ kind: 'ClusterRoleBinding', namespaced: false, optional: true }); } - return ; + return ( + + ); }; -export const RolesDetailsPage = props => ; +export const RolesDetailsPage = (props) => ( + +); export const ClusterRolesDetailsPage = RolesDetailsPage; -const EmptyMsg = () => ; +const EmptyMsg = () => ( + +); -const RolesList = props =>
; +const RolesList = (props) => ( +
+); -export const roleType = role => { +export const roleType = (role) => { if (!role) { return undefined; } @@ -220,35 +285,39 @@ export const roleType = role => { return role.metadata.namespace ? 'namespace' : 'cluster'; }; -export const RolesPage = ({namespace, mock, showTitle}) => { +export const RolesPage = ({ namespace, mock, showTitle }) => { const createNS = namespace || 'default'; const accessReview = { model: RoleModel, namespace: createNS, }; - return _.flatMap(resources, 'data').filter(r => !!r)} - resources={[ - {kind: 'Role', namespaced: true, optional: mock}, - {kind: 'ClusterRole', namespaced: false, optional: true}, - ]} - rowFilters={[{ - type: 'role-kind', - selected: ['cluster', 'namespace'], - reducer: roleType, - items: [ - {id: 'cluster', title: 'Cluster-wide Roles'}, - {id: 'namespace', title: 'Namespace Roles'}, - {id: 'system', title: 'System Roles'}, - ], - }]} - title="Roles" - />; + return ( + _.flatMap(resources, 'data').filter((r) => !!r)} + resources={[ + { kind: 'Role', namespaced: true, optional: mock }, + { kind: 'ClusterRole', namespaced: false, optional: true }, + ]} + rowFilters={[ + { + type: 'role-kind', + selected: ['cluster', 'namespace'], + reducer: roleType, + items: [ + { id: 'cluster', title: 'Cluster-wide Roles' }, + { id: 'namespace', title: 'Namespace Roles' }, + { id: 'system', title: 'System Roles' }, + ], + }, + ]} + title="Roles" + /> + ); }; diff --git a/frontend/public/components/RBAC/rules.jsx b/frontend/public/components/RBAC/rules.jsx index 7ff1e285b1a..12426d94a01 100644 --- a/frontend/public/components/RBAC/rules.jsx +++ b/frontend/public/components/RBAC/rules.jsx @@ -7,101 +7,133 @@ import { RoleModel, ClusterRoleModel } from '../../models'; import { Kebab, EmptyBox, ResourceIcon } from '../utils'; import { confirmModal } from '../modals'; -export const RulesList = ({rules, name, namespace}) => _.isEmpty(rules) - ? - :
-
-
- Actions +export const RulesList = ({ rules, name, namespace }) => + _.isEmpty(rules) ? ( + + ) : ( +
+
+
Actions
+
API Groups
+
Resources
-
- API Groups +
+ {rules.map((rule, i) => ( +
+ +
+ ))}
-
- Resources -
-
-
- {rules.map((rule, i) =>
- -
)}
-
; + ); -const Actions = ({verbs}) => { +const Actions = ({ verbs }) => { let actions = []; - _.each(verbs, a => { + _.each(verbs, (a) => { if (a === '*') { actions =
All
; return false; } - actions.push(
{a}
); + actions.push( +
+ {a} +
, + ); }); return
{actions}
; }; -const Groups = ({apiGroups}) => { +const Groups = ({ apiGroups }) => { // defaults to [""] let groups = []; - _.each(apiGroups, g => { + _.each(apiGroups, (g) => { if (g === '*') { - groups =
* All
; + groups = ( +
+ * All +
+ ); return false; } - groups.push(
{g}
); + groups.push( +
+ {g} +
, + ); }); return
{groups}
; }; -const Resources = connect(({k8s}) => ({allModels: k8s.getIn(['RESOURCES', 'models'])}))( - ({resources, nonResourceURLs, allModels}) => { +const Resources = connect(({ k8s }) => ({ allModels: k8s.getIn(['RESOURCES', 'models']) }))( + ({ resources, nonResourceURLs, allModels }) => { let allResources = []; - resources && _.each([...new Set(resources)].sort(), r => { - if (r === '') { - return false; - } - if (r === '*') { - allResources = [All Resources]; - return false; - } - const base = r.split('/')[0]; - const kind = allModels.find(model => model.plural === base); + resources && + _.each([...new Set(resources)].sort(), (r) => { + if (r === '') { + return false; + } + if (r === '*') { + allResources = [ + + All Resources + , + ]; + return false; + } + const base = r.split('/')[0]; + const kind = allModels.find((model) => model.plural === base); - allResources.push( - {r} - ); - }); + allResources.push( + + {' '} + {r} + , + ); + }); if (nonResourceURLs && nonResourceURLs.length) { if (allResources.length) { allResources.push(
); } let URLs = []; - _.each(nonResourceURLs.sort(), r => { + _.each(nonResourceURLs.sort(), (r) => { if (r === '*') { - URLs = [
All Non-resource URLs
]; + URLs = [ +
+ All Non-resource URLs +
, + ]; return false; } - URLs.push(
{r}
); + URLs.push( +
+ {r} +
, + ); }); allResources.push.apply(allResources, URLs); } return
{allResources}
; - }); + }, +); const DeleteRule = (name, namespace, i) => ({ label: 'Delete Rule', - callback: () => confirmModal({ - title: 'Delete Rule', - message: `Are you sure you want to delete Rule #${i}?`, - btnText: 'Delete Rule', - executeFn: () => { - const kind = namespace ? RoleModel : ClusterRoleModel; - return k8sPatch(kind, {metadata: {name, namespace}}, [{ - op: 'remove', path: `/rules/${i}`, - }]); - }, - }), + callback: () => + confirmModal({ + title: 'Delete Rule', + message: `Are you sure you want to delete Rule #${i}?`, + btnText: 'Delete Rule', + executeFn: () => { + const kind = namespace ? RoleModel : ClusterRoleModel; + return k8sPatch(kind, { metadata: { name, namespace } }, [ + { + op: 'remove', + path: `/rules/${i}`, + }, + ]); + }, + }), }); // This page is temporarily disabled until we update the safe resources list. @@ -110,25 +142,27 @@ const DeleteRule = (name, namespace, i) => ({ // href: namespace ? `/k8s/ns/${namespace}/roles/${name}/${i}/edit` : `/k8s/cluster/clusterroles/${name}/${i}/edit`, // }); -const RuleKebab = ({name, namespace, i}) => { +const RuleKebab = ({ name, namespace, i }) => { const options = [ // EditRule, DeleteRule, - ].map(f => f(name, namespace, i)); + ].map((f) => f(name, namespace, i)); return ; }; -const Rule = ({resources, nonResourceURLs, verbs, apiGroups, name, namespace, i}) =>
-
- -
-
- -
-
- -
-
- +const Rule = ({ resources, nonResourceURLs, verbs, apiGroups, name, namespace, i }) => ( +
+
+ +
+
+ +
+
+ +
+
+ +
-
; +); diff --git a/frontend/public/components/about-modal.tsx b/frontend/public/components/about-modal.tsx index 6bed9a9f07a..e26cc7325e4 100644 --- a/frontend/public/components/about-modal.tsx +++ b/frontend/public/components/about-modal.tsx @@ -1,6 +1,12 @@ import * as React from 'react'; import * as _ from 'lodash-es'; -import { Alert, AboutModal as PfAboutModal, TextContent, TextList, TextListItem } from '@patternfly/react-core'; +import { + Alert, + AboutModal as PfAboutModal, + TextContent, + TextList, + TextListItem, +} from '@patternfly/react-core'; import { Link } from 'react-router-dom'; import { FLAGS } from '../const'; @@ -10,13 +16,19 @@ import { ExternalLink, Firehose } from './utils'; import { ClusterVersionModel } from '../models'; import { ClusterVersionKind, referenceForModel } from '../module/k8s'; import { k8sVersion } from '../module/status'; -import { hasAvailableUpdates, getK8sGitVersion, getOpenShiftVersion, getClusterID, getErrataLink } from '../module/k8s/cluster-settings'; +import { + hasAvailableUpdates, + getK8sGitVersion, + getOpenShiftVersion, + getClusterID, + getErrataLink, +} from '../module/k8s/cluster-settings'; -const AboutModalItems: React.FC = ({closeAboutModal, cv}) => { +const AboutModalItems: React.FC = ({ closeAboutModal, cv }) => { const [kubernetesVersion, setKubernetesVersion] = React.useState(''); React.useEffect(() => { k8sVersion() - .then(response => setKubernetesVersion(getK8sGitVersion(response) || '-')) + .then((response) => setKubernetesVersion(getK8sGitVersion(response) || '-')) .catch(() => setKubernetesVersion('unknown')); }, []); @@ -27,7 +39,20 @@ const AboutModalItems: React.FC = ({closeAboutModal, cv}) const errataLink = getErrataLink(clusterVersion); return ( - {clusterVersion && hasAvailableUpdates(clusterVersion) && Update available. View Cluster Settings} />} + {clusterVersion && hasAvailableUpdates(clusterVersion) && ( + + Update available.{' '} + + View Cluster Settings + + + } + /> + )} {openshiftVersion && ( @@ -35,26 +60,38 @@ const AboutModalItems: React.FC = ({closeAboutModal, cv}) OpenShift Version
{openshiftVersion}
- {errataLink &&
} + {errataLink && ( +
+ +
+ )}
)} Kubernetes Version - {kubernetesVersion} + + {kubernetesVersion} + {channel && ( Channel - {channel} + + {channel} + )} {clusterID && ( Cluster ID - {clusterID} + + {clusterID} + )} API Server - {window.SERVER_FLAGS.kubeAPIServerURL} + + {window.SERVER_FLAGS.kubeAPIServerURL} +
@@ -78,8 +115,12 @@ const AboutModal_: React.FC = (props) => { brandImageAlt={details.productName} noAboutModalBoxContentContainer={true} > - {!customBranding &&

OpenShift is Red Hat's container application platform that allows developers to quickly develop, host, - and scale applications in a cloud environment.

} + {!customBranding && ( +

+ OpenShift is Red Hat's container application platform that allows developers to + quickly develop, host, and scale applications in a cloud environment. +

+ )} @@ -99,5 +140,5 @@ type AboutModalItemsProps = { type AboutModalProps = { isOpen: boolean; closeAboutModal: () => void; - flags: {[key: string]: boolean}; + flags: { [key: string]: boolean }; }; diff --git a/frontend/public/components/alert-manager.tsx b/frontend/public/components/alert-manager.tsx index 19afb966a31..90c64d8af7c 100644 --- a/frontend/public/components/alert-manager.tsx +++ b/frontend/public/components/alert-manager.tsx @@ -7,88 +7,118 @@ import { PencilAltIcon } from '@patternfly/react-icons'; import { referenceForModel, K8sResourceKind } from '../module/k8s'; import { ListPage, DetailsPage, Table, TableRow, TableData } from './factory'; -import { SectionHeading, LabelList, navFactory, ResourceLink, Selector, Firehose, LoadingInline, pluralize } from './utils'; +import { + SectionHeading, + LabelList, + navFactory, + ResourceLink, + Selector, + Firehose, + LoadingInline, + pluralize, +} from './utils'; import { configureReplicaCountModal } from './modals'; import { AlertmanagerModel } from '../models'; const Details: React.SFC = (props) => { const alertManager = props.obj; - const {metadata, spec} = alertManager; + const { metadata, spec } = alertManager; const openReplicaCountModal = (event) => { event.preventDefault(); event.target.blur(); - configureReplicaCountModal({resourceKind: AlertmanagerModel, resource: alertManager}); + configureReplicaCountModal({ resourceKind: AlertmanagerModel, resource: alertManager }); }; - return
-
- -
-
-
-
Name
-
{metadata.name}
-
Labels
-
- {spec.nodeSelector &&
Alert Manager Node Selector
} - {spec.nodeSelector &&
} -
-
-
-
-
Version
-
{spec.version}
-
Replicas
-
- -
-
+ return ( +
+
+ +
+
+
+
Name
+
{metadata.name}
+
Labels
+
+ +
+ {spec.nodeSelector &&
Alert Manager Node Selector
} + {spec.nodeSelector && ( +
+ +
+ )} +
+
+
+
+
Version
+
{spec.version}
+
Replicas
+
+ +
+
+
-
; + ); }; -const {details, editYaml} = navFactory; +const { details, editYaml } = navFactory; -export const AlertManagersDetailsPage = props => ; +export const AlertManagersDetailsPage = (props) => ( + +); const AlertManagersNameList = (props) => { if (props.loadError) { return null; } - return
-
-
- {!props.loaded - ? - : _.map(props.alertmanagers.data, (alertManager, i) =>
- -
)} + return ( +
+
+
+ {!props.loaded ? ( + + ) : ( + _.map(props.alertmanagers.data, (alertManager, i) => ( +
+ +
+ )) + )} +
-
; + ); }; -export const AlertManagersListContainer = props => - -; +export const AlertManagersListContainer = (props) => ( + + + +); const tableColumnClasses = [ classNames('col-md-2', 'col-sm-3', 'col-xs-6'), @@ -98,12 +128,22 @@ const tableColumnClasses = [ classNames('col-md-3', 'col-sm-3', 'hidden-xs'), ]; -const AlertManagerTableRow: React.FC = ({obj: alertManager, index, key, style}) => { - const {metadata, spec} = alertManager; +const AlertManagerTableRow: React.FC = ({ + obj: alertManager, + index, + key, + style, +}) => { + const { metadata, spec } = alertManager; return ( - + @@ -111,9 +151,7 @@ const AlertManagerTableRow: React.FC = ({obj: alertMa - - {spec.version} - + {spec.version} @@ -131,32 +169,57 @@ type AlertManagerTableRowProps = { const AlertManagerTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Version', sortField: 'spec.version', transforms: [sortable], + title: 'Version', + sortField: 'spec.version', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Node Selector', sortField: 'spec.nodeSelector', transforms: [sortable], + title: 'Node Selector', + sortField: 'spec.nodeSelector', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, ]; }; AlertManagerTableHeader.displayName = 'AlertManagerTableHeader'; -export const AlertManagersList = props =>
; +export const AlertManagersList = (props) => ( +
+); -export const AlertManagersPage = props => ; +export const AlertManagersPage = (props) => ( + +); type DetailsProps = { obj: K8sResourceKind; diff --git a/frontend/public/components/api-explorer.tsx b/frontend/public/components/api-explorer.tsx index 7d978eda87e..52e80a68c38 100644 --- a/frontend/public/components/api-explorer.tsx +++ b/frontend/public/components/api-explorer.tsx @@ -64,26 +64,42 @@ const getAPIResourceLink = (activeNamespace: string, model: K8sKind) => { return `/api-resource/ns/${activeNamespace}/${ref}`; }; -const APIResourceLink_: React.FC = ({activeNamespace, model}) => { +const APIResourceLink_: React.FC = ({ + activeNamespace, + model, +}) => { const to = getAPIResourceLink(activeNamespace, model); - return - - {model.kind} - ; + return ( + + + + + + {model.kind} + + + ); }; -const APIResourceLink = connect(mapStateToProps)(APIResourceLink_); +const APIResourceLink = connect( + mapStateToProps, +)(APIResourceLink_); const EmptyAPIResourcesMsg: React.FC<{}> = () => ; -const Group: React.FC<{value: string}> = ({value}) => { +const Group: React.FC<{ value: string }> = ({ value }) => { if (!value) { return <>-; } const [first, ...rest] = value.split('.'); - return _.isEmpty(rest) - ? <>{value} - : <>{first}.{rest.join('.')}; + return _.isEmpty(rest) ? ( + <>{value} + ) : ( + <> + {first} + .{rest.join('.')} + + ); }; const tableClasses = [ @@ -94,53 +110,73 @@ const tableClasses = [ 'col-lg-3 col-md-5 hidden-sm hidden-xs', ]; -const APIResourceHeader = () => [{ - title: 'Kind', - sortField: 'kind', - transforms: [sortable], - props: { className: tableClasses[0] }, -}, { - title: 'Group', - sortField: 'apiGroup', - transforms: [sortable], - props: { className: tableClasses[1] }, -}, { - title: 'Version', - sortField: 'apiVersion', - transforms: [sortable], - props: { className: tableClasses[2] }, -}, { - title: 'Namespaced', - sortField: 'namespaced', - transforms: [sortable], - props: { className: tableClasses[3] }, -}, { - title: 'Description', - props: { className: tableClasses[4] }, -}]; - -const APIResourceRows = ({componentProps: {data}}) => _.map(data, (model: K8sKind) => [{ - title: , - props: { className: tableClasses[0] }, -}, { - title: , - props: { className: tableClasses[1] }, -}, { - title: model.apiVersion, - props: { className: tableClasses[2] }, -}, { - title: model.namespaced ? 'true' : 'false', - props: { className: tableClasses[3] }, -}, { - title:
{getResourceDescription(model)}
, - props: { className: tableClasses[4] }, -}]); - -const stateToProps = ({k8s}) => ({ +const APIResourceHeader = () => [ + { + title: 'Kind', + sortField: 'kind', + transforms: [sortable], + props: { className: tableClasses[0] }, + }, + { + title: 'Group', + sortField: 'apiGroup', + transforms: [sortable], + props: { className: tableClasses[1] }, + }, + { + title: 'Version', + sortField: 'apiVersion', + transforms: [sortable], + props: { className: tableClasses[2] }, + }, + { + title: 'Namespaced', + sortField: 'namespaced', + transforms: [sortable], + props: { className: tableClasses[3] }, + }, + { + title: 'Description', + props: { className: tableClasses[4] }, + }, +]; + +const APIResourceRows = ({ componentProps: { data } }) => + _.map(data, (model: K8sKind) => [ + { + title: , + props: { className: tableClasses[0] }, + }, + { + title: ( + + + + ), + props: { className: tableClasses[1] }, + }, + { + title: model.apiVersion, + props: { className: tableClasses[2] }, + }, + { + title: model.namespaced ? 'true' : 'false', + props: { className: tableClasses[3] }, + }, + { + title:
{getResourceDescription(model)}
, + props: { className: tableClasses[4] }, + }, + ]); + +const stateToProps = ({ k8s }) => ({ models: k8s.getIn(['RESOURCES', 'models']), }); -const APIResourcesList = compose(withRouter, connect(stateToProps))(({models, location}) => { +const APIResourcesList = compose( + withRouter, + connect(stateToProps), +)(({ models, location }) => { const ALL = '#all#'; const GROUP_PARAM = 'g'; const VERSION_PARAM = 'v'; @@ -154,14 +190,17 @@ const APIResourcesList = compose(withRouter, connect = models.reduce((result: Set, {apiGroup}) => { + const groups: Set = models.reduce((result: Set, { apiGroup }) => { return apiGroup ? result.add(apiGroup) : result; }, new Set()); const sortedGroups: string[] = [...groups].sort(); - const groupOptions = sortedGroups.reduce((result, group: string) => { - result[group] = ; - return result; - }, {[ALL]: 'All Groups', '': 'No Group'}); + const groupOptions = sortedGroups.reduce( + (result, group: string) => { + result[group] = ; + return result; + }, + { [ALL]: 'All Groups', '': 'No Group' }, + ); const groupSpacer = new Set(); if (sortedGroups.length) { @@ -173,14 +212,17 @@ const APIResourcesList = compose(withRouter, connect = models.reduce((result: Set, {apiVersion}) => { + const versions: Set = models.reduce((result: Set, { apiVersion }) => { return result.add(apiVersion); }, new Set()); const sortedVersions: string[] = [...versions].sort(); - const versionOptions = sortedVersions.reduce((result, version: string) => { - result[version] = version; - return result; - }, {[ALL]: 'All Versions'}); + const versionOptions = sortedVersions.reduce( + (result, version: string) => { + result[version] = version; + return result; + }, + { [ALL]: 'All Versions' }, + ); const versionSpacer = new Set(); if (sortedVersions.length) { @@ -189,13 +231,13 @@ const APIResourcesList = compose(withRouter, connect(['cluster']); // filter by group, version, or text - const filteredResources = models.filter(({kind, apiGroup, apiVersion, namespaced}) => { + const filteredResources = models.filter(({ kind, apiGroup, apiVersion, namespaced }) => { if (groupFilter !== ALL && (apiGroup || '') !== groupFilter) { return false; } @@ -220,7 +262,11 @@ const APIResourcesList = compose(withRouter, connect apiGroup || '1', 'apiVersion', 'kind']); + const sortedResources = _.sortBy(filteredResources.toArray(), [ + ({ apiGroup }) => apiGroup || '1', + 'apiVersion', + 'kind', + ]); const updateURL = (k: string, v: string) => { if (v === ALL) { @@ -240,71 +286,75 @@ const APIResourcesList = compose(withRouter, connect -
-
- - - + return ( + <> +
+
+ + + +
+
+ setTextFilter(e.target.value)} + /> +
-
- setTextFilter(e.target.value)} +
+
- -
-
- - ; + + ); }); APIResourcesList.displayName = 'APIResourcesList'; -export const APIExplorerPage: React.FC<{}> = () => <> - - Explore API Resources - -
-

Explore API Resources

-
- -; +export const APIExplorerPage: React.FC<{}> = () => ( + <> + + Explore API Resources + +
+

Explore API Resources

+
+ + +); APIExplorerPage.displayName = 'APIExplorerPage'; -const APIResourceOverview: React.FC = ({customData: {kindObj}}) => { - const {kind, apiGroup, apiVersion, namespaced, verbs, shortNames} = kindObj; +const APIResourceOverview: React.FC = ({ customData: { kindObj } }) => { + const { kind, apiGroup, apiVersion, namespaced, verbs, shortNames } = kindObj; const description = getResourceDescription(kindObj); return (
@@ -332,7 +382,9 @@ const APIResourceOverview: React.FC = ({customData: {kindOb {description && ( <>
Description
-
{description}
+
+ {description} +
)} @@ -340,8 +392,8 @@ const APIResourceOverview: React.FC = ({customData: {kindOb ); }; -const scrollTop = () => document.getElementById('content-scrollable').scrollTop = 0; -const APIResourceSchema: React.FC = ({customData: {kindObj}}) => { +const scrollTop = () => (document.getElementById('content-scrollable').scrollTop = 0); +const APIResourceSchema: React.FC = ({ customData: { kindObj } }) => { return (
@@ -349,45 +401,70 @@ const APIResourceSchema: React.FC = ({customData: {kindObj} ); }; -const APIResourceInstances: React.FC = ({customData: {kindObj, namespace}}) => { - const componentLoader = resourceListPages.get(referenceForModel(kindObj), () => Promise.resolve(DefaultPage)); +const APIResourceInstances: React.FC = ({ + customData: { kindObj, namespace }, +}) => { + const componentLoader = resourceListPages.get(referenceForModel(kindObj), () => + Promise.resolve(DefaultPage), + ); const ns = kindObj.namespaced ? namespace : undefined; - return ; + return ( + + ); }; -const Subject: React.FC<{value: string}> = ({value}) => { +const Subject: React.FC<{ value: string }> = ({ value }) => { const [first, ...rest] = value.split(':'); - return first === 'system' && !_.isEmpty(rest) - ? ( - <> - {first}: - {rest.join(':')} - - ) - : <>{value}; + return first === 'system' && !_.isEmpty(rest) ? ( + <> + {first}: + {rest.join(':')} + + ) : ( + <>{value} + ); }; -const AccessTableHeader = () => [{ - title: 'Subject', - sortField: 'name', - transforms: [sortable], -}, { - title: 'Type', - sortField: 'type', - transforms: [sortable], -}]; - -const AccessTableRows = ({componentProps: {data}}) => _.map(data, (subject) => [{ - title: , -}, { - title: subject.type, -}]); +const AccessTableHeader = () => [ + { + title: 'Subject', + sortField: 'name', + transforms: [sortable], + }, + { + title: 'Type', + sortField: 'type', + transforms: [sortable], + }, +]; + +const AccessTableRows = ({ componentProps: { data } }) => + _.map(data, (subject) => [ + { + title: ( + + + + ), + }, + { + title: subject.type, + }, + ]); const EmptyAccessReviewMsg: React.FC<{}> = () => ; -const APIResourceAccessReview: React.FC = ({customData: {kindObj, namespace}}) => { - const {apiGroup, apiVersion, namespaced, plural, verbs} = kindObj; +const APIResourceAccessReview: React.FC = ({ + customData: { kindObj, namespace }, +}) => { + const { apiGroup, apiVersion, namespaced, plural, verbs } = kindObj; // state const [verb, setVerb] = React.useState(_.first(verbs)); @@ -401,7 +478,9 @@ const APIResourceAccessReview: React.FC = ({customData: {ki // perform the access review React.useEffect(() => { setError(null); - const accessReviewModel = namespace ? LocalResourceAccessReviewsModel : ResourceAccessReviewsModel; + const accessReviewModel = namespace + ? LocalResourceAccessReviewsModel + : ResourceAccessReviewsModel; const req: ResourceAccessReviewRequest = { apiVersion: apiVersionForModel(accessReviewModel), kind: accessReviewModel.kind, @@ -417,7 +496,13 @@ const APIResourceAccessReview: React.FC = ({customData: {ki }, [apiGroup, apiVersion, plural, namespace, verb]); if (error) { - return ; + return ( + + ); } if (!accessResponse) { @@ -429,9 +514,9 @@ const APIResourceAccessReview: React.FC = ({customData: {ki const serviceAccounts = []; _.each(accessResponse.users, (name: string) => { if (name.startsWith('system:serviceaccount:')) { - serviceAccounts.push({name, type: 'ServiceAccount'}); + serviceAccounts.push({ name, type: 'ServiceAccount' }); } else { - users.push({name, type: 'User'}); + users.push({ name, type: 'User' }); } }); const groups = _.map(accessResponse.groups, (name: string) => ({ name, type: 'Group' })); @@ -446,11 +531,12 @@ const APIResourceAccessReview: React.FC = ({customData: {ki const allSelected = showUsers && showGroups && showServiceAccounts; const itemCount = accessResponse.users.length + accessResponse.groups.length; const selectedCount = data.length; - const filteredData = data.filter(({name}: {name: string}) => fuzzy(filter, name)); + const filteredData = data.filter(({ name }: { name: string }) => fuzzy(filter, name)); const sortedData = _.orderBy(filteredData, ['type', 'name'], ['asc', 'asc']); // event handlers - const onFilterChange: React.ReactEventHandler = (e) => setFilter(e.currentTarget.value); + const onFilterChange: React.ReactEventHandler = (e) => + setFilter(e.currentTarget.value); const toggleShowUsers = (e: React.MouseEvent) => { e.preventDefault(); setShowUsers(!showUsers); @@ -482,11 +568,7 @@ const APIResourceAccessReview: React.FC = ({customData: {ki />
- +
@@ -496,16 +578,30 @@ const APIResourceAccessReview: React.FC = ({customData: {ki selectedCount={selectedCount} onSelectAll={onSelectAll} > - - - + + +

The following subjects can {verb} {plural} {namespaced && namespace && <> in namespace {namespace}} {namespaced && !namespace && <> in all namespaces} - {namespaced && <> at the cluster scope} - . + {namespaced && <> at the cluster scope}.

= ({customData: {ki ); }; -const APIResourcePage_ = ({match, kindObj, kindsInFlight, flags}: {match: any, kindObj: K8sKind, kindsInFlight: boolean, flags: {[key: string]: boolean}}) => { +const APIResourcePage_ = ({ + match, + kindObj, + kindsInFlight, + flags, +}: { + match: any; + kindObj: K8sKind; + kindsInFlight: boolean; + flags: { [key: string]: boolean }; +}) => { if (!kindObj) { - return kindsInFlight - ? - :
+ return kindsInFlight ? ( + + ) : ( +

404: Not Found

-
; +
+ ); } - const breadcrumbs = [{ - name: 'Explore', - path: '/api-explorer', - }, { - name: 'Resource Details', - path: match.url, - }]; - - const pages = [{ - href: '', - name: 'Overview', - component: APIResourceOverview, - }, { - href: 'schema', - name: 'Schema', - component: APIResourceSchema, - }]; + const breadcrumbs = [ + { + name: 'Explore', + path: '/api-explorer', + }, + { + name: 'Resource Details', + path: match.url, + }, + ]; + + const pages = [ + { + href: '', + name: 'Overview', + component: APIResourceOverview, + }, + { + href: 'schema', + name: 'Schema', + component: APIResourceSchema, + }, + ]; if (_.isEmpty(kindObj.verbs) || kindObj.verbs.includes('list')) { pages.push({ @@ -566,17 +680,21 @@ const APIResourcePage_ = ({match, kindObj, kindsInFlight, flags}: {match: any, k const namespace = kindObj.namespaced ? match.params.ns : null; - return <> - - - {kindObj.label} - -
- -

{kindObj.label}

-
- - ; + return ( + <> + + + {kindObj.label} + +
+ +

+ {kindObj.label} +

+
+ + + ); }; export const APIResourcePage = connectToModel(connectToFlags(FLAGS.OPENSHIFT)(APIResourcePage_)); diff --git a/frontend/public/components/app-contents.tsx b/frontend/public/components/app-contents.tsx index 14af1369800..4c13c6bc294 100644 --- a/frontend/public/components/app-contents.tsx +++ b/frontend/public/components/app-contents.tsx @@ -19,12 +19,9 @@ import { getActivePerspective } from '../reducers/ui'; import { RootState } from '../redux'; //PF4 Imports -import { - PageSection, - PageSectionVariants, -} from '@patternfly/react-core'; +import { PageSection, PageSectionVariants } from '@patternfly/react-core'; -const RedirectComponent = props => { +const RedirectComponent = (props) => { const to = `/k8s${props.location.pathname}`; return ; }; @@ -43,44 +40,42 @@ function NamespaceFromURL(Component) { } const namespacedRoutes = []; -_.each(namespacedPrefixes, p => { +_.each(namespacedPrefixes, (p) => { namespacedRoutes.push(`${p}/ns/:ns`); namespacedRoutes.push(`${p}/all-namespaces`); }); type DefaultPageProps = { activePerspective: string; - flags: {[key: string]: boolean}; + flags: { [key: string]: boolean }; }; // The default page component lets us connect to flags without connecting the entire App. const DefaultPage_: React.FC = ({ flags, activePerspective }) => { - if (Object.keys(flags).some(key => flagPending(flags[key]))) { + if (Object.keys(flags).some((key) => flagPending(flags[key]))) { return ; } // support redirecting to perspective landing page return flags[FLAGS.OPENSHIFT] ? ( p.properties.id === activePerspective) - .properties.getLandingPageURL(flags) - } + to={plugins.registry + .getPerspectives() + .find((p) => p.properties.id === activePerspective) + .properties.getLandingPageURL(flags)} /> ) : ( p.properties.id === activePerspective) - .properties.getK8sLandingPageURL(flags) - } + to={plugins.registry + .getPerspectives() + .find((p) => p.properties.id === activePerspective) + .properties.getK8sLandingPageURL(flags)} /> ); }; const DefaultPage = connect((state: RootState) => ({ activePerspective: getActivePerspective(state), -}))( - connectToFlags(FLAGS.OPENSHIFT, FLAGS.CAN_LIST_NS)(DefaultPage_), -); +}))(connectToFlags(FLAGS.OPENSHIFT, FLAGS.CAN_LIST_NS)(DefaultPage_)); const LazyRoute = (props) => ( // use `withRouter` to force a re-render when routes change since we are using React.memo const AppContents = connect((state: RootState) => ({ activePerspective: getActivePerspective(state), -}))(withRouter(React.memo(({activePerspective}) => ( - -
- - -
- - {getPageRouteExtensions(activePerspective)} - - - import('./dashboards-page/dashboards' /* webpackChunkName: "dashboards" */).then(m => m.DashboardsPage)} /> - - {/* Redirect legacy routes to avoid breaking links */} - - - - - - - - - import('./api-explorer' /* webpackChunkName: "api-explorer" */).then(m => m.APIExplorerPage)} /> - import('./api-explorer' /* webpackChunkName: "api-explorer" */).then(m => m.APIResourcePage)} /> - import('./api-explorer' /* webpackChunkName: "api-explorer" */).then(m => NamespaceFromURL(m.APIResourcePage))} /> - import('./api-explorer' /* webpackChunkName: "api-explorer" */).then(m => NamespaceFromURL(m.APIResourcePage))} /> - - import('./command-line-tools' /* webpackChunkName: "command-line-tools" */).then(m => m.CommandLineToolsPage)} /> - - - - import('./catalog/catalog-page' /* webpackChunkName: "catalog" */).then(m => m.CatalogPage)} /> - import('./catalog/catalog-page' /* webpackChunkName: "catalog" */).then(m => m.CatalogPage)} /> - - - import('./provisioned-services' /* webpackChunkName: "provisionedservices" */).then(m => m.ProvisionedServicesPage)} /> - import('./provisioned-services' /* webpackChunkName: "provisionedservices" */).then(m => m.ProvisionedServicesPage)} /> - - - import('./broker-management' /* webpackChunkName: "brokermanagment" */).then(m => m.BrokerManagementPage)} /> - - import('./service-catalog/create-instance' /* webpackChunkName: "create-service-instance" */).then(m => m.CreateInstancePage)} /> - import('./service-catalog/create-binding' /* webpackChunkName: "create-binding" */).then(m => m.CreateBindingPage)} /> - import('./instantiate-template' /* webpackChunkName: "instantiate-template" */).then(m => m.InstantiateTemplatePage)} /> - - } /> - - import('./events' /* webpackChunkName: "events" */).then(m => NamespaceFromURL(m.EventStreamPage))} /> - import('./events' /* webpackChunkName: "events" */).then(m => NamespaceFromURL(m.EventStreamPage))} /> - - - - - import('./import-yaml' /* webpackChunkName: "import-yaml" */).then(m => NamespaceFromURL(m.ImportYamlPage))} /> - import('./import-yaml' /* webpackChunkName: "import-yaml" */).then(m => NamespaceFromURL(m.ImportYamlPage))} /> - - { - // These pages are temporarily disabled. We need to update the safe resources list. - // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> - // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> - } - - { - // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> - // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> - } - - import('./secrets/create-secret' /* webpackChunkName: "create-secret" */).then(m => m.CreateSecret)} /> - import('./secrets/create-secret' /* webpackChunkName: "create-secret" */).then(m => m.EditSecret)} /> - import('./create-yaml').then(m => m.EditYAMLPage)} /> - - import('./routes/create-route' /* webpackChunkName: "create-route" */).then(m => m.CreateRoute)} /> - - import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.CreateRoleBinding)} kind="RoleBinding" /> - import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.CreateRoleBinding)} kind="RoleBinding" /> - import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.CopyRoleBinding)} /> - import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRoleBinding)} /> - import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.CopyRoleBinding)} /> - import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRoleBinding)} /> - import('./storage/attach-storage' /* webpackChunkName: "attach-storage" */).then(m => m.AttachStorage)} /> - - import('./storage/create-pvc' /* webpackChunkName: "create-pvc" */).then(m => m.CreatePVC)} /> - - import('./monitoring' /* webpackChunkName: "monitoring" */).then(m => m.MonitoringUI)} /> - import('./monitoring' /* webpackChunkName: "monitoring" */).then(m => m.MonitoringUI)} /> - import('./monitoring' /* webpackChunkName: "monitoring" */).then(m => m.MonitoringUI)} /> - import('./monitoring' /* webpackChunkName: "monitoring" */).then(m => m.MonitoringUI)} /> - import('./monitoring' /* webpackChunkName: "monitoring" */).then(m => m.MonitoringUI)} /> - - import('./cluster-settings/github-idp-form' /* webpackChunkName: "github-idp-form" */).then(m => m.AddGitHubPage)} /> - import('./cluster-settings/gitlab-idp-form' /* webpackChunkName: "gitlab-idp-form" */).then(m => m.AddGitLabPage)} /> - import('./cluster-settings/google-idp-form' /* webpackChunkName: "google-idp-form" */).then(m => m.AddGooglePage)} /> - import('./cluster-settings/htpasswd-idp-form' /* webpackChunkName: "htpasswd-idp-form" */).then(m => m.AddHTPasswdPage)} /> - import('./cluster-settings/keystone-idp-form' /* webpackChunkName: "keystone-idp-form" */).then(m => m.AddKeystonePage)} /> - import('./cluster-settings/ldap-idp-form' /* webpackChunkName: "ldap-idp-form" */).then(m => m.AddLDAPPage)} /> - import('./cluster-settings/openid-idp-form' /* webpackChunkName: "openid-idp-form" */).then(m => m.AddOpenIDPage)} /> - import('./cluster-settings/basicauth-idp-form' /* webpackChunkName: "basicauth-idp-form" */).then(m => m.AddBasicAuthPage)} /> - import('./cluster-settings/request-header-idp-form' /* webpackChunkName: "request-header-idp-form" */).then(m => m.AddRequestHeaderPage)} /> - import('./cluster-settings/cluster-settings' /* webpackChunkName: "cluster-settings" */).then(m => m.ClusterSettingsPage)} /> - - import('./storage-class-form' /* webpackChunkName: "storage-class-form" */).then(m => m.StorageClassForm)} /> - - - import('./create-yaml' /* webpackChunkName: "create-yaml" */).then(m => m.CreateYAML)} /> - - import('./container').then(m => m.ContainersDetailsPage)} /> - import('./create-yaml' /* webpackChunkName: "create-yaml" */).then(m => NamespaceFromURL(m.CreateYAML))} /> - - - - - - - import('./error' /* webpackChunkName: "error" */).then(m => m.ErrorPage)} /> - - - import('./error' /* webpackChunkName: "error" */).then(m => m.ErrorPage404)} /> - -
-
-
-)))); +}))( + withRouter( + React.memo(({ activePerspective }) => ( + +
+ + +
+ + {getPageRouteExtensions(activePerspective)} + + + + import('./dashboards-page/dashboards' /* webpackChunkName: "dashboards" */).then( + (m) => m.DashboardsPage, + ) + } + /> + + {/* Redirect legacy routes to avoid breaking links */} + + + + + + + + + + import('./api-explorer' /* webpackChunkName: "api-explorer" */).then( + (m) => m.APIExplorerPage, + ) + } + /> + + import('./api-explorer' /* webpackChunkName: "api-explorer" */).then( + (m) => m.APIResourcePage, + ) + } + /> + + import('./api-explorer' /* webpackChunkName: "api-explorer" */).then((m) => + NamespaceFromURL(m.APIResourcePage), + ) + } + /> + + import('./api-explorer' /* webpackChunkName: "api-explorer" */).then((m) => + NamespaceFromURL(m.APIResourcePage), + ) + } + /> + + + import('./command-line-tools' /* webpackChunkName: "command-line-tools" */).then( + (m) => m.CommandLineToolsPage, + ) + } + /> + + + + + import('./catalog/catalog-page' /* webpackChunkName: "catalog" */).then( + (m) => m.CatalogPage, + ) + } + /> + + import('./catalog/catalog-page' /* webpackChunkName: "catalog" */).then( + (m) => m.CatalogPage, + ) + } + /> + + + + import( + './provisioned-services' /* webpackChunkName: "provisionedservices" */ + ).then((m) => m.ProvisionedServicesPage) + } + /> + + import( + './provisioned-services' /* webpackChunkName: "provisionedservices" */ + ).then((m) => m.ProvisionedServicesPage) + } + /> + + + + import('./broker-management' /* webpackChunkName: "brokermanagment" */).then( + (m) => m.BrokerManagementPage, + ) + } + /> + + + import( + './service-catalog/create-instance' /* webpackChunkName: "create-service-instance" */ + ).then((m) => m.CreateInstancePage) + } + /> + + import( + './service-catalog/create-binding' /* webpackChunkName: "create-binding" */ + ).then((m) => m.CreateBindingPage) + } + /> + + import( + './instantiate-template' /* webpackChunkName: "instantiate-template" */ + ).then((m) => m.InstantiateTemplatePage) + } + /> + + ( + + )} + /> + + + import('./events' /* webpackChunkName: "events" */).then((m) => + NamespaceFromURL(m.EventStreamPage), + ) + } + /> + + import('./events' /* webpackChunkName: "events" */).then((m) => + NamespaceFromURL(m.EventStreamPage), + ) + } + /> + + + + + + import('./import-yaml' /* webpackChunkName: "import-yaml" */).then((m) => + NamespaceFromURL(m.ImportYamlPage), + ) + } + /> + + import('./import-yaml' /* webpackChunkName: "import-yaml" */).then((m) => + NamespaceFromURL(m.ImportYamlPage), + ) + } + /> + + { + // These pages are temporarily disabled. We need to update the safe resources list. + // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> + // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> + } + + { + // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> + // import('./RBAC' /* webpackChunkName: "rbac" */).then(m => m.EditRulePage)} /> + } + + + import('./secrets/create-secret' /* webpackChunkName: "create-secret" */).then( + (m) => m.CreateSecret, + ) + } + /> + + import('./secrets/create-secret' /* webpackChunkName: "create-secret" */).then( + (m) => m.EditSecret, + ) + } + /> + import('./create-yaml').then((m) => m.EditYAMLPage)} + /> + + + import('./routes/create-route' /* webpackChunkName: "create-route" */).then( + (m) => m.CreateRoute, + ) + } + /> + + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CreateRoleBinding) + } + kind="RoleBinding" + /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CreateRoleBinding) + } + kind="RoleBinding" + /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CopyRoleBinding) + } + /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.EditRoleBinding) + } + /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.CopyRoleBinding) + } + /> + + import('./RBAC' /* webpackChunkName: "rbac" */).then((m) => m.EditRoleBinding) + } + /> + + import('./storage/attach-storage' /* webpackChunkName: "attach-storage" */).then( + (m) => m.AttachStorage, + ) + } + /> + + + import('./storage/create-pvc' /* webpackChunkName: "create-pvc" */).then( + (m) => m.CreatePVC, + ) + } + /> + + + import('./monitoring' /* webpackChunkName: "monitoring" */).then( + (m) => m.MonitoringUI, + ) + } + /> + + import('./monitoring' /* webpackChunkName: "monitoring" */).then( + (m) => m.MonitoringUI, + ) + } + /> + + import('./monitoring' /* webpackChunkName: "monitoring" */).then( + (m) => m.MonitoringUI, + ) + } + /> + + import('./monitoring' /* webpackChunkName: "monitoring" */).then( + (m) => m.MonitoringUI, + ) + } + /> + + import('./monitoring' /* webpackChunkName: "monitoring" */).then( + (m) => m.MonitoringUI, + ) + } + /> + + + import( + './cluster-settings/github-idp-form' /* webpackChunkName: "github-idp-form" */ + ).then((m) => m.AddGitHubPage) + } + /> + + import( + './cluster-settings/gitlab-idp-form' /* webpackChunkName: "gitlab-idp-form" */ + ).then((m) => m.AddGitLabPage) + } + /> + + import( + './cluster-settings/google-idp-form' /* webpackChunkName: "google-idp-form" */ + ).then((m) => m.AddGooglePage) + } + /> + + import( + './cluster-settings/htpasswd-idp-form' /* webpackChunkName: "htpasswd-idp-form" */ + ).then((m) => m.AddHTPasswdPage) + } + /> + + import( + './cluster-settings/keystone-idp-form' /* webpackChunkName: "keystone-idp-form" */ + ).then((m) => m.AddKeystonePage) + } + /> + + import( + './cluster-settings/ldap-idp-form' /* webpackChunkName: "ldap-idp-form" */ + ).then((m) => m.AddLDAPPage) + } + /> + + import( + './cluster-settings/openid-idp-form' /* webpackChunkName: "openid-idp-form" */ + ).then((m) => m.AddOpenIDPage) + } + /> + + import( + './cluster-settings/basicauth-idp-form' /* webpackChunkName: "basicauth-idp-form" */ + ).then((m) => m.AddBasicAuthPage) + } + /> + + import( + './cluster-settings/request-header-idp-form' /* webpackChunkName: "request-header-idp-form" */ + ).then((m) => m.AddRequestHeaderPage) + } + /> + + import( + './cluster-settings/cluster-settings' /* webpackChunkName: "cluster-settings" */ + ).then((m) => m.ClusterSettingsPage) + } + /> + + + import('./storage-class-form' /* webpackChunkName: "storage-class-form" */).then( + (m) => m.StorageClassForm, + ) + } + /> + + + + import('./create-yaml' /* webpackChunkName: "create-yaml" */).then( + (m) => m.CreateYAML, + ) + } + /> + + import('./container').then((m) => m.ContainersDetailsPage)} + /> + + import('./create-yaml' /* webpackChunkName: "create-yaml" */).then((m) => + NamespaceFromURL(m.CreateYAML), + ) + } + /> + + + + + + + + import('./error' /* webpackChunkName: "error" */).then((m) => m.ErrorPage) + } + /> + + + + import('./error' /* webpackChunkName: "error" */).then((m) => m.ErrorPage404) + } + /> + +
+
+
+ )), + ), +); export default AppContents; diff --git a/frontend/public/components/app.jsx b/frontend/public/components/app.jsx index 9e9be9c79a6..2dab3cf8b6d 100644 --- a/frontend/public/components/app.jsx +++ b/frontend/public/components/app.jsx @@ -69,14 +69,13 @@ class App extends React.PureComponent { } _onNavToggle() { - // Some components, like svg charts, need to reflow when nav is toggled. // Fire event after a short delay to allow nav animation to complete. setTimeout(() => { window.dispatchEvent(new Event('nav_toggle')); }, 100); - this.setState(prevState => { + this.setState((prevState) => { return { isNavOpen: !prevState.isNavOpen, }; @@ -86,14 +85,14 @@ class App extends React.PureComponent { _onNavSelect() { //close nav on mobile nav selects if (!this._isDesktop()) { - this.setState({isNavOpen: false}); + this.setState({ isNavOpen: false }); } } _onResize() { const isDesktop = this._isDesktop(); if (this.previousDesktopState !== isDesktop) { - this.setState({isNavOpen: isDesktop}); + this.setState({ isNavOpen: isDesktop }); this.previousDesktopState = isDesktop; } } @@ -104,14 +103,17 @@ class App extends React.PureComponent { return ( - + } - sidebar={} + sidebar={ + + } > @@ -124,13 +126,15 @@ class App extends React.PureComponent { const startDiscovery = () => store.dispatch(watchAPIServices()); // Load cached API resources from localStorage to speed up page load. -getCachedResources().then(resources => { - if (resources) { - store.dispatch(receivedResources(resources)); - } - // Still perform discovery to refresh the cache. - startDiscovery(); -}).catch(startDiscovery); +getCachedResources() + .then((resources) => { + if (resources) { + store.dispatch(receivedResources(resources)); + } + // Still perform discovery to refresh the cache. + startDiscovery(); + }) + .catch(startDiscovery); store.dispatch(detectFeatures()); @@ -142,27 +146,41 @@ fetchSwagger(); // Used by GUI tests to check for unhandled exceptions window.windowError = false; -window.onerror = window.onunhandledrejection = () => window.windowError = true; +window.onerror = window.onunhandledrejection = () => (window.windowError = true); if ('serviceWorker' in navigator) { if (window.SERVER_FLAGS.loadTestFactor > 1) { // eslint-disable-next-line import/no-unresolved import('file-loader?name=load-test.sw.js!../load-test.sw.js') .then(() => navigator.serviceWorker.register('/load-test.sw.js')) - .then(() => new Promise(r => navigator.serviceWorker.controller ? r() : navigator.serviceWorker.addEventListener('controllerchange', () => r()))) - .then(() => navigator.serviceWorker.controller.postMessage({topic: 'setFactor', value: window.SERVER_FLAGS.loadTestFactor})); + .then( + () => + new Promise((r) => + navigator.serviceWorker.controller + ? r() + : navigator.serviceWorker.addEventListener('controllerchange', () => r()), + ), + ) + .then(() => + navigator.serviceWorker.controller.postMessage({ + topic: 'setFactor', + value: window.SERVER_FLAGS.loadTestFactor, + }), + ); } else { - navigator.serviceWorker.getRegistrations() - .then((registrations) => registrations.forEach(reg => reg.unregister())) + navigator.serviceWorker + .getRegistrations() + .then((registrations) => registrations.forEach((reg) => reg.unregister())) // eslint-disable-next-line no-console - .catch(e => console.warn('Error unregistering service workers', e)); + .catch((e) => console.warn('Error unregistering service workers', e)); } } -render(( +render( - -), document.getElementById('app')); + , + document.getElementById('app'), +); diff --git a/frontend/public/components/broker-management.tsx b/frontend/public/components/broker-management.tsx index dc8cf5b5b81..66e92fd9e9c 100644 --- a/frontend/public/components/broker-management.tsx +++ b/frontend/public/components/broker-management.tsx @@ -1,23 +1,27 @@ import * as React from 'react'; -import {HorizontalNav, PageHeading} from './utils'; -import {ClusterServiceBrokerPage} from './cluster-service-broker'; -import {ClusterServiceClassPage} from './cluster-service-class'; +import { HorizontalNav, PageHeading } from './utils'; +import { ClusterServiceBrokerPage } from './cluster-service-broker'; +import { ClusterServiceClassPage } from './cluster-service-class'; -const pages = [{ - href: '', - name: 'Service Brokers', - component: ClusterServiceBrokerPage, -}, { - href: 'serviceclasses', - name: 'Service Classes', - component: ClusterServiceClassPage, -}]; +const pages = [ + { + href: '', + name: 'Service Brokers', + component: ClusterServiceBrokerPage, + }, + { + href: 'serviceclasses', + name: 'Service Classes', + component: ClusterServiceClassPage, + }, +]; -export const BrokerManagementPage: React.SFC = ({match}) => +export const BrokerManagementPage: React.SFC = ({ match }) => ( - ; + +); export type BrokerManagementPageProps = { match: any; diff --git a/frontend/public/components/build-config.tsx b/frontend/public/components/build-config.tsx index e88dba8ea69..6ccc9d81b58 100644 --- a/frontend/public/components/build-config.tsx +++ b/frontend/public/components/build-config.tsx @@ -30,12 +30,15 @@ const BuildConfigsReference: K8sResourceKindReference = 'BuildConfig'; const startBuildAction: KebabAction = (kind, buildConfig) => ({ label: 'Start Build', - callback: () => startBuild(buildConfig).then(build => { - history.push(resourceObjPath(build, referenceFor(build))); - }).catch(err => { - const error = err.message; - errorModal({error}); - }), + callback: () => + startBuild(buildConfig) + .then((build) => { + history.push(resourceObjPath(build, referenceFor(build))); + }) + .catch((err) => { + const error = err.message; + errorModal({ error }); + }), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -52,23 +55,31 @@ const menuActions = [ ...Kebab.factory.common, ]; -export const BuildConfigsDetails: React.SFC = ({obj: buildConfig}) => -
- -
-
- -
-
- +export const BuildConfigsDetails: React.SFC = ({ obj: buildConfig }) => ( + +
+ +
+
+ +
+
+ +
-
- - -; - -const BuildsTabPage = ({obj: buildConfig}) => ; + + + +); + +const BuildsTabPage = ({ obj: buildConfig }) => ( + +); const pages = [ navFactory.details(BuildConfigsDetails), @@ -78,12 +89,9 @@ const pages = [ navFactory.events(ResourceEventStream), ]; -export const BuildConfigsDetailsPage: React.SFC = props => - ; +export const BuildConfigsDetailsPage: React.SFC = (props) => ( + +); BuildConfigsDetailsPage.displayName = 'BuildConfigsDetailsPage'; const tableColumnClasses = [ @@ -97,33 +105,47 @@ const tableColumnClasses = [ const BuildConfigsTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: '', props: { className: tableColumnClasses[4] }, + title: '', + props: { className: tableColumnClasses[4] }, }, ]; }; BuildConfigsTableHeader.displayName = 'BuildConfigsTableHeader'; -const BuildConfigsTableRow: React.FC = ({obj, index, key, style}) => { +const BuildConfigsTableRow: React.FC = ({ obj, index, key, style }) => { return ( - + @@ -132,7 +154,7 @@ const BuildConfigsTableRow: React.FC = ({obj, index, - { fromNow(obj.metadata.creationTimestamp) } + {fromNow(obj.metadata.creationTimestamp)} @@ -148,24 +170,40 @@ type BuildConfigsTableRowProps = { style: object; }; -const buildStrategy = (buildConfig: K8sResourceKind): BuildStrategyType => buildConfig.spec.strategy.type; +const buildStrategy = (buildConfig: K8sResourceKind): BuildStrategyType => + buildConfig.spec.strategy.type; -const allStrategies = [BuildStrategyType.Docker, BuildStrategyType.JenkinsPipeline, BuildStrategyType.Source, BuildStrategyType.Custom]; -const filters = [{ - type: 'build-strategy', - selected: allStrategies, - reducer: buildStrategy, - items: _.map(allStrategies, strategy => ({ - id: strategy, - title: strategy, - })), -}]; +const allStrategies = [ + BuildStrategyType.Docker, + BuildStrategyType.JenkinsPipeline, + BuildStrategyType.Source, + BuildStrategyType.Custom, +]; +const filters = [ + { + type: 'build-strategy', + selected: allStrategies, + reducer: buildStrategy, + items: _.map(allStrategies, (strategy) => ({ + id: strategy, + title: strategy, + })), + }, +]; -export const BuildConfigsList: React.SFC = props =>
; +export const BuildConfigsList: React.SFC = (props) => ( +
+); BuildConfigsList.displayName = 'BuildConfigsList'; -export const BuildConfigsPage: React.SFC = props => +export const BuildConfigsPage: React.SFC = (props) => ( = props => ListComponent={BuildConfigsList} canCreate={true} filterLabel={props.filterLabel} - rowFilters={filters} />; + rowFilters={filters} + /> +); BuildConfigsPage.displayName = 'BuildConfigsListPage'; export type BuildConfigsDetailsProps = { diff --git a/frontend/public/components/build-logs.jsx b/frontend/public/components/build-logs.jsx index 6e0a8a67a2a..2eb82968919 100644 --- a/frontend/public/components/build-logs.jsx +++ b/frontend/public/components/build-logs.jsx @@ -1,6 +1,12 @@ import * as _ from 'lodash-es'; import * as React from 'react'; -import { LOG_SOURCE_RUNNING, LOG_SOURCE_TERMINATED, LOG_SOURCE_WAITING, MsgBox, ResourceLog } from './utils'; +import { + LOG_SOURCE_RUNNING, + LOG_SOURCE_TERMINATED, + LOG_SOURCE_WAITING, + MsgBox, + ResourceLog, +} from './utils'; import { getJenkinsLogURL, BuildPipelineLogLink } from './build-pipeline'; import { BuildStrategyType } from './build'; import { BuildPhase } from '../module/k8s/builds'; @@ -11,17 +17,18 @@ const PipelineLogMessage = ({ build }) => { ? 'Pipeline build logs are available through Jenkins (linked below)' : 'A link to the Jenkins pipeline build logs will appear below when the build starts'; - const detail = -

{message}

- -
; + const detail = ( + +

{message}

+ +
+ ); return ; }; const buildPhaseToLogSourceStatus = (phase) => { switch (phase) { - case BuildPhase.New: case BuildPhase.Pending: return LOG_SOURCE_WAITING; @@ -48,21 +55,21 @@ export class BuildLogs extends React.Component { static getDerivedStateFromProps({ obj: build }, { status: prevStatus }) { const phase = _.get(build, 'status.phase'); const status = buildPhaseToLogSourceStatus(phase); - return prevStatus !== status ? {status} : null; + return prevStatus !== status ? { status } : null; } render() { const { obj: build } = this.props; const isPipeline = _.get(build, 'spec.strategy.type') === BuildStrategyType.JenkinsPipeline; - return
- { isPipeline - ? - : - } -
; + return ( +
+ {isPipeline ? ( + + ) : ( + + )} +
+ ); } } diff --git a/frontend/public/components/build-pipeline.tsx b/frontend/public/components/build-pipeline.tsx index 5c582d72283..459c49cab5f 100644 --- a/frontend/public/components/build-pipeline.tsx +++ b/frontend/public/components/build-pipeline.tsx @@ -1,20 +1,13 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import * as _ from 'lodash-es'; -import { - BanIcon, - PendingIcon, - SyncAltIcon, -} from '@patternfly/react-icons'; +import { BanIcon, PendingIcon, SyncAltIcon } from '@patternfly/react-icons'; import { resourcePath, ExternalLink } from './utils'; import { fromNow } from './utils/datetime'; import { K8sResourceKind } from '../module/k8s'; import { getBuildNumber } from '../module/k8s/builds'; -import { - GreenCheckCircleIcon, - RedExclamationCircleIcon, -} from '@console/shared'; +import { GreenCheckCircleIcon, RedExclamationCircleIcon } from '@console/shared'; const getStages = (status): any[] => (status && status.stages) || []; const getJenkinsStatus = (resource: K8sResourceKind) => { @@ -27,65 +20,74 @@ const getJenkinsStatus = (resource: K8sResourceKind) => { return _.isError(status) ? {} : status; }; -export const getJenkinsLogURL = (resource: K8sResourceKind): string => _.get(resource, ['metadata', 'annotations', 'openshift.io/jenkins-console-log-url']); -export const getJenkinsBuildURL = (resource: K8sResourceKind): string => _.get(resource, ['metadata', 'annotations', 'openshift.io/jenkins-build-uri']); +export const getJenkinsLogURL = (resource: K8sResourceKind): string => + _.get(resource, ['metadata', 'annotations', 'openshift.io/jenkins-console-log-url']); +export const getJenkinsBuildURL = (resource: K8sResourceKind): string => + _.get(resource, ['metadata', 'annotations', 'openshift.io/jenkins-build-uri']); const BuildSummaryStatusIcon: React.SFC = ({ status }) => { const statusClass = _.lowerCase(status); - const icon = ({ + const icon = { new: '', pending: , running: , complete: , failed: , cancelled: , - })[statusClass]; + }[statusClass]; - return icon - ? + return icon ? ( + {icon} - : null; + ) : null; }; export const BuildPipelineLogLink: React.SFC = ({ obj }) => { const link = getJenkinsLogURL(obj); - return link - ? - : null; + return link ? ( + + ) : null; }; -const StagesNotStarted: React.SFC = () =>
- No stages have started. -
; +const StagesNotStarted: React.SFC = () => ( +
No stages have started.
+); -const BuildSummaryTimestamp: React.SFC = ({ timestamp }) => - {fromNow(timestamp)} -; +const BuildSummaryTimestamp: React.SFC = ({ timestamp }) => ( + {fromNow(timestamp)} +); const BuildPipelineSummary: React.SFC = ({ obj }) => { const { name, namespace } = obj.metadata; const buildNumber = getBuildNumber(obj); const path: string = resourcePath(obj.kind, name, namespace); - return
-
- Build {buildNumber} + return ( +
+
+ {' '} + + Build {buildNumber} + +
+ +
- - -
; -}; - -const BuildAnimation: React.SFC = ({ status }) =>
-
-
-
-
-
-
+ ); +}; + +const BuildAnimation: React.SFC = ({ status }) => ( +
+
+
+
+
+
+
+
-
; +); const JenkinsInputUrl: React.SFC = ({ obj, stage }) => { const pending = stage.status === 'PAUSED_PENDING_INPUT'; @@ -95,45 +97,55 @@ const JenkinsInputUrl: React.SFC = ({ obj, stage }) => { } const buildUrl = getJenkinsBuildURL(obj); - return
- -
; + return ( +
+ +
+ ); }; -const BuildStageTimestamp: React.SFC = ({ timestamp }) =>
- {fromNow(timestamp)} -
; +const BuildStageTimestamp: React.SFC = ({ timestamp }) => ( +
{fromNow(timestamp)}
+); const BuildStageName: React.SFC = ({ name }) => { - return
- {name} -
; + return ( +
+ {name} +
+ ); }; const BuildStage: React.SFC = ({ obj, stage }) => { - return
-
- - - - + return ( +
+
+ + + + +
-
; + ); }; export const BuildPipeline: React.SFC = ({ obj }) => { const jenkinsStatus: any = getJenkinsStatus(obj); const stages = getStages(jenkinsStatus); - return
- -
-
- {_.isEmpty(stages) - ? - : stages.map(stage => )} + return ( +
+ +
+
+ {_.isEmpty(stages) ? ( + + ) : ( + stages.map((stage) => ) + )} +
-
; + ); }; export type BuildPipelineProps = { diff --git a/frontend/public/components/build.tsx b/frontend/public/components/build.tsx index 1d145a75173..e5e8fec393c 100644 --- a/frontend/public/components/build.tsx +++ b/frontend/public/components/build.tsx @@ -5,7 +5,13 @@ import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; import { Status } from '@console/shared'; -import { K8sResourceKindReference, referenceFor, K8sResourceKind, k8sPatch, K8sKind } from '../module/k8s'; +import { + K8sResourceKindReference, + referenceFor, + K8sResourceKind, + k8sPatch, + K8sKind, +} from '../module/k8s'; import { cloneBuild, formatBuildDuration, getBuildNumber } from '../module/k8s/builds'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { errorModal, confirmModal } from './modals'; @@ -38,12 +44,15 @@ const BuildsReference: K8sResourceKindReference = 'Build'; const CloneBuildAction: KebabAction = (kind: K8sKind, build: K8sResourceKind) => ({ label: 'Rebuild', - callback: () => cloneBuild(build).then(clone => { - history.push(resourceObjPath(clone, referenceFor(clone))); - }).catch(err => { - const error = err.message; - errorModal({ error }); - }), + callback: () => + cloneBuild(build) + .then((clone) => { + history.push(resourceObjPath(clone, referenceFor(clone))); + }) + .catch((err) => { + const error = err.message; + errorModal({ error }); + }), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -56,16 +65,19 @@ const CloneBuildAction: KebabAction = (kind: K8sKind, build: K8sResourceKind) => const CancelAction: KebabAction = (kind: K8sKind, build: K8sResourceKind) => ({ label: 'Cancel Build', - hidden: build.status.phase !== 'Running' && build.status.phase !== 'Pending' && build.status.phase !== 'New', - callback: () => confirmModal({ - title: 'Cancel build', - message: 'Are you sure you want to cancel this build?', - btnText: 'Yes, cancel', - cancelText: 'No, don\'t cancel', - executeFn: () => k8sPatch(kind, - build, - [{ op: 'add', path: '/status/cancelled', value: true }]), - }), + hidden: + build.status.phase !== 'Running' && + build.status.phase !== 'Pending' && + build.status.phase !== 'New', + callback: () => + confirmModal({ + title: 'Cancel build', + message: 'Are you sure you want to cancel this build?', + btnText: 'Yes, cancel', + cancelText: "No, don't cancel", + executeFn: () => + k8sPatch(kind, build, [{ op: 'add', path: '/status/cancelled', value: true }]), + }), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -89,27 +101,29 @@ export enum BuildStrategyType { Source = 'Source', } -export const BuildLogLink = ({build}) => { - const {metadata: {name, namespace}} = build; +export const BuildLogLink = ({ build }) => { + const { + metadata: { name, namespace }, + } = build; const isPipeline = _.get(build, 'spec.strategy.type') === BuildStrategyType.JenkinsPipeline; - return isPipeline - ? - : - View Logs - ; + return isPipeline ? ( + + ) : ( + View Logs + ); }; -export const BuildNumberLink = ({build}) => { - const {metadata: {name, namespace}} = build; +export const BuildNumberLink = ({ build }) => { + const { + metadata: { name, namespace }, + } = build; const buildNumber = getBuildNumber(build); const title = _.isFinite(buildNumber) ? `#${buildNumber}` : name; - return - {title} - ; + return {title}; }; -const BuildGraphs = requirePrometheus(({build}) => { +const BuildGraphs = requirePrometheus(({ build }) => { const podName = _.get(build, ['metadata', 'annotations', 'openshift.io/build.pod-name']); if (!podName) { return null; @@ -117,35 +131,37 @@ const BuildGraphs = requirePrometheus(({build}) => { const namespace = build.metadata.namespace; - return -
-
- -
-
- -
-
- + return ( + +
+
+ +
+
+ +
+
+ +
-
-
- ; +
+ + ); }); export const BuildsDetails: React.SFC = ({ obj: build }) => { @@ -154,40 +170,54 @@ export const BuildsDetails: React.SFC = ({ obj: build }) => const duration = formatBuildDuration(build); const hasPipeline = build.spec.strategy.type === BuildStrategyType.JenkinsPipeline; - return -
- - - {hasPipeline &&
-
- -
-
} -
-
- - {triggeredBy &&
Triggered By
} - {triggeredBy &&
{triggeredBy}
} - {startTimestamp &&
Started
} - {startTimestamp &&
} -
-
-
- -
Status
-
- {logSnippet &&
Log Snippet
} - {logSnippet &&
{logSnippet}
} - {message &&
Reason
} - {message &&
{message}
} - {duration &&
Duration
} - {duration &&
{duration}
} -
+ return ( + +
+ + + {hasPipeline && ( +
+
+ +
+
+ )} +
+
+ + {triggeredBy &&
Triggered By
} + {triggeredBy &&
{triggeredBy}
} + {startTimestamp &&
Started
} + {startTimestamp && ( +
+ +
+ )} +
+
+
+ +
Status
+
+ +
+ {logSnippet &&
Log Snippet
} + {logSnippet && ( +
+
{logSnippet}
+
+ )} + {message &&
Reason
} + {message &&
{message}
} + {duration &&
Duration
} + {duration &&
{duration}
} +
+
-
- - ; + + + ); }; export const getStrategyType = (strategy: BuildStrategyType) => { @@ -210,24 +240,34 @@ export const getEnvPath = (props) => { return strategyType ? ['spec', 'strategy', strategyType] : null; }; -const EnvironmentPage = (props) => import('./environment.jsx').then(c => c.EnvironmentPage)} {...props} />; +const EnvironmentPage = (props) => ( + import('./environment.jsx').then((c) => c.EnvironmentPage)} + {...props} + /> +); export const BuildEnvironmentComponent = (props) => { - const {obj} = props; + const { obj } = props; const readOnly = obj.kind === 'Build'; const envPath = getEnvPath(props); if (envPath) { - return ; + return ( + + ); } - return
-
The environment variable editor does not support build - strategy: {obj.spec.strategy.type}. + return ( +
+
+ The environment variable editor does not support build strategy: {obj.spec.strategy.type}. +
-
; + ); }; const pages = [ @@ -238,12 +278,9 @@ const pages = [ navFactory.events(ResourceEventStream), ]; -export const BuildsDetailsPage: React.SFC = props => - ; +export const BuildsDetailsPage: React.SFC = (props) => ( + +); BuildsDetailsPage.displayName = 'BuildsDetailsPage'; const tableColumnClasses = [ @@ -257,33 +294,47 @@ const tableColumnClasses = [ const BuildsTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Status', sortField: 'status.phase', transforms: [sortable], + title: 'Status', + sortField: 'status.phase', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: '', props: { className: tableColumnClasses[4] }, + title: '', + props: { className: tableColumnClasses[4] }, }, ]; }; BuildsTableHeader.displayName = 'BuildsTableHeader'; -const BuildsTableRow: React.FC = ({obj, index, key, style}) => { +const BuildsTableRow: React.FC = ({ obj, index, key, style }) => { return ( - + @@ -308,24 +359,34 @@ type BuildsTableRowProps = { style: object; }; -export const BuildsList: React.SFC = props =>
; +export const BuildsList: React.SFC = (props) => ( +
+); BuildsList.displayName = 'BuildsList'; -export const buildPhase = build => build.status.phase; +export const buildPhase = (build) => build.status.phase; const allPhases = ['New', 'Pending', 'Running', 'Complete', 'Failed', 'Error', 'Cancelled']; -const filters = [{ - type: 'build-status', - selected: allPhases, - reducer: buildPhase, - items: _.map(allPhases, phase => ({ - id: phase, - title: phase, - })), -}]; +const filters = [ + { + type: 'build-status', + selected: allPhases, + reducer: buildPhase, + items: _.map(allPhases, (phase) => ({ + id: phase, + title: phase, + })), + }, +]; -export const BuildsPage: React.SFC = props => +export const BuildsPage: React.SFC = (props) => ( = props => ListComponent={BuildsList} canCreate={false} rowFilters={filters} - />; + /> +); BuildsPage.displayName = 'BuildsListPage'; export type BuildsDetailsProps = { - obj: any, + obj: any; }; export type BuildsPageProps = { - showTitle?: boolean, - namespace?: string, - selector?: any, + showTitle?: boolean; + namespace?: string; + selector?: any; }; export type BuildsDetailsPageProps = { - match: any, + match: any; }; diff --git a/frontend/public/components/catalog/catalog-item-details.jsx b/frontend/public/components/catalog/catalog-item-details.jsx index 4de7f891283..2d8e3b40f65 100644 --- a/frontend/public/components/catalog/catalog-item-details.jsx +++ b/frontend/public/components/catalog/catalog-item-details.jsx @@ -1,14 +1,14 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import * as PropTypes from 'prop-types'; -import {Link} from 'react-router-dom'; -import {Modal} from 'patternfly-react'; -import {CatalogItemHeader, PropertiesSidePanel, PropertyItem} from 'patternfly-react-extensions'; +import { Link } from 'react-router-dom'; +import { Modal } from 'patternfly-react'; +import { CatalogItemHeader, PropertiesSidePanel, PropertyItem } from 'patternfly-react-extensions'; -import {normalizeIconClass} from './catalog-item-icon'; -import {ClusterServicePlanModel} from '../../models'; -import {k8sGet} from '../../module/k8s'; -import {Timestamp, ExternalLink} from '../utils'; +import { normalizeIconClass } from './catalog-item-icon'; +import { ClusterServicePlanModel } from '../../models'; +import { k8sGet } from '../../module/k8s'; +import { Timestamp, ExternalLink } from '../utils'; export class CatalogTileDetails extends React.Component { state = { @@ -23,17 +23,19 @@ export class CatalogTileDetails extends React.Component { } getPlans(obj) { - k8sGet(ClusterServicePlanModel, null, null, {queryParams: { fieldSelector: `spec.clusterServiceClassRef.name=${obj.metadata.name}`} }) - .then(({items: plans}) => { - this.setState({ - plans: _.orderBy(plans, ['spec.externalMetadata.displayName', 'metadata.name']), - }); + k8sGet(ClusterServicePlanModel, null, null, { + queryParams: { fieldSelector: `spec.clusterServiceClassRef.name=${obj.metadata.name}` }, + }).then(({ items: plans }) => { + this.setState({ + plans: _.orderBy(plans, ['spec.externalMetadata.displayName', 'metadata.name']), }); + }); } render() { const { closeOverlay } = this.props; - const { obj, + const { + obj, kind, tileName, tileImgUrl, @@ -44,7 +46,8 @@ export class CatalogTileDetails extends React.Component { supportUrl, longDescription, documentationUrl, - sampleRepo } = this.props.item; + sampleRepo, + } = this.props.item; const { plans } = this.state; const vendor = tileProvider ? `Provided by ${tileProvider}` : null; @@ -52,9 +55,19 @@ export class CatalogTileDetails extends React.Component { const creationTimestamp = _.get(obj, 'metadata.creationTimestamp'); const supportUrlLink = ; - const documentationUrlLink = ; - const sampleRepoLink = ; - const planItems = _.map(plans, plan =>
  • {plan.spec.description || plan.spec.externalName}
  • ); + const documentationUrlLink = ( + + ); + const sampleRepoLink = ( + + ); + const planItems = _.map(plans, (plan) => ( +
  • {plan.spec.description || plan.spec.externalName}
  • + )); return ( @@ -64,45 +77,82 @@ export class CatalogTileDetails extends React.Component { title={tileName} vendor={vendor} iconClass={iconClass} - iconImg={tileImgUrl} /> + iconImg={tileImgUrl} + />
    - {this.props.item.createLabel} + + {this.props.item.createLabel} + {tileProvider && } {supportUrl && } - {creationTimestamp && } />} + {creationTimestamp && ( + } + /> + )}
    {tileDescription &&

    {tileDescription}

    } {longDescription &&

    {longDescription}

    } {sampleRepo &&

    Sample repository: {sampleRepoLink}

    } - {documentationUrl && -

    Documentation

    -

    {documentationUrlLink}

    -
    } - {!_.isEmpty(plans) && -

    Service Plans

    -
      - {planItems} -
    -
    } - {kind === 'ImageStream' && -
    -

    - The following resources will be created: -

    -
      -
    • A build config to build source from a Git repository.
    • -
    • An image stream to track built images.
    • -
    • A deployment config to rollout new revisions when the image changes.
    • -
    • A service to expose your workload inside the cluster.
    • -
    • An optional route to expose your workload outside the cluster.
    • -
    -
    } + {documentationUrl && ( + +

    Documentation

    +

    {documentationUrlLink}

    +
    + )} + {!_.isEmpty(plans) && ( + +

    Service Plans

    +
      {planItems}
    +
    + )} + {kind === 'ImageStream' && ( + +
    +

    The following resources will be created:

    +
      +
    • + A{' '} + build config{' '} + to build source from a Git repository. +
    • +
    • + An{' '} + image stream{' '} + to track built images. +
    • +
    • + A{' '} + + deployment config + {' '} + to rollout new revisions when the image changes. +
    • +
    • + A service to + expose your workload inside the cluster. +
    • +
    • + An optional{' '} + route to + expose your workload outside the cluster. +
    • +
    +
    + )}
    diff --git a/frontend/public/components/catalog/catalog-item-icon.tsx b/frontend/public/components/catalog/catalog-item-icon.tsx index ad5f2c8ebf3..3162d1dcae2 100644 --- a/frontend/public/components/catalog/catalog-item-icon.tsx +++ b/frontend/public/components/catalog/catalog-item-icon.tsx @@ -189,7 +189,11 @@ export const getImageForIconClass = (iconClass: string): string => { }; export const getServiceClassIcon = (serviceClass: K8sResourceKind): string => { - return _.get(serviceClass, ['spec', 'externalMetadata', 'console.openshift.io/iconClass'], logos.get('icon-catalog')); + return _.get( + serviceClass, + ['spec', 'externalMetadata', 'console.openshift.io/iconClass'], + logos.get('icon-catalog'), + ); }; export const getServiceClassImage = (serviceClass: K8sResourceKind): string => { @@ -206,14 +210,33 @@ export const getTemplateIcon = (template: TemplateKind): string => { return _.get(template, 'metadata.annotations.iconClass'); }; -export const ClusterServiceClassIcon: React.FC = ({serviceClass, iconSize}) => { +export const ClusterServiceClassIcon: React.FC = ({ + serviceClass, + iconSize, +}) => { const iconClass = getServiceClassIcon(serviceClass); const imageUrl = getServiceClassImage(serviceClass); - return - { imageUrl - ? - : } - ; + return ( + + {imageUrl ? ( + + ) : ( + + )} + + ); }; ClusterServiceClassIcon.displayName = 'ClusterServiceClassIcon'; @@ -222,14 +245,30 @@ export type ClusterServiceClassIconProps = { iconSize?: string; }; -export const ImageStreamIcon: React.FC = ({tag, iconSize}) => { +export const ImageStreamIcon: React.FC = ({ tag, iconSize }) => { const iconClass = getImageStreamIcon(tag); const iconClassImg = getImageForIconClass(iconClass); - return - { iconClassImg - ? - : } - ; + return ( + + {iconClassImg ? ( + + ) : ( + + )} + + ); }; export type ImageStreamIconProps = { diff --git a/frontend/public/components/catalog/catalog-items.jsx b/frontend/public/components/catalog/catalog-items.jsx index d9726fc6e0d..0e92e8c3e8a 100644 --- a/frontend/public/components/catalog/catalog-items.jsx +++ b/frontend/public/components/catalog/catalog-items.jsx @@ -16,14 +16,19 @@ export const catalogCategories = { label: 'Languages', field: 'tags', subcategories: { - java: {id: 'java', label: 'Java', values: ['java']}, - javascript: {id: 'javascript', label: 'JavaScript', field: 'tags', values: ['javascript', 'nodejs', 'js']}, - dotnet: {id: 'dotnet', label: '.NET', field: 'tags', values: ['dotnet']}, - perl: {id: 'perl', label: 'Perl', field: 'tags', values: ['perl']}, - ruby: {id: 'ruby', label: 'Ruby', field: 'tags', values: ['ruby']}, - php: {id: 'php', label: 'PHP', field: 'tags', values: ['php']}, - python: {id: 'python', label: 'Python', field: 'tags', values: ['python']}, - golang: {id: 'golang', label: 'Go', field: 'tags', values: ['golang', 'go']}, + java: { id: 'java', label: 'Java', values: ['java'] }, + javascript: { + id: 'javascript', + label: 'JavaScript', + field: 'tags', + values: ['javascript', 'nodejs', 'js'], + }, + dotnet: { id: 'dotnet', label: '.NET', field: 'tags', values: ['dotnet'] }, + perl: { id: 'perl', label: 'Perl', field: 'tags', values: ['perl'] }, + ruby: { id: 'ruby', label: 'Ruby', field: 'tags', values: ['ruby'] }, + php: { id: 'php', label: 'PHP', field: 'tags', values: ['php'] }, + python: { id: 'python', label: 'Python', field: 'tags', values: ['python'] }, + golang: { id: 'golang', label: 'Go', field: 'tags', values: ['golang', 'go'] }, }, }, databases: { @@ -31,10 +36,10 @@ export const catalogCategories = { label: 'Databases', field: 'tags', subcategories: { - mongodb: {id: 'mongodb', label: 'Mongo', field: 'tags', values: ['mongodb']}, - mysql: {id: 'mysql', label: 'MySQL', field: 'tags', values: ['mysql']}, - postgresql: {id: 'postgresql', label: 'Postgres', field: 'tags', values: ['postgresql']}, - mariadb: {id: 'mariadb', label: 'MariaDB', field: 'tags', values: ['mariadb']}, + mongodb: { id: 'mongodb', label: 'Mongo', field: 'tags', values: ['mongodb'] }, + mysql: { id: 'mysql', label: 'MySQL', field: 'tags', values: ['mysql'] }, + postgresql: { id: 'postgresql', label: 'Postgres', field: 'tags', values: ['postgresql'] }, + mariadb: { id: 'mariadb', label: 'MariaDB', field: 'tags', values: ['mariadb'] }, }, }, middleware: { @@ -42,10 +47,30 @@ export const catalogCategories = { label: 'Middleware', field: 'tags', subcategories: { - integration: {id: 'integration', label: 'Integration', field: 'tags', values: ['amq', 'fuse', 'jboss-fuse', 'sso', '3scale']}, - processAutomation: {id: 'processAutomation', label: 'Process Automation', field: 'tags', values: ['decisionserver', 'processserver']}, - analyticsData: {id: 'analyticsData', label: 'Analytics & Data', field: 'tags', values: ['datagrid', 'datavirt']}, - runtimes: {id: 'runtimes', label: 'Runtimes & Frameworks', field: 'tags', values: ['eap', 'httpd', 'tomcat']}, + integration: { + id: 'integration', + label: 'Integration', + field: 'tags', + values: ['amq', 'fuse', 'jboss-fuse', 'sso', '3scale'], + }, + processAutomation: { + id: 'processAutomation', + label: 'Process Automation', + field: 'tags', + values: ['decisionserver', 'processserver'], + }, + analyticsData: { + id: 'analyticsData', + label: 'Analytics & Data', + field: 'tags', + values: ['datagrid', 'datavirt'], + }, + runtimes: { + id: 'runtimes', + label: 'Runtimes & Frameworks', + field: 'tags', + values: ['eap', 'httpd', 'tomcat'], + }, }, }, cicd: { @@ -53,8 +78,8 @@ export const catalogCategories = { label: 'CI/CD', field: 'tags', subcategories: { - jenkins: {id: 'jenkins', label: 'Jenkins', field: 'tags', values: ['jenkins']}, - pipelines: {id: 'pipelines', label: 'Pipelines', field: 'tags', values: ['pipelines']}, + jenkins: { id: 'jenkins', label: 'Jenkins', field: 'tags', values: ['jenkins'] }, + pipelines: { id: 'pipelines', label: 'Pipelines', field: 'tags', values: ['pipelines'] }, }, }, virtualization: { @@ -62,20 +87,19 @@ export const catalogCategories = { label: 'Virtualization', field: 'tags', subcategories: { - vms: {id: 'vms', label: 'Virtual Machines', field: 'tags', values: ['virtualmachine']}, + vms: { id: 'vms', label: 'Virtual Machines', field: 'tags', values: ['virtualmachine'] }, }, }, }; -const pageDescription = 'Add shared apps, services, or source-to-image builders to your project from the Developer ' + +const pageDescription = + 'Add shared apps, services, or source-to-image builders to your project from the Developer ' + 'Catalog. Cluster admins can install additional apps which will show up here automatically.'; // Filter property white list -const filterGroups = [ - 'kind', -]; +const filterGroups = ['kind']; -const getAvailableFilters = initialFilters => { +const getAvailableFilters = (initialFilters) => { const filters = _.cloneDeep(initialFilters); filters.kind = { ClusterServiceClass: { @@ -103,7 +127,6 @@ const getAvailableFilters = initialFilters => { return filters; }; - const filterGroupNameMap = { kind: 'Type', }; @@ -121,12 +144,14 @@ const keywordCompare = (filterString, item) => { return false; } - return item.tileName.toLowerCase().includes(filterString) || + return ( + item.tileName.toLowerCase().includes(filterString) || (item.tileDescription && item.tileDescription.toLowerCase().includes(filterString)) || - (item.tags && item.tags.includes(filterString)); + (item.tags && item.tags.includes(filterString)) + ); }; -const setURLParams = params => { +const setURLParams = (params) => { const url = new URL(window.location); const searchParams = `?${params.toString()}${url.hash}`; @@ -137,7 +162,7 @@ export class CatalogTileViewPage extends React.Component { constructor(props) { super(props); - this.state = {detailsItem: null}; + this.state = { detailsItem: null }; this.openOverlay = this.openOverlay.bind(this); this.closeOverlay = this.closeOverlay.bind(this); @@ -145,12 +170,13 @@ export class CatalogTileViewPage extends React.Component { } componentDidMount() { - const {items} = this.props; + const { items } = this.props; const searchParams = new URLSearchParams(window.location.search); const detailsItemID = searchParams.get('details-item'); - const detailsItem = detailsItemID && _.find(items, item => detailsItemID === _.get(item, 'obj.metadata.uid')); + const detailsItem = + detailsItemID && _.find(items, (item) => detailsItemID === _.get(item, 'obj.metadata.uid')); - this.setState({detailsItem}); + this.setState({ detailsItem }); } openOverlay(detailsItem) { @@ -158,7 +184,7 @@ export class CatalogTileViewPage extends React.Component { params.set('details-item', _.get(detailsItem, 'obj.metadata.uid')); setURLParams(params); - this.setState({detailsItem}); + this.setState({ detailsItem }); } closeOverlay() { @@ -166,7 +192,7 @@ export class CatalogTileViewPage extends React.Component { params.delete('details-item'); setURLParams(params); - this.setState({detailsItem: null}); + this.setState({ detailsItem: null }); } renderTile(item) { @@ -201,7 +227,9 @@ export class CatalogTileViewPage extends React.Component { _.sortBy(itemsToSort, ({tileName}) => tileName.toLowerCase())} + itemsSorter={(itemsToSort) => + _.sortBy(itemsToSort, ({ tileName }) => tileName.toLowerCase()) + } getAvailableCategories={() => catalogCategories} // TODO(alecmerdler): Dynamic filters for each Operator and its provided APIs getAvailableFilters={getAvailableFilters} @@ -213,8 +241,15 @@ export class CatalogTileViewPage extends React.Component { pageDescription={pageDescription} emptyStateInfo="No developer catalog items are being shown due to the filters being applied." /> - - {detailsItem && } + + {detailsItem && ( + + )} ); diff --git a/frontend/public/components/catalog/catalog-page.tsx b/frontend/public/components/catalog/catalog-page.tsx index 8aaf1f32b0c..9ce441646a2 100644 --- a/frontend/public/components/catalog/catalog-page.tsx +++ b/frontend/public/components/catalog/catalog-page.tsx @@ -4,15 +4,24 @@ import { Helmet } from 'react-helmet'; import { ANNOTATIONS, FLAGS } from '../../const'; import { CatalogTileViewPage } from './catalog-items'; -import { k8sListPartialMetadata, referenceForModel, serviceClassDisplayName, K8sResourceKind, PartialObjectMetadata } from '../../module/k8s'; +import { + k8sListPartialMetadata, + referenceForModel, + serviceClassDisplayName, + K8sResourceKind, + PartialObjectMetadata, +} from '../../module/k8s'; import { withStartGuide } from '../start-guide'; import { connectToFlags, flagPending } from '../../reducers/features'; -import { Firehose, LoadError, PageHeading, skeletonCatalog, StatusBox, FirehoseResult } from '../utils'; import { - getAnnotationTags, - getMostRecentBuilderTag, - isBuilder, -} from '../image-stream'; + Firehose, + LoadError, + PageHeading, + skeletonCatalog, + StatusBox, + FirehoseResult, +} from '../utils'; +import { getAnnotationTags, getMostRecentBuilderTag, isBuilder } from '../image-stream'; import { getImageForIconClass, getImageStreamIcon, @@ -28,26 +37,37 @@ export class CatalogListPage extends React.Component _.get(this.props, referenceForModel(properties.model))) - .map(({properties}) => properties.normalize(_.get(this.props, [referenceForModel(properties.model), 'data']))) + plugins.registry + .getDevCatalogModels() + .filter(({ properties }) => _.get(this.props, referenceForModel(properties.model))) + .map(({ properties }) => + properties.normalize(_.get(this.props, [referenceForModel(properties.model), 'data'])), + ), ); const { @@ -84,74 +104,98 @@ export class CatalogListPage extends React.Component { - // Prefer native templates to template-service-broker service classes. - if (serviceClass.status.removedFromBrokerCatalog || serviceClass.spec.clusterServiceBrokerName === 'template-service-broker') { + const { namespace = '' } = this.props; + return _.reduce( + serviceClasses, + (acc, serviceClass) => { + // Prefer native templates to template-service-broker service classes. + if ( + serviceClass.status.removedFromBrokerCatalog || + serviceClass.spec.clusterServiceBrokerName === 'template-service-broker' + ) { + return acc; + } + + const iconClass = getServiceClassIcon(serviceClass); + const tileImgUrl = getServiceClassImage(serviceClass); + + acc.push({ + obj: serviceClass, + kind: 'ClusterServiceClass', + tileName: serviceClassDisplayName(serviceClass), + tileIconClass: tileImgUrl ? null : iconClass, + tileImgUrl, + tileDescription: serviceClass.spec.description, + tileProvider: _.get(serviceClass, 'spec.externalMetadata.providerDisplayName'), + tags: serviceClass.spec.tags, + createLabel: 'Create Service Instance', + href: `/catalog/create-service-instance?cluster-service-class=${ + serviceClass.metadata.name + }&preselected-ns=${namespace}`, + supportUrl: _.get(serviceClass, 'spec.externalMetadata.supportUrl'), + longDescription: _.get(serviceClass, 'spec.externalMetadata.longDescription'), + documentationUrl: _.get(serviceClass, 'spec.externalMetadata.documentationUrl'), + }); return acc; - } - - const iconClass = getServiceClassIcon(serviceClass); - const tileImgUrl = getServiceClassImage(serviceClass); - - acc.push({ - obj: serviceClass, - kind: 'ClusterServiceClass', - tileName: serviceClassDisplayName(serviceClass), - tileIconClass: tileImgUrl ? null : iconClass, - tileImgUrl, - tileDescription: serviceClass.spec.description, - tileProvider: _.get(serviceClass, 'spec.externalMetadata.providerDisplayName'), - tags: serviceClass.spec.tags, - createLabel: 'Create Service Instance', - href: `/catalog/create-service-instance?cluster-service-class=${serviceClass.metadata.name}&preselected-ns=${namespace}`, - supportUrl: _.get(serviceClass, 'spec.externalMetadata.supportUrl'), - longDescription: _.get(serviceClass, 'spec.externalMetadata.longDescription'), - documentationUrl: _.get(serviceClass, 'spec.externalMetadata.documentationUrl'), - }); - return acc; - }, []); + }, + [], + ); } normalizeTemplates(templates) { - return _.reduce(templates, (acc, template) => { - const { name, namespace, annotations = {} } = template.metadata; - const tags = (annotations.tags || '').split(/\s*,\s*/); - if (tags.includes('hidden')) { + return _.reduce( + templates, + (acc, template) => { + const { name, namespace, annotations = {} } = template.metadata; + const tags = (annotations.tags || '').split(/\s*,\s*/); + if (tags.includes('hidden')) { + return acc; + } + const iconClass = getTemplateIcon(template); + const tileImgUrl = getImageForIconClass(iconClass); + const tileIconClass = tileImgUrl ? null : iconClass; + acc.push({ + obj: template, + kind: 'Template', + tileName: annotations[ANNOTATIONS.displayName] || name, + tileIconClass, + tileImgUrl, + tileDescription: annotations.description, + tags, + createLabel: 'Instantiate Template', + tileProvider: annotations[ANNOTATIONS.providerDisplayName], + documentationUrl: annotations[ANNOTATIONS.documentationURL], + supportUrl: annotations[ANNOTATIONS.supportURL], + href: `/catalog/instantiate-template?template=${name}&template-ns=${namespace}&preselected-ns=${this + .props.namespace || ''}`, + }); return acc; - } - const iconClass = getTemplateIcon(template); - const tileImgUrl = getImageForIconClass(iconClass); - const tileIconClass = tileImgUrl ? null : iconClass; - acc.push({ - obj: template, - kind: 'Template', - tileName: annotations[ANNOTATIONS.displayName] || name, - tileIconClass, - tileImgUrl, - tileDescription: annotations.description, - tags, - createLabel: 'Instantiate Template', - tileProvider: annotations[ANNOTATIONS.providerDisplayName], - documentationUrl: annotations[ANNOTATIONS.documentationURL], - supportUrl: annotations[ANNOTATIONS.supportURL], - href: `/catalog/instantiate-template?template=${name}&template-ns=${namespace}&preselected-ns=${this.props.namespace || ''}`, - }); - return acc; - }, []); + }, + [], + ); } normalizeImageStreams(imageStreams) { const builderimageStreams = _.filter(imageStreams, isBuilder); - return _.map(builderimageStreams, imageStream => { + return _.map(builderimageStreams, (imageStream) => { const { namespace: currentNamespace = '' } = this.props; const { name, namespace } = imageStream.metadata; const tag = getMostRecentBuilderTag(imageStream); - const tileName = _.get(imageStream, ['metadata', 'annotations', ANNOTATIONS.displayName]) || name; + const tileName = + _.get(imageStream, ['metadata', 'annotations', ANNOTATIONS.displayName]) || name; const iconClass = getImageStreamIcon(tag); const tileImgUrl = getImageForIconClass(iconClass); const tileIconClass = tileImgUrl ? null : iconClass; @@ -160,7 +204,7 @@ export class CatalogListPage extends React.Component - - ; + const { loaded, loadError } = this.props; + const { items } = this.state; + + return ( + + + + ); } } -export const Catalog = connectToFlags(FLAGS.OPENSHIFT, FLAGS.SERVICE_CATALOG, ...plugins.registry.getDevCatalogModels().map(({properties}) => properties.flag))((props) => { - const {flags, mock, namespace} = props; +export const Catalog = connectToFlags( + FLAGS.OPENSHIFT, + FLAGS.SERVICE_CATALOG, + ...plugins.registry.getDevCatalogModels().map(({ properties }) => properties.flag), +)((props) => { + const { flags, mock, namespace } = props; const openshiftFlag = flags[FLAGS.OPENSHIFT]; const serviceCatalogFlag = flags[FLAGS.SERVICE_CATALOG]; const [templateMetadata, setTemplateMetadata] = React.useState(); @@ -206,10 +262,12 @@ export const Catalog = connectToFlags(FLAGS.OPENSHIFT, FLAGS.SERVI if (!loadTemplates) { return; } - k8sListPartialMetadata(TemplateModel, {ns: 'openshift'}).then((metadata) => { - setTemplateMetadata(metadata); - setTemplateError(null); - }).catch(setTemplateError); + k8sListPartialMetadata(TemplateModel, { ns: 'openshift' }) + .then((metadata) => { + setTemplateMetadata(metadata); + setTemplateError(null); + }) + .catch(setTemplateError); }, [loadTemplates]); // Load templates for the current project. @@ -222,38 +280,55 @@ export const Catalog = connectToFlags(FLAGS.OPENSHIFT, FLAGS.SERVI setProjectTemplateMetadata([]); setProjectTemplateError(null); } else { - k8sListPartialMetadata(TemplateModel, {ns: namespace}).then((metadata) => { - setProjectTemplateMetadata(metadata); - setProjectTemplateError(null); - }).catch(setTemplateError); + k8sListPartialMetadata(TemplateModel, { ns: namespace }) + .then((metadata) => { + setProjectTemplateMetadata(metadata); + setProjectTemplateError(null); + }) + .catch(setTemplateError); } }, [loadTemplates, namespace]); const error = templateError || projectTemplateError; if (error) { - return ; + return ( + + ); } - if (_.some(flags, flag => flagPending(flag))) { + if (_.some(flags, (flag) => flagPending(flag))) { return null; } const resources = [ - ...(serviceCatalogFlag ? [{ - isList: true, - kind: referenceForModel(ClusterServiceClassModel), - namespaced: false, - prop: 'clusterServiceClasses', - }] : []), - ...(openshiftFlag ? [{ - isList: true, - kind: 'ImageStream', - namespace: 'openshift', - prop: 'imageStreams', - }] : []), - ...plugins.registry.getDevCatalogModels() - .filter(({properties}) => !properties.flag || flags[properties.flag]) - .map(({properties}) => ({ + ...(serviceCatalogFlag + ? [ + { + isList: true, + kind: referenceForModel(ClusterServiceClassModel), + namespaced: false, + prop: 'clusterServiceClasses', + }, + ] + : []), + ...(openshiftFlag + ? [ + { + isList: true, + kind: 'ImageStream', + namespace: 'openshift', + prop: 'imageStreams', + }, + ] + : []), + ...plugins.registry + .getDevCatalogModels() + .filter(({ properties }) => !properties.flag || flags[properties.flag]) + .map(({ properties }) => ({ isList: true, kind: referenceForModel(properties.model), namespaced: properties.model.namespaced, @@ -262,25 +337,35 @@ export const Catalog = connectToFlags(FLAGS.OPENSHIFT, FLAGS.SERVI })), ]; - return - - ; + return ( + + + + ); }); -export const CatalogPage = withStartGuide(({match, noProjectsAvailable}) => { +export const CatalogPage = withStartGuide(({ match, noProjectsAvailable }) => { const namespace = _.get(match, 'params.ns'); - return - - Developer Catalog - -
    - -

    - Add shared apps, services, or source-to-image builders to your project from the Developer Catalog. Cluster admins can install additional apps which will show up here automatically. -

    - -
    -
    ; + return ( + + + Developer Catalog + +
    + +

    + Add shared apps, services, or source-to-image builders to your project from the Developer + Catalog. Cluster admins can install additional apps which will show up here automatically. +

    + +
    +
    + ); }); export type CatalogListPageProps = { @@ -298,7 +383,7 @@ export type CatalogListPageState = { }; export type CatalogProps = { - flags: {[key: string]: boolean}; + flags: { [key: string]: boolean }; namespace?: string; mock: boolean; }; diff --git a/frontend/public/components/chargeback.tsx b/frontend/public/components/chargeback.tsx index b033721efea..3395fcea4cf 100644 --- a/frontend/public/components/chargeback.tsx +++ b/frontend/public/components/chargeback.tsx @@ -9,17 +9,8 @@ import { Conditions } from './conditions'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { coFetchJSON } from '../co-fetch'; import { ChargebackReportModel } from '../models'; -import { - LoadError, - LoadingInline, - MsgBox, -} from './utils/status-box'; -import { - GroupVersionKind, - K8sResourceKind, - modelFor, - referenceForModel, -} from '../module/k8s'; +import { LoadError, LoadingInline, MsgBox } from './utils/status-box'; +import { GroupVersionKind, K8sResourceKind, modelFor, referenceForModel } from '../module/k8s'; import { Kebab, DownloadButton, @@ -36,24 +27,36 @@ import { export const ReportReference: GroupVersionKind = referenceForModel(ChargebackReportModel); export const ScheduledReportReference: GroupVersionKind = 'metering.openshift.io~ScheduledReport'; -export const ReportGenerationQueryReference: GroupVersionKind = 'metering.openshift.io~v1alpha1~ReportQuery'; +export const ReportGenerationQueryReference: GroupVersionKind = + 'metering.openshift.io~v1alpha1~ReportQuery'; -const reportPages=[ - {name: 'All Reports', href: ReportReference}, - {name: 'Report Queries', href: ReportGenerationQueryReference}, +const reportPages = [ + { name: 'All Reports', href: ReportReference }, + { name: 'Report Queries', href: ReportGenerationQueryReference }, ]; const { common } = Kebab.factory; const menuActions = [...Kebab.getExtensionsActionsForKind(ChargebackReportModel), ...common]; -const dataURL = (obj, format='json') => { - return `${window.SERVER_FLAGS.meteringBaseURL}/api/v2/reports/${obj.metadata.namespace}/${obj.metadata.name}/table?format=${format}`; +const dataURL = (obj, format = 'json') => { + return `${window.SERVER_FLAGS.meteringBaseURL}/api/v2/reports/${obj.metadata.namespace}/${ + obj.metadata.name + }/table?format=${format}`; }; -const ChargebackNavBar: React.SFC<{match: {url: string}}> = props =>
    - - -
    ; +const ChargebackNavBar: React.SFC<{ match: { url: string } }> = (props) => ( +
    + + +
    +); const tableColumnClasses = [ classNames('col-lg-3', 'col-md-3', 'col-xs-4'), @@ -67,42 +70,67 @@ const tableColumnClasses = [ const ReportsTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Report Query', props: { className: tableColumnClasses[2] }, + title: 'Report Query', + props: { className: tableColumnClasses[2] }, }, { - title: 'Reporting Start', sortField: 'spec.reportingStart', transforms: [sortable], + title: 'Reporting Start', + sortField: 'spec.reportingStart', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Reporting End', sortField: 'spec.reportingEnd', transforms: [sortable], + title: 'Reporting End', + sortField: 'spec.reportingEnd', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; ReportsTableHeader.displayName = 'ReportsTableHeader'; -const ReportsTableRow: React.FC = ({obj, index, key, style}) => { +const ReportsTableRow: React.FC = ({ obj, index, key, style }) => { return ( - + - + - + @@ -126,34 +154,47 @@ type ReportsTableRowProps = { class ReportsDetails extends React.Component { render() { - const {obj} = this.props; - return
    -
    - -
    -
    - -
    -
    -
    -
    Reporting Start
    -
    -
    Reporting End
    -
    -
    Report Query
    -
    -
    Run Immediately?
    -
    {Boolean(_.get(obj, ['spec', 'runImmediately'])).toString()}
    -
    + const { obj } = this.props; + return ( +
    +
    + +
    +
    + +
    +
    +
    +
    Reporting Start
    +
    + +
    +
    Reporting End
    +
    + +
    +
    Report Query
    +
    + +
    +
    Run Immediately?
    +
    {Boolean(_.get(obj, ['spec', 'runImmediately'])).toString()}
    +
    +
    +
    + + +
    +
    -
    - - -
    - -
    ; + ); } } @@ -168,7 +209,7 @@ const numericUnits = new Set([ 'seconds', ]); -const DataCell = ({name, value, unit}: DataTableCellProps) => { +const DataCell = ({ name, value, unit }: DataTableCellProps) => { if (numericUnits.has(unit)) { return
    {_.round(value, 2).toLocaleString()}
    ; } @@ -183,32 +224,39 @@ const DataCell = ({name, value, unit}: DataTableCellProps) => { return value; }; -const DataTable = ({cols, rows, schema}: DataTableProps) => { - +const DataTable = ({ cols, rows, schema }: DataTableProps) => { const getUnit = (col: string) => { - const colSchema = _.find(schema.values, {'name': col}); + const colSchema = _.find(schema.values, { name: col }); return _.get(colSchema, 'unit', _.isFinite(_.get(colSchema, 'value')) ? 'numeric' : null); }; - const DataTableHeader = () => _.map(cols, col => { - return ({ - sortAsNumber: numericUnits.has(getUnit(col)), - sortField: col, - title: {col.replace(/_/g, ' ')}, - transforms: [sortable], + const DataTableHeader = () => + _.map(cols, (col) => { + return { + sortAsNumber: numericUnits.has(getUnit(col)), + sortField: col, + title: {col.replace(/_/g, ' ')}, + transforms: [sortable], + }; }); - }); - - const DataTableRows = ({componentProps: {data}}: DataTableRowsProps) => _.map(data, (r) => _.map(r, (v, c) => { - return {title: }; - })); - - return
    ; + + const DataTableRows = ({ componentProps: { data } }: DataTableRowsProps) => + _.map(data, (r) => + _.map(r, (v, c) => { + return { title: }; + }), + ); + + return ( +
    + ); }; class ReportData extends React.Component { @@ -226,14 +274,18 @@ class ReportData extends React.Component { } fetchData() { - this.setState({ - inFlight: true, - error: null, - // setState is async. Re-render with inFlight = true so that we don't show a "No data" msg while data is loading - }, () => coFetchJSON(dataURL(this.props.obj)) - .then(data => this.makeTable(data)) - .catch(e => this.setState({error: e})) - .then(() => this.setState({inFlight: false}))); + this.setState( + { + inFlight: true, + error: null, + // setState is async. Re-render with inFlight = true so that we don't show a "No data" msg while data is loading + }, + () => + coFetchJSON(dataURL(this.props.obj)) + .then((data) => this.makeTable(data)) + .catch((e) => this.setState({ error: e })) + .then(() => this.setState({ inFlight: false })), + ); } componentDidMount() { @@ -247,13 +299,13 @@ class ReportData extends React.Component { } const conditions = _.get(nextProps.obj, 'status.conditions'); - const isReportFinished = _.some(conditions, {type: 'Running', status: 'False'}); + const isReportFinished = _.some(conditions, { type: 'Running', status: 'False' }); if (isReportFinished) { this.fetchData(); } } - makeTable(data=this.state.data) { + makeTable(data = this.state.data) { if (!data || _.isEmpty(data.results)) { return; } @@ -267,12 +319,12 @@ class ReportData extends React.Component { }); } - getColumns(data: {results: any[]}) { + getColumns(data: { results: any[] }) { const firstRow = _.head(data.results); - return _.map(firstRow.values, item => item.name); + return _.map(firstRow.values, (item) => item.name); } - getRows(data: {results: any[]}) { + getRows(data: { results: any[] }) { /* Converts data to rows: // data = { // results: [ @@ -318,23 +370,33 @@ class ReportData extends React.Component { // }, // ... */ - const rows = _.map(data.results, ({values}) => { - return _.reduce(values, (acc, {name, value}) => { - acc[name] = value; - return acc; - }, {}); + const rows = _.map(data.results, ({ values }) => { + return _.reduce( + values, + (acc, { name, value }) => { + acc[name] = value; + return acc; + }, + {}, + ); }); return rows; } render() { - const {obj} = this.props; - const {data, cols, rows, inFlight, error} = this.state; + const { obj } = this.props; + const { data, cols, rows, inFlight, error } = this.state; let dataElem = ; if (inFlight) { - dataElem =
    ; + dataElem = ( +
    +
    + +
    +
    + ); } else if (error) { dataElem = ; } else if (data) { @@ -347,34 +409,57 @@ class ReportData extends React.Component { const format = 'csv'; const downloadURL = dataURL(obj, format); - return
    - - - { dataElem } -
    ; + return ( +
    + + + {dataElem} +
    + ); } } -const reportsPages = [ - navFactory.details(ReportsDetails), - navFactory.editYaml(), -]; - -const EmptyMsg = () => ; - -export const ReportsList: React.SFC = props =>
    ; - -const ReportsPage_: React.SFC = props => { - return
    - - -
    ; +const reportsPages = [navFactory.details(ReportsDetails), navFactory.editYaml()]; + +const EmptyMsg = () => ( + +); + +export const ReportsList: React.SFC = (props) => ( +
    +); + +const ReportsPage_: React.SFC = (props) => { + return ( +
    + + +
    + ); }; export const ReportsPage = connectToFlags(FLAGS.CHARGEBACK)(ReportsPage_); -export const ReportsDetailsPage: React.SFC = props => { - return ; +export const ReportsDetailsPage: React.SFC = (props) => { + return ( + + ); }; const reportsGenerationColumnClasses = [ @@ -388,38 +473,64 @@ const reportsGenerationColumnClasses = [ const ReportGenerationQueriesTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: reportsGenerationColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: reportsGenerationColumnClasses[1] }, }, { - title: 'Labels', props: { className: reportsGenerationColumnClasses[2] }, + title: 'Labels', + props: { className: reportsGenerationColumnClasses[2] }, }, { - title: 'Created At', sortField: 'metadata.creationTimestamp', transforms: [sortable], + title: 'Created At', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: reportsGenerationColumnClasses[3] }, }, { - title: '', props: { className: reportsGenerationColumnClasses[4] }, + title: '', + props: { className: reportsGenerationColumnClasses[4] }, }, ]; }; ReportGenerationQueriesTableHeader.displayName = 'ReportGenerationQueriesTableHeader'; -const ReportGenerationQueriesTableRow: React.FC = ({obj, index, key, style}) => { +const ReportGenerationQueriesTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + - + - + @@ -438,51 +549,88 @@ type ReportGenerationQueriesTableRowProps = { style: object; }; -const ReportGenerationQueriesDetails: React.SFC = ({obj}) => { - const columns = _.get(obj, ['spec', 'columns'], []).map((column, i) =>
    - - - ); - - return
    -
    - - -
    Query
    -
    {_.get(obj, ['spec', 'query'])}
    -
    -
    -

    Columns

    -
    -
    {column.name}{column.type}
    - - - - - - - - {columns} - -
    ColumnType
    +const ReportGenerationQueriesDetails: React.SFC = ({ + obj, +}) => { + const columns = _.get(obj, ['spec', 'columns'], []).map((column, i) => ( + + {column.name} + {column.type} + + )); + + return ( +
    +
    + + +
    Query
    +
    +
    +              {_.get(obj, ['spec', 'query'])}
    +            
    +
    +
    +
    +

    Columns

    +
    + + + + + + + + {columns} +
    ColumnType
    +
    -
    - + +
    -
    ; + ); }; -export const ReportGenerationQueriesList: React.SFC = props => ; - -export const ReportGenerationQueriesPage: React.SFC = props =>
    - - -
    ; - -const reportGenerationQueryPages = [navFactory.details(ReportGenerationQueriesDetails), navFactory.editYaml()]; -export const ReportGenerationQueriesDetailsPage: React.SFC = props => { - return ; +export const ReportGenerationQueriesList: React.SFC = (props) => ( +
    +); + +export const ReportGenerationQueriesPage: React.SFC = (props) => ( +
    + + +
    +); + +const reportGenerationQueryPages = [ + navFactory.details(ReportGenerationQueriesDetails), + navFactory.editYaml(), +]; +export const ReportGenerationQueriesDetailsPage: React.SFC< + ReportGenerationQueriesDetailsPageProps +> = (props) => { + return ( + + ); }; export type ReportsDetailsProps = { @@ -515,13 +663,13 @@ export type DataTableCellProps = { }; export type DataTableRowsProps = { - componentProps: {data: any[]}; + componentProps: { data: any[] }; }; export type ReportsPageProps = { filterLabel: string; - flags: {[_: string]: boolean}; - match: {url: string}; + flags: { [_: string]: boolean }; + match: { url: string }; }; export type ReportsDetailsPageProps = { @@ -538,7 +686,7 @@ export type ReportGenerationQueriesDetailsProps = { export type ReportGenerationQueriesPageProps = { filterLabel: string; - match: {url: string}; + match: { url: string }; }; export type ReportGenerationQueriesDetailsPageProps = { diff --git a/frontend/public/components/checkbox.tsx b/frontend/public/components/checkbox.tsx index 2a46da63030..93b372719fe 100644 --- a/frontend/public/components/checkbox.tsx +++ b/frontend/public/components/checkbox.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; -export const Checkbox: React.SFC = ({name, label, checked, onChange}) => ( +export const Checkbox: React.SFC = ({ name, label, checked, onChange }) => (
    -
    - - -
    - ; +
    + + +
    + + ); }; -const ServiceClassTabPage = ({obj}) => ; -export const ClusterServiceBrokerDetailsPage: React.SFC = props => ; -export const ClusterServiceBrokerList: React.SFC = props =>
    ; +const ServiceClassTabPage = ({ obj }) => ( + +); +export const ClusterServiceBrokerDetailsPage: React.SFC = ( + props, +) => ( + +); +export const ClusterServiceBrokerList: React.SFC = (props) => ( +
    +); -export const ClusterServiceBrokerPage: React.SFC = props => +export const ClusterServiceBrokerPage: React.SFC = (props) => ( ; + /> +); export type ClusterServiceBrokerPageProps = { - obj: K8sResourceKind + obj: K8sResourceKind; }; export type ClusterServiceBrokerDetailsProps = { - obj: K8sResourceKind + obj: K8sResourceKind; }; export type ClusterServiceBrokerDetailsPageProps = { - match: any + match: any; }; diff --git a/frontend/public/components/cluster-service-class-info.tsx b/frontend/public/components/cluster-service-class-info.tsx index 85219480590..fa665a4a170 100644 --- a/frontend/public/components/cluster-service-class-info.tsx +++ b/frontend/public/components/cluster-service-class-info.tsx @@ -5,7 +5,9 @@ import { K8sResourceKind, serviceClassDisplayName } from '../module/k8s'; import { ClusterServiceClassIcon } from './catalog/catalog-item-icon'; import { ExternalLink } from './utils'; -export const ClusterServiceClassInfo: React.FC = ({obj: serviceClass}) => { +export const ClusterServiceClassInfo: React.FC = ({ + obj: serviceClass, +}) => { const displayName = serviceClassDisplayName(serviceClass); const description = _.get(serviceClass, 'spec.description'); const longDescription = _.get(serviceClass, 'spec.externalMetadata.longDescription'); @@ -14,26 +16,42 @@ export const ClusterServiceClassInfo: React.FC = ( const provider = _.get(serviceClass, 'spec.externalMetadata.providerDisplayName'); const tags = _.get(serviceClass, 'spec.tags'); - return
    -
    - -
    -

    {displayName}

    - {provider &&

    Provided by {provider}

    } - {tags &&

    {_.map(tags, (tag, i) => {tag})}

    } - {(documentationURL || supportURL) &&
      - {documentationURL &&
    • - -
    • } - {supportURL &&
    • - -
    • } -
    } + return ( +
    +
    + +
    +

    {displayName}

    + {provider &&

    Provided by {provider}

    } + {tags && ( +

    + {_.map(tags, (tag, i) => ( + + {tag} + + ))} +

    + )} + {(documentationURL || supportURL) && ( +
      + {documentationURL && ( +
    • + +
    • + )} + {supportURL && ( +
    • + +
    • + )} +
    + )} +
    + {description &&

    {description}

    } + {longDescription &&

    {longDescription}

    }
    - {description &&

    {description}

    } - {longDescription &&

    {longDescription}

    } -
    ; + ); }; export type ClusterServiceClassInfoProps = { diff --git a/frontend/public/components/cluster-service-class.tsx b/frontend/public/components/cluster-service-class.tsx index 4123f228781..9d56c0d4c73 100644 --- a/frontend/public/components/cluster-service-class.tsx +++ b/frontend/public/components/cluster-service-class.tsx @@ -4,7 +4,15 @@ import * as _ from 'lodash-es'; import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; -import { history, SectionHeading, detailsPage, navFactory, ResourceSummary, resourcePathFromModel, ResourceLink } from './utils'; +import { + history, + SectionHeading, + detailsPage, + navFactory, + ResourceSummary, + resourcePathFromModel, + ResourceLink, +} from './utils'; import { viewYamlComponent } from './utils/horizontal-nav'; import { ClusterServiceClassModel, ClusterServiceBrokerModel } from '../models'; import { K8sResourceKind, referenceForModel, serviceClassDisplayName } from '../module/k8s'; @@ -17,16 +25,16 @@ const createInstance = (kindObj, serviceClass) => { return { btnClass: 'btn-primary', callback: () => { - history.push(`/catalog/create-service-instance?cluster-service-class=${serviceClass.metadata.name}`); + history.push( + `/catalog/create-service-instance?cluster-service-class=${serviceClass.metadata.name}`, + ); }, label: 'Create Instance', }; } }; -const actionButtons = [ - createInstance, -]; +const actionButtons = [createInstance]; const tableColumnClasses = [ classNames('col-sm-6', 'col-xs-12'), @@ -37,34 +45,48 @@ const tableColumnClasses = [ const ClusterServiceClassTableHeader = () => { return [ { - title: 'Display Name', sortFunc: 'serviceClassDisplayName', transforms: [sortable], + title: 'Display Name', + sortFunc: 'serviceClassDisplayName', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'External Name', sortField: 'spec.externalName', transforms: [sortable], + title: 'External Name', + sortField: 'spec.externalName', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Broker', sortField: 'spec.clusterServiceBrokerName', transforms: [sortable], + title: 'Broker', + sortField: 'spec.clusterServiceBrokerName', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, ]; }; ClusterServiceClassTableHeader.displayName = 'ClusterServiceClassTableHeader'; -const ClusterServiceClassTableRow: React.FC = ({obj: serviceClass, index, key, style}) => { +const ClusterServiceClassTableRow: React.FC = ({ + obj: serviceClass, + index, + key, + style, +}) => { const path = resourcePathFromModel(ClusterServiceClassModel, serviceClass.metadata.name); return ( - {serviceClassDisplayName(serviceClass)} - - - {serviceClass.spec.externalName} + + {serviceClassDisplayName(serviceClass)} + + {serviceClass.spec.externalName} - + ); @@ -77,45 +99,68 @@ type ClusterServiceClassTableRowProps = { style: object; }; - -const ClusterServiceClassDetails: React.FC = ({obj: serviceClass}) =>
    -
    -
    - -
    -
    - - -
    External Name
    -
    {serviceClass.spec.externalName || '-'}
    -
    - {serviceClass.status.removedFromBrokerCatalog && -
    Removed From Catalog
    -
    {serviceClass.status.removedFromBrokerCatalog}
    -
    } +const ClusterServiceClassDetails: React.FC = ({ + obj: serviceClass, +}) => ( +
    +
    +
    + +
    +
    + + +
    External Name
    +
    {serviceClass.spec.externalName || '-'}
    +
    + {serviceClass.status.removedFromBrokerCatalog && ( + +
    Removed From Catalog
    +
    {serviceClass.status.removedFromBrokerCatalog}
    +
    + )} +
    -
    ; +); -const ClusterServicePlanTab: React.FC<{obj: K8sResourceKind}> = ({obj}) => { - return ; +const ClusterServicePlanTab: React.FC<{ obj: K8sResourceKind }> = ({ obj }) => { + return ( + + ); }; -export const ClusterServiceClassDetailsPage: React.FC = props => ; +export const ClusterServiceClassDetailsPage: React.FC = ( + props, +) => ( + +); -export const ClusterServiceClassList: React.FC = props =>
    ; +export const ClusterServiceClassList: React.FC = (props) => ( +
    +); -export const ClusterServiceClassPage: React.FC = props => +export const ClusterServiceClassPage: React.FC = (props) => ( = p kind={referenceForModel(ClusterServiceClassModel)} textFilter="service-class" canCreate={false} - />; + /> +); export type ClusterServiceClassPageProps = { - showTitle?: boolean, - fieldSelector?: string + showTitle?: boolean; + fieldSelector?: string; }; export type ClusterServiceClassDetailsProps = { - obj: K8sResourceKind, + obj: K8sResourceKind; }; export type ClusterServiceClassDetailsPageProps = { - match: any, - name: string, + match: any; + name: string; }; diff --git a/frontend/public/components/cluster-service-plan.tsx b/frontend/public/components/cluster-service-plan.tsx index 88bfdcadd1f..4463fd62711 100644 --- a/frontend/public/components/cluster-service-plan.tsx +++ b/frontend/public/components/cluster-service-plan.tsx @@ -4,7 +4,11 @@ import { sortable } from '@patternfly/react-table'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { SectionHeading, detailsPage, navFactory, ResourceLink, ResourceSummary } from './utils'; import { K8sResourceKind, referenceForModel, servicePlanDisplayName } from '../module/k8s'; -import { ClusterServicePlanModel, ClusterServiceBrokerModel, ClusterServiceClassModel } from '../models'; +import { + ClusterServicePlanModel, + ClusterServiceBrokerModel, + ClusterServiceClassModel, +} from '../models'; import { viewYamlComponent } from './utils/horizontal-nav'; const tableColumnClasses = [ @@ -16,32 +20,49 @@ const tableColumnClasses = [ const ClusterServicePlanTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'External Name', sortField: 'spec.externalName', transforms: [sortable], + title: 'External Name', + sortField: 'spec.externalName', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Broker', sortField: 'spec.clusterServiceBrokerName', transforms: [sortable], + title: 'Broker', + sortField: 'spec.clusterServiceBrokerName', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, ]; }; ClusterServicePlanTableHeader.displayName = 'ClusterServicePlanTableHeader'; -const ClusterServicePlanTableRow: React.FC = ({obj: servicePlan, index, key, style}) => { +const ClusterServicePlanTableRow: React.FC = ({ + obj: servicePlan, + index, + key, + style, +}) => { return ( - - - - {servicePlan.spec.externalName} + + {servicePlan.spec.externalName} - + ); @@ -54,61 +75,90 @@ type ClusterServicePlanTableRowProps = { style: object; }; -const ClusterServicePlanDetails: React.SFC = ({obj: servicePlan}) => { - return
    - -
    -
    - -
    -
    -
    -
    Description
    -
    {servicePlan.spec.description}
    -
    Broker
    -
    -
    Service Class
    -
    - {servicePlan.status.removedFromBrokerCatalog && -
    Removed From Catalog
    -
    {servicePlan.status.removedFromBrokerCatalog}
    -
    } -
    +const ClusterServicePlanDetails: React.SFC = ({ + obj: servicePlan, +}) => { + return ( +
    + +
    +
    + +
    +
    +
    +
    Description
    +
    {servicePlan.spec.description}
    +
    Broker
    +
    + +
    +
    Service Class
    +
    + +
    + {servicePlan.status.removedFromBrokerCatalog && ( + +
    Removed From Catalog
    +
    {servicePlan.status.removedFromBrokerCatalog}
    +
    + )} +
    +
    -
    ; + ); }; -export const ClusterServicePlanDetailsPage: React.SFC = props => ; +export const ClusterServicePlanDetailsPage: React.SFC = ( + props, +) => ( + +); -export const ClusterServicePlanList: React.SFC = props =>
    ; +export const ClusterServicePlanList: React.SFC = (props) => ( +
    +); -export const ClusterServicePlanPage: React.SFC = props => +export const ClusterServicePlanPage: React.SFC = (props) => ( ; + /> +); export type ClusterServicePlanPageProps = { - showTitle?: boolean, - fieldSelector?: string + showTitle?: boolean; + fieldSelector?: string; }; export type ClusterServicePlanDetailsProps = { - obj: K8sResourceKind + obj: K8sResourceKind; }; export type ClusterServicePlanDetailsPageProps = { - match: any + match: any; }; diff --git a/frontend/public/components/cluster-settings/basicauth-idp-form.tsx b/frontend/public/components/cluster-settings/basicauth-idp-form.tsx index 92d05ebd6f5..3a546e53f6b 100644 --- a/frontend/public/components/cluster-settings/basicauth-idp-form.tsx +++ b/frontend/public/components/cluster-settings/basicauth-idp-form.tsx @@ -10,7 +10,12 @@ import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; import { IDPCAFileInput } from './idp-cafile-input'; -export const DroppableFileInput = (props: any) => import('../utils/file-input').then(c => c.DroppableFileInput)} {...props} />; +export const DroppableFileInput = (props: any) => ( + import('../utils/file-input').then((c) => c.DroppableFileInput)} + {...props} + /> +); export class AddBasicAuthPage extends PromiseComponent<{}, AddBasicAuthPageState> { readonly state: AddBasicAuthPageState = { @@ -102,113 +107,118 @@ export class AddBasicAuthPage extends PromiseComponent<{}, AddBasicAuthPageState submit: React.FormEventHandler = (e) => { e.preventDefault(); if (_.isEmpty(this.state.keyFileContent) !== _.isEmpty(this.state.certFileContent)) { - this.setState({errorMessage: 'Values for Certificate and Key should both be either excluded or provided.'}); + this.setState({ + errorMessage: 'Values for Certificate and Key should both be either excluded or provided.', + }); return; } // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { - const promises = [ - this.createTLSSecret(), - this.createCAConfigMap(), - ]; - - Promise.all(promises).then(([tlsSecret, configMap]) => { - const caName = configMap ? configMap.metadata.name : ''; - const secretName = tlsSecret ? tlsSecret.metadata.name: ''; - return this.addBasicAuthIDP(oauth, secretName, caName); - }).then(redirectToOAuthPage); + const promises = [this.createTLSSecret(), this.createCAConfigMap()]; + + Promise.all(promises) + .then(([tlsSecret, configMap]) => { + const caName = configMap ? configMap.metadata.name : ''; + const secretName = tlsSecret ? tlsSecret.metadata.name : ''; + return this.addBasicAuthIDP(oauth, secretName, caName); + }) + .then(redirectToOAuthPage); }); }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; urlChanged: React.ReactEventHandler = (event) => { - this.setState({url: event.currentTarget.value}); + this.setState({ url: event.currentTarget.value }); }; caFileChanged = (caFileContent: string) => { - this.setState({caFileContent}); + this.setState({ caFileContent }); }; certFileChanged = (certFileContent: string) => { - this.setState({certFileContent}); + this.setState({ certFileContent }); }; keyFileChanged = (keyFileContent: string) => { - this.setState({keyFileContent}); + this.setState({ keyFileContent }); }; render() { const { name, url, caFileContent, certFileContent, keyFileContent } = this.state; const title = 'Add Identity Provider: Basic Authentication'; - return
    - - {title} - -
    -

    {title}

    -

    - Basic authentication is a generic backend integration mechanism that allows users to authenticate with credentials validated against a remote identity provider. -

    - -
    - - -

    - The remote URL to connect to. + return ( +

    + + {title} + + +

    {title}

    +

    + Basic authentication is a generic backend integration mechanism that allows users to + authenticate with credentials validated against a remote identity provider.

    -
    - -
    - -
    -
    - -
    - - - - - - - -
    ; + +
    + + +

    + The remote URL to connect to. +

    +
    + +
    + +
    +
    + +
    + + + + + + + +
    + ); } } export type AddBasicAuthPageState = { name: string; - url: string + url: string; caFileContent: string; certFileContent: string; keyFileContent: string; diff --git a/frontend/public/components/cluster-settings/cluster-operator.tsx b/frontend/public/components/cluster-settings/cluster-operator.tsx index 32b71e58402..7cb9b52e9a3 100644 --- a/frontend/public/components/cluster-settings/cluster-operator.tsx +++ b/frontend/public/components/cluster-settings/cluster-operator.tsx @@ -6,13 +6,7 @@ import { Alert } from '@patternfly/react-core'; import { SyncAltIcon, UnknownIcon } from '@patternfly/react-icons'; import { ClusterOperatorModel } from '../../models'; -import { - DetailsPage, - ListPage, - Table, - TableRow, - TableData, -} from '../factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from '../factory'; import { Conditions } from '../conditions'; import { getClusterOperatorStatus, @@ -36,12 +30,11 @@ import { ResourceSummary, SectionHeading, } from '../utils'; -import { - GreenCheckCircleIcon, - YellowExclamationTriangleIcon, -} from '@console/shared'; +import { GreenCheckCircleIcon, YellowExclamationTriangleIcon } from '@console/shared'; -export const clusterOperatorReference: K8sResourceKindReference = referenceForModel(ClusterOperatorModel); +export const clusterOperatorReference: K8sResourceKindReference = referenceForModel( + ClusterOperatorModel, +); const getIcon = (status: OperatorStatus) => { return { @@ -52,9 +45,13 @@ const getIcon = (status: OperatorStatus) => { }[status]; }; -const OperatorStatusIconAndLabel: React.SFC = ({status}) => { +const OperatorStatusIconAndLabel: React.SFC = ({ status }) => { const icon = getIcon(status); - return {icon} {status}; + return ( + + {icon} {status} + + ); }; const tableColumnClasses = [ @@ -68,38 +65,53 @@ const tableColumnClasses = [ const ClusterOperatorTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Status', sortFunc: 'getClusterOperatorStatus', transforms: [sortable], + title: 'Status', + sortFunc: 'getClusterOperatorStatus', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Version', sortFunc: 'getClusterOperatorVersion', transforms: [sortable], + title: 'Version', + sortFunc: 'getClusterOperatorVersion', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Message', props: { className: tableColumnClasses[3] }, + title: 'Message', + props: { className: tableColumnClasses[3] }, }, ]; }; ClusterOperatorTableHeader.displayName = 'ClusterOperatorTableHeader'; -const ClusterOperatorTableRow: React.FC = ({obj, index, key, style}) => { +const ClusterOperatorTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { const { status, message } = getStatusAndMessage(obj); const operatorVersion = getClusterOperatorVersion(obj); return ( - + - - {operatorVersion || '-'} - + {operatorVersion || '-'} {message ? _.truncate(message, { length: 256, separator: ' ' }) : '-'} @@ -114,7 +126,15 @@ type ClusterOperatorTableRowProps = { style: object; }; -export const ClusterOperatorList: React.SFC = props =>
    ; +export const ClusterOperatorList: React.SFC = (props) => ( +
    +); const allStatuses = [ OperatorStatus.Available, @@ -123,32 +143,38 @@ const allStatuses = [ OperatorStatus.Unknown, ]; -const filters = [{ - type: 'cluster-operator-status', - selected: allStatuses, - reducer: getClusterOperatorStatus, - items: _.map(allStatuses, phase => ({ - id: phase, - title: phase, - })), -}]; +const filters = [ + { + type: 'cluster-operator-status', + selected: allStatuses, + reducer: getClusterOperatorStatus, + items: _.map(allStatuses, (phase) => ({ + id: phase, + title: phase, + })), + }, +]; -const UpdateInProgressAlert: React.SFC = ({cv}) => { - const updateCondition = getClusterVersionCondition(cv, ClusterVersionConditionType.Progressing, K8sResourceConditionStatus.True); +const UpdateInProgressAlert: React.SFC = ({ cv }) => { + const updateCondition = getClusterVersionCondition( + cv, + ClusterVersionConditionType.Progressing, + K8sResourceConditionStatus.True, + ); return ( - { updateCondition && + {updateCondition && (
    {updateCondition.message}
    - } + )}
    ); }; -export const ClusterOperatorPage: React.SFC = props => +export const ClusterOperatorPage: React.SFC = (props) => ( = props => canCreate={false} rowFilters={filters} /> - ; + +); -const OperandVersions: React.SFC = ({versions}) => { - return _.isEmpty(versions) - ? - :
    +const OperandVersions: React.SFC = ({ versions }) => { + return _.isEmpty(versions) ? ( + + ) : ( +
    @@ -173,7 +201,7 @@ const OperandVersions: React.SFC = ({versions}) => { - {_.map(versions, ({name, version}, i) => ( + {_.map(versions, ({ name, version }, i) => ( @@ -181,18 +209,17 @@ const OperandVersions: React.SFC = ({versions}) => { ))}
    {name} {version}
    -
    ; +
    + ); }; - -const ClusterOperatorDetails: React.SFC = ({obj}) => { +const ClusterOperatorDetails: React.SFC = ({ obj }) => { const { status, message } = getStatusAndMessage(obj); const versions: OperandVersion[] = _.get(obj, 'status.versions', []); const conditions = _.get(obj, 'status.conditions', []); // Show the operator version in the details overview if it's the only version. - const operatorVersion = versions.length === 1 && versions[0].name === 'operator' - ? versions[0].version - : null; + const operatorVersion = + versions.length === 1 && versions[0].name === 'operator' ? versions[0].version : null; return (
    @@ -203,12 +230,16 @@ const ClusterOperatorDetails: React.SFC = ({obj}) =
    - {operatorVersion && -
    Version
    -
    {operatorVersion}
    -
    } + {operatorVersion && ( + +
    Version
    +
    {operatorVersion}
    +
    + )}
    Status
    -
    +
    + +
    Message
    {message || '-'}
    @@ -227,12 +258,13 @@ const ClusterOperatorDetails: React.SFC = ({obj}) = ); }; -export const ClusterOperatorDetailsPage: React.SFC = props => +export const ClusterOperatorDetailsPage: React.SFC = (props) => ( ; + /> +); type OperatorStatusIconAndLabelProps = { status: OperatorStatus; diff --git a/frontend/public/components/cluster-settings/cluster-settings.tsx b/frontend/public/components/cluster-settings/cluster-settings.tsx index 2eb142ebd9d..df94371da93 100644 --- a/frontend/public/components/cluster-settings/cluster-settings.tsx +++ b/frontend/public/components/cluster-settings/cluster-settings.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import { Helmet } from 'react-helmet'; -import { Button , Tooltip } from '@patternfly/react-core'; +import { Button, Tooltip } from '@patternfly/react-core'; import { Link } from 'react-router-dom'; import { @@ -51,79 +51,115 @@ import { } from '@console/shared'; const cancelUpdate = (cv: ClusterVersionKind) => { - k8sPatch(ClusterVersionModel, cv, [{path: '/spec/desiredUpdate', op: 'remove'}]).catch(err => { - const error = err.message; - errorModal({error}); - }); + k8sPatch(ClusterVersionModel, cv, [{ path: '/spec/desiredUpdate', op: 'remove' }]).catch( + (err) => { + const error = err.message; + errorModal({ error }); + }, + ); }; export const clusterAutoscalerReference = referenceForModel(ClusterAutoscalerModel); -export const CurrentChannel: React.SFC = ({cv}) => ; - -const InvalidMessage: React.SFC = ({cv}) => <> -
    - Invalid cluster version -
    +export const CurrentChannel: React.SFC = ({ cv }) => ( -; +); -const UpdatesAvailableMessage: React.SFC = ({cv}) => <> -
    - Update available -
    -
    - -
    -; + +); -const UpdatingMessage: React.SFC = ({cv}) => { - const updatingCondition = getClusterVersionCondition(cv, ClusterVersionConditionType.Progressing, K8sResourceConditionStatus.True); - return <> - {updatingCondition.message &&
    {updatingCondition.message}
    } - View details - ; +const UpdatesAvailableMessage: React.SFC = ({ cv }) => ( + <> +
    + Update available +
    +
    + +
    + +); + +const UpdatingMessage: React.SFC = ({ cv }) => { + const updatingCondition = getClusterVersionCondition( + cv, + ClusterVersionConditionType.Progressing, + K8sResourceConditionStatus.True, + ); + return ( + <> + {updatingCondition.message && ( +
    + {updatingCondition.message} +
    + )} + View details + + ); }; -const ErrorRetrievingMessage: React.SFC = ({cv}) => { - const retrievedUpdatesCondition = getClusterVersionCondition(cv, ClusterVersionConditionType.RetrievedUpdates, K8sResourceConditionStatus.False); - return retrievedUpdatesCondition.reason === 'NoChannel' - ? No update channel selected - : ( - - Error retrieving - - ); +const ErrorRetrievingMessage: React.SFC = ({ cv }) => { + const retrievedUpdatesCondition = getClusterVersionCondition( + cv, + ClusterVersionConditionType.RetrievedUpdates, + K8sResourceConditionStatus.False, + ); + return retrievedUpdatesCondition.reason === 'NoChannel' ? ( + No update channel selected + ) : ( + + + Error retrieving + + + ); }; -const FailingMessage: React.SFC = ({cv}) => { - const failingCondition = getClusterVersionCondition(cv, ClusterVersionConditionType.Failing, K8sResourceConditionStatus.True); - return <> -
    - - Failing - -
    - View details - ; +const FailingMessage: React.SFC = ({ cv }) => { + const failingCondition = getClusterVersionCondition( + cv, + ClusterVersionConditionType.Failing, + K8sResourceConditionStatus.True, + ); + return ( + <> +
    + + + Failing + + +
    + View details + + ); }; -const UpToDateMessage: React.SFC<{}> = () => - Up to date -; +const UpToDateMessage: React.SFC<{}> = () => ( + + Up to date + +); -export const UpdateStatus: React.SFC = ({cv}) => { +export const UpdateStatus: React.SFC = ({ cv }) => { const status = getClusterUpdateStatus(cv); switch (status) { case ClusterUpdateStatus.Invalid: @@ -141,42 +177,61 @@ export const UpdateStatus: React.SFC = ({cv}) => { } }; -export const CurrentVersion: React.SFC = ({cv}) => { +export const CurrentVersion: React.SFC = ({ cv }) => { const desiredVersion = getDesiredClusterVersion(cv); const lastVersion = getLastCompletedUpdate(cv); const status = getClusterUpdateStatus(cv); if (status === ClusterUpdateStatus.UpToDate || status === ClusterUpdateStatus.UpdatesAvailable) { - return desiredVersion - ? {desiredVersion} - : <> Unknown; + return desiredVersion ? ( + {desiredVersion} + ) : ( + <> + +  Unknown + + ); } - return lastVersion - ? {lastVersion} - : <>None; + return lastVersion ? {lastVersion} : <>None; }; -export const UpdateLink: React.SFC = ({cv}) => { +export const UpdateLink: React.SFC = ({ cv }) => { const status = getClusterUpdateStatus(cv); const updatesAvailable = !_.isEmpty(getAvailableClusterUpdates(cv)); - return <> - { - updatesAvailable && (status === ClusterUpdateStatus.ErrorRetrieving || status === ClusterUpdateStatus.Failing || status === ClusterUpdateStatus.Updating) - ? - : null - } - ; + return ( + <> + {updatesAvailable && + (status === ClusterUpdateStatus.ErrorRetrieving || + status === ClusterUpdateStatus.Failing || + status === ClusterUpdateStatus.Updating) ? ( + + ) : null} + + ); }; -export const CurrentVersionHeader: React.SFC = ({cv}) => { +export const CurrentVersionHeader: React.SFC = ({ cv }) => { const status = getClusterUpdateStatus(cv); - return <> - { status === ClusterUpdateStatus.UpToDate || status === ClusterUpdateStatus.UpdatesAvailable ? 'Current Version' : 'Last Completed Version' } - ; + return ( + <> + {status === ClusterUpdateStatus.UpToDate || status === ClusterUpdateStatus.UpdatesAvailable + ? 'Current Version' + : 'Last Completed Version'} + + ); }; -export const ClusterVersionDetailsTable: React.SFC = ({obj: cv, autoscalers}) => { +export const ClusterVersionDetailsTable: React.SFC = ({ + obj: cv, + autoscalers, +}) => { const { history = [] } = cv.status; const clusterID = getClusterID(cv); const errataLink = getErrataLink(cv); @@ -184,133 +239,186 @@ export const ClusterVersionDetailsTable: React.SFC -
    -
    -
    -
    -
    -
    -
    Channel
    -
    -
    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -
    -
    -
    Update Status
    -
    -
    + return ( + <> +
    +
    +
    +
    +
    +
    +
    Channel
    +
    + +
    +
    +
    +
    +
    +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    +
    Update Status
    +
    + +
    +
    +
    +
    +

    + View this cluster and manage subscription settings in{' '} + . +

    +
    +
    Cluster ID
    +
    + {clusterID} +
    +
    Desired Release Image
    +
    + {imageParts.length === 2 ? ( + <> + {imageParts[0]}@ + {imageParts[1]} + + ) : ( + desiredImage || '-' + )} +
    +
    Cluster Version Configuration
    +
    + +
    +
    Cluster Autoscaler
    +
    + {_.isEmpty(autoscalers) ? ( + + + Create Autoscaler + + ) : ( + autoscalers.map((autoscaler) => ( +
    + +
    + )) + )} +
    +
    +
    -
    -

    - View this cluster and manage subscription settings in - {' '} - . -

    -
    -
    Cluster ID
    -
    {clusterID}
    -
    Desired Release Image
    -
    - {imageParts.length === 2 - ? <>{imageParts[0]}@{imageParts[1]} - : desiredImage || '-'} -
    -
    Cluster Version Configuration
    -
    - -
    -
    Cluster Autoscaler
    -
    - {_.isEmpty(autoscalers) - ? - Create Autoscaler - - : autoscalers.map(autoscaler =>
    )} -
    -
    +
    + + {errataLink && ( + + + + )} + + {_.isEmpty(history) ? ( + + ) : ( +
    + + + + + + + + + + + {_.map(history, (update, i) => ( + + + + + + + ))} + +
    VersionStateStartedCompleted
    + {update.version || '-'} + {update.state || '-'} + + + {update.completionTime ? ( + + ) : ( + '-' + )} +
    +
    + )}
    -
    -
    - - {errataLink && } - - {_.isEmpty(history) - ? - :
    - - - - - - - - - - - {_.map(history, (update, i) => - - - - - )} - -
    VersionStateStartedCompleted
    {update.version || '-'}{update.state || '-'}{update.completionTime ? : '-'}
    -
    - } -
    - ; + + ); }; -export const ClusterOperatorTabPage: React.SFC = ({obj: cv}) => ; +export const ClusterOperatorTabPage: React.SFC = ({ obj: cv }) => ( + +); -const pages = [{ - href: '', - name: 'Overview', - component: ClusterVersionDetailsTable, -}, { - href: 'clusteroperators', - name: 'Cluster Operators', - component: ClusterOperatorTabPage, -}, { - href: 'globalconfig', - name: 'Global Configuration', - component: GlobalConfigPage, -}]; +const pages = [ + { + href: '', + name: 'Overview', + component: ClusterVersionDetailsTable, + }, + { + href: 'clusteroperators', + name: 'Cluster Operators', + component: ClusterOperatorTabPage, + }, + { + href: 'globalconfig', + name: 'Global Configuration', + component: GlobalConfigPage, + }, +]; -export const ClusterSettingsPage: React.SFC = ({match}) => { +export const ClusterSettingsPage: React.SFC = ({ match }) => { const title = 'Cluster Settings'; const resources = [ - {kind: clusterVersionReference, name: 'version', isList: false, prop: 'obj'}, - {kind: clusterAutoscalerReference, isList: true, prop: 'autoscalers', optional: true}, + { kind: clusterVersionReference, name: 'version', isList: false, prop: 'obj' }, + { kind: clusterAutoscalerReference, isList: true, prop: 'autoscalers', optional: true }, ]; const resourceKeys = _.map(resources, 'prop'); - return <> - - {title} - -
    -

    {title}

    -
    - - - - ; + return ( + <> + + {title} + +
    +

    + {title} +

    +
    + + + + + ); }; type UpdateStatusProps = { diff --git a/frontend/public/components/cluster-settings/cluster-version.tsx b/frontend/public/components/cluster-settings/cluster-version.tsx index 555158f7f95..3dcb8e0b0ab 100644 --- a/frontend/public/components/cluster-settings/cluster-version.tsx +++ b/frontend/public/components/cluster-settings/cluster-version.tsx @@ -4,20 +4,12 @@ import * as _ from 'lodash-es'; import { ClusterVersionModel } from '../../models'; import { DetailsPage } from '../factory'; import { Conditions } from '../conditions'; -import { - ClusterVersionKind, - K8sResourceKindReference, - referenceForModel, -} from '../../module/k8s'; -import { - navFactory, - ResourceSummary, - SectionHeading, -} from '../utils'; +import { ClusterVersionKind, K8sResourceKindReference, referenceForModel } from '../../module/k8s'; +import { navFactory, ResourceSummary, SectionHeading } from '../utils'; const clusterVersionReference: K8sResourceKindReference = referenceForModel(ClusterVersionModel); -const ClusterVersionDetails: React.SFC = ({obj}) => { +const ClusterVersionDetails: React.SFC = ({ obj }) => { const conditions = _.get(obj, 'status.conditions', []); return ( @@ -33,12 +25,13 @@ const ClusterVersionDetails: React.SFC = ({obj}) => ); }; -export const ClusterVersionDetailsPage: React.SFC = props => +export const ClusterVersionDetailsPage: React.SFC = (props) => ( ; + /> +); type ClusterVersionDetailsProps = { obj: ClusterVersionKind; diff --git a/frontend/public/components/cluster-settings/github-idp-form.tsx b/frontend/public/components/cluster-settings/github-idp-form.tsx index 5d1d99821a5..c0d831312d6 100644 --- a/frontend/public/components/cluster-settings/github-idp-form.tsx +++ b/frontend/public/components/cluster-settings/github-idp-form.tsx @@ -4,17 +4,11 @@ import { ActionGroup, Button } from '@patternfly/react-core'; import { SecretModel, ConfigMapModel } from '../../models'; import { IdentityProvider, k8sCreate, K8sResourceKind, OAuthKind } from '../../module/k8s'; -import { - ButtonBar, - ListInput, - PromiseComponent, - history, -} from '../utils'; +import { ButtonBar, ListInput, PromiseComponent, history } from '../utils'; import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; import { IDPCAFileInput } from './idp-cafile-input'; - export class AddGitHubPage extends PromiseComponent<{}, AddGitHubPageState> { readonly state: AddGitHubPageState = { name: 'github', @@ -70,7 +64,11 @@ export class AddGitHubPage extends PromiseComponent<{}, AddGitHubPageState> { return this.handlePromise(k8sCreate(ConfigMapModel, ca)); } - addGitHubIDP(oauth: OAuthKind, clientSecretName: string, caName: string): Promise { + addGitHubIDP( + oauth: OAuthKind, + clientSecretName: string, + caName: string, + ): Promise { const { name, clientID, hostname, organizations, teams } = this.state; const idp: IdentityProvider = { name, @@ -99,122 +97,148 @@ export class AddGitHubPage extends PromiseComponent<{}, AddGitHubPageState> { submit: React.FormEventHandler = (e) => { e.preventDefault(); if (this.state.organizations.length > 0 && this.state.teams.length > 0) { - this.setState({errorMessage: 'Specify either organizations or teams, but not both.'}); + this.setState({ errorMessage: 'Specify either organizations or teams, but not both.' }); return; } // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { - const promises = [ - this.createClientSecret(), - this.createCAConfigMap(), - ]; - - Promise.all(promises).then(([secret, configMap]) => { - const caName = configMap ? configMap.metadata.name : ''; - return this.addGitHubIDP(oauth, secret.metadata.name, caName); - }).then(redirectToOAuthPage); + const promises = [this.createClientSecret(), this.createCAConfigMap()]; + + Promise.all(promises) + .then(([secret, configMap]) => { + const caName = configMap ? configMap.metadata.name : ''; + return this.addGitHubIDP(oauth, secret.metadata.name, caName); + }) + .then(redirectToOAuthPage); }); }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; clientIDChanged: React.ReactEventHandler = (event) => { - this.setState({clientID: event.currentTarget.value}); + this.setState({ clientID: event.currentTarget.value }); }; clientSecretChanged: React.ReactEventHandler = (event) => { - this.setState({clientSecret: event.currentTarget.value}); + this.setState({ clientSecret: event.currentTarget.value }); }; hostnameChanged: React.ReactEventHandler = (event) => { - this.setState({hostname: event.currentTarget.value}); + this.setState({ hostname: event.currentTarget.value }); }; organizationsChanged = (organizations: string[]) => { - this.setState({organizations}); + this.setState({ organizations }); }; teamsChanged = (teams: string[]) => { - this.setState({teams}); + this.setState({ teams }); }; caFileChanged = (caFileContent: string) => { - this.setState({caFileContent}); + this.setState({ caFileContent }); }; render() { const { name, clientID, clientSecret, hostname, caFileContent } = this.state; const title = 'Add Identity Provider: GitHub'; - return
    - - {title} - -
    -

    {title}

    -

    - You can use the GitHub integration to connect to either GitHub or GitHub Enterprise. For GitHub Enterprise, you must provide the hostname of your instance and can optionally provide a CA certificate bundle to use in requests to the server. -

    - -
    - - -
    -
    - - -
    -
    - - -

    - Optional domain for use with a hosted instance of GitHub Enterprise. + return ( +

    + + {title} + + +

    {title}

    +

    + You can use the GitHub integration to connect to either GitHub or GitHub Enterprise. For + GitHub Enterprise, you must provide the hostname of your instance and can optionally + provide a CA certificate bundle to use in requests to the server. +

    + +
    + + +
    +
    + + +
    +
    + + +

    + Optional domain for use with a hosted instance of GitHub Enterprise. +

    +
    + +
    +

    Organizations

    +

    + Optionally list organizations. If specified, only GitHub users that are members of at + least one of the listed organizations will be allowed to log in. Cannot be used in + combination with teams. +

    + +
    +

    Teams

    +

    + Optionally list teams. If specified, only GitHub users that are members of at least one + of the listed teams will be allowed to log in. Cannot be used in combination with{' '} + organizations.

    -
    - -
    -

    Organizations

    -

    Optionally list organizations. If specified, only GitHub users that are members of at least one of the listed organizations will be allowed to log in. Cannot be used in combination with teams.

    - -
    -

    Teams

    -

    Optionally list teams. If specified, only GitHub users that are members of at least one of the listed teams will be allowed to log in. Cannot be used in combination with organizations.

    - - - - - - - - -
    ; + + + + + + + + +
    + ); } } @@ -222,7 +246,7 @@ export type AddGitHubPageState = { name: string; clientID: string; clientSecret: string; - hostname: string + hostname: string; organizations: string[]; teams: string[]; caFileContent: string; diff --git a/frontend/public/components/cluster-settings/gitlab-idp-form.tsx b/frontend/public/components/cluster-settings/gitlab-idp-form.tsx index 84ac8b2e442..ec56a48cbd3 100644 --- a/frontend/public/components/cluster-settings/gitlab-idp-form.tsx +++ b/frontend/public/components/cluster-settings/gitlab-idp-form.tsx @@ -9,7 +9,6 @@ import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; import { IDPCAFileInput } from './idp-cafile-input'; - export class AddGitLabPage extends PromiseComponent<{}, AddGitLabPageState> { readonly state: AddGitLabPageState = { name: 'gitlab', @@ -63,7 +62,11 @@ export class AddGitLabPage extends PromiseComponent<{}, AddGitLabPageState> { return this.handlePromise(k8sCreate(ConfigMapModel, ca)); } - addGitLabIDP(oauth: OAuthKind, clientSecretName: string, caName: string): Promise { + addGitLabIDP( + oauth: OAuthKind, + clientSecretName: string, + caName: string, + ): Promise { const { name, clientID, url } = this.state; const idp: IdentityProvider = { name, @@ -91,108 +94,116 @@ export class AddGitLabPage extends PromiseComponent<{}, AddGitLabPageState> { e.preventDefault(); // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { - const promises = [ - this.createClientSecret(), - this.createCAConfigMap(), - ]; - - Promise.all(promises).then(([secret, configMap]) => { - const caName = configMap ? configMap.metadata.name : ''; - return this.addGitLabIDP(oauth, secret.metadata.name, caName); - }).then(redirectToOAuthPage); + const promises = [this.createClientSecret(), this.createCAConfigMap()]; + + Promise.all(promises) + .then(([secret, configMap]) => { + const caName = configMap ? configMap.metadata.name : ''; + return this.addGitLabIDP(oauth, secret.metadata.name, caName); + }) + .then(redirectToOAuthPage); }); }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; clientIDChanged: React.ReactEventHandler = (event) => { - this.setState({clientID: event.currentTarget.value}); + this.setState({ clientID: event.currentTarget.value }); }; clientSecretChanged: React.ReactEventHandler = (event) => { - this.setState({clientSecret: event.currentTarget.value}); + this.setState({ clientSecret: event.currentTarget.value }); }; urlChanged: React.ReactEventHandler = (event) => { - this.setState({url: event.currentTarget.value}); + this.setState({ url: event.currentTarget.value }); }; caFileChanged = (caFileContent: string) => { - this.setState({caFileContent}); + this.setState({ caFileContent }); }; render() { const { name, clientID, clientSecret, url, caFileContent } = this.state; const title = 'Add Identity Provider: GitLab'; - return
    - - {title} - -
    -

    {title}

    -

    - You can use GitLab integration for users authenticating with GitLab credentials. -

    - -
    - - -

    - The OAuth server base URL. + return ( +

    + + {title} + + +

    {title}

    +

    + You can use GitLab integration for users authenticating with GitLab credentials.

    -
    -
    - - -
    -
    - - -
    - - - - - + - - - -
    ; + + + + +
    + ); } } export type AddGitLabPageState = { name: string; - url: string + url: string; clientID: string; clientSecret: string; caFileContent: string; diff --git a/frontend/public/components/cluster-settings/global-config.tsx b/frontend/public/components/cluster-settings/global-config.tsx index da7ed33421c..37ecb44e67d 100644 --- a/frontend/public/components/cluster-settings/global-config.tsx +++ b/frontend/public/components/cluster-settings/global-config.tsx @@ -31,27 +31,31 @@ const oauthMenuItems = _.map(addIDPItems, (label: string, id: string) => ({ href: `/settings/idp/${id}`, })); -const ItemRow = ({item}) => { +const ItemRow = ({ item }) => { const resourceLink = resourcePathFromModel(item.model, item.name, item.namespace); const apiExplorerLink = `/api-resource/cluster/${referenceForModel(item.model)}`; const menuItems = [ editYAMLMenuItem(item.kind, resourceLink), viewAPIExplorerMenuItem(item.kind, apiExplorerLink), - ...(item.kind === 'OAuth') ? oauthMenuItems : [], + ...(item.kind === 'OAuth' ? oauthMenuItems : []), ]; const description = getResourceDescription(item.model); - return
    -
    - {item.kind} + return ( +
    +
    + + {item.kind} + +
    +
    +
    {description || '-'}
    +
    +
    + +
    -
    -
    {description || '-'}
    -
    -
    - -
    -
    ; + ); }; class GlobalConfigPage_ extends React.Component { @@ -59,7 +63,7 @@ class GlobalConfigPage_ extends React.Component { - return k8sList(model) - .catch(err => { - errorMessage += `${err.message} `; - this.setState({ errorMessage }); - return []; - }).then(items => items.map((i: K8sKind) => ({...i, model}))); - })).then((responses) => { + Promise.all( + this.props.configResources.map((model: K8sKind) => { + return k8sList(model) + .catch((err) => { + errorMessage += `${err.message} `; + this.setState({ errorMessage }); + return []; + }) + .then((items) => items.map((i: K8sKind) => ({ ...i, model }))); + }), + ).then((responses) => { const flattenedResponses = _.flatten(responses); - const winnowedResponses = flattenedResponses.map(item => ({model: item.model, uid: item.metadata.uid, name: item.metadata.name, namespace: item.metadata.namespace, kind: item.kind})); + const winnowedResponses = flattenedResponses.map((item) => ({ + model: item.model, + uid: item.metadata.uid, + name: item.metadata.name, + namespace: item.metadata.namespace, + kind: item.kind, + })); const sortedResponses = _.sortBy(_.flatten(winnowedResponses), 'kind', 'asc'); this.setState({ @@ -91,9 +104,10 @@ class GlobalConfigPage_ extends React.Component ( - flag && (flagPending(flags.get(flag)) || !flags.get(flag)) - )); + const requirementMissing = _.some( + requiredArray, + (flag) => flag && (flagPending(flags.get(flag)) || !flags.get(flag)), + ); return requirementMissing ? null : c.properties; } @@ -101,29 +115,42 @@ class GlobalConfigPage_ extends React.Component this.checkFlags(item)).map(item => item.properties); + const usableConfigs = globalConfigs + .filter((item) => this.checkFlags(item)) + .map((item) => item.properties); const allItems = usableConfigs.length > 0 && items.concat(usableConfigs); - const sortedItems = usableConfigs.length > 0 ? _.sortBy(_.flatten(allItems), 'kind', 'asc') : items; - - return
    - {errorMessage && {errorMessage}} - {loading && } - {!loading && -

    - Edit the following resources to manage the configuration of your cluster. -

    -
    -
    -
    Configuration Resource
    -
    Description
    -
    -
    -
    - { _.map(sortedItems, item => )} -
    -
    -
    } -
    ; + const sortedItems = + usableConfigs.length > 0 ? _.sortBy(_.flatten(allItems), 'kind', 'asc') : items; + + return ( +
    + {errorMessage && ( + + {errorMessage} + + )} + {loading && } + {!loading && ( + +

    + Edit the following resources to manage the configuration of your cluster. +

    +
    +
    +
    Configuration Resource
    +
    Description
    +
    +
    +
    + {_.map(sortedItems, (item) => ( + + ))} +
    +
    + + )} +
    + ); } } @@ -135,9 +162,9 @@ type GlobalConfigPageProps = { }; type GlobalConfigPageState = { - errorMessage: string, - items: any, - loading: boolean, + errorMessage: string; + items: any; + loading: boolean; }; type GlobalConfigObjectProps = { @@ -146,4 +173,4 @@ type GlobalConfigObjectProps = { name: string; namespace: string; uid: string; -} +}; diff --git a/frontend/public/components/cluster-settings/google-idp-form.tsx b/frontend/public/components/cluster-settings/google-idp-form.tsx index 255ba5a13cc..a0df65a6c2a 100644 --- a/frontend/public/components/cluster-settings/google-idp-form.tsx +++ b/frontend/public/components/cluster-settings/google-idp-form.tsx @@ -8,7 +8,6 @@ import { ButtonBar, PromiseComponent, history } from '../utils'; import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; - export class AddGooglePage extends PromiseComponent<{}, AddGooglePageState> { readonly state: AddGooglePageState = { name: 'google', @@ -62,7 +61,7 @@ export class AddGooglePage extends PromiseComponent<{}, AddGooglePageState> { e.preventDefault(); // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { return this.createClientSecret() .then((secret: K8sResourceKind) => this.addGoogleIDP(oauth, secret.metadata.name)) @@ -71,88 +70,97 @@ export class AddGooglePage extends PromiseComponent<{}, AddGooglePageState> { }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; clientIDChanged: React.ReactEventHandler = (event) => { - this.setState({clientID: event.currentTarget.value}); + this.setState({ clientID: event.currentTarget.value }); }; clientSecretChanged: React.ReactEventHandler = (event) => { - this.setState({clientSecret: event.currentTarget.value}); + this.setState({ clientSecret: event.currentTarget.value }); }; hostedDomainChanged: React.ReactEventHandler = (event) => { - this.setState({hostedDomain: event.currentTarget.value}); + this.setState({ hostedDomain: event.currentTarget.value }); }; render() { const { name, clientID, clientSecret, hostedDomain } = this.state; const title = 'Add Identity Provider: Google'; - return
    - - {title} - -
    -

    {title}

    -

    - You can use Google integration for users authenticating with Google credentials. -

    - -
    - - -
    -
    - - -
    -
    - - -

    - Restrict users to a Google App domain. + return ( +

    + + {title} + + +

    {title}

    +

    + You can use Google integration for users authenticating with Google credentials.

    -
    - - - - - - - -
    ; + +
    + + +
    +
    + + +
    +
    + + +

    + Restrict users to a Google App domain. +

    +
    + + + + + + + +
    + ); } } export type AddGooglePageState = { name: string; - hostedDomain: string + hostedDomain: string; clientID: string; clientSecret: string; inProgress: boolean; diff --git a/frontend/public/components/cluster-settings/htpasswd-idp-form.tsx b/frontend/public/components/cluster-settings/htpasswd-idp-form.tsx index bf6a6667b38..2889e4f307f 100644 --- a/frontend/public/components/cluster-settings/htpasswd-idp-form.tsx +++ b/frontend/public/components/cluster-settings/htpasswd-idp-form.tsx @@ -3,22 +3,17 @@ import { Helmet } from 'react-helmet'; import { ActionGroup, Button } from '@patternfly/react-core'; import { SecretModel } from '../../models'; -import { - IdentityProvider, - k8sCreate, - K8sResourceKind, - OAuthKind, -} from '../../module/k8s'; -import { - AsyncComponent, - ButtonBar, - PromiseComponent, - history, -} from '../utils'; +import { IdentityProvider, k8sCreate, K8sResourceKind, OAuthKind } from '../../module/k8s'; +import { AsyncComponent, ButtonBar, PromiseComponent, history } from '../utils'; import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; -export const DroppableFileInput = (props: any) => import('../utils/file-input').then(c => c.DroppableFileInput)} {...props} />; +export const DroppableFileInput = (props: any) => ( + import('../utils/file-input').then((c) => c.DroppableFileInput)} + {...props} + /> +); export class AddHTPasswdPage extends PromiseComponent<{}, AddHTPasswdPageState> { readonly state: AddHTPasswdPageState = { @@ -26,7 +21,7 @@ export class AddHTPasswdPage extends PromiseComponent<{}, AddHTPasswdPageState> htpasswdFileContent: '', inProgress: false, errorMessage: '', - } + }; getOAuthResource(): Promise { return this.handlePromise(getOAuthResource()); @@ -67,68 +62,67 @@ export class AddHTPasswdPage extends PromiseComponent<{}, AddHTPasswdPageState> submit: React.FormEventHandler = (e) => { e.preventDefault(); if (!this.state.htpasswdFileContent) { - this.setState({errorMessage: 'You must specify an HTPasswd file.'}); + this.setState({ errorMessage: 'You must specify an HTPasswd file.' }); return; } // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { return this.createHTPasswdSecret() .then((secret: K8sResourceKind) => this.addHTPasswdIDP(oauth, secret.metadata.name)) .then(redirectToOAuthPage); }); - } + }; nameChanged: React.ReactEventHandler = (e) => { - this.setState({name: e.currentTarget.value}); + this.setState({ name: e.currentTarget.value }); }; htpasswdFileChanged = (htpasswdFileContent: string) => { - this.setState({htpasswdFileContent}); + this.setState({ htpasswdFileContent }); }; render() { const { name, htpasswdFileContent } = this.state; const title = 'Add Identity Provider: HTPasswd'; - return
    - - {title} - -
    -

    {title}

    -

    - HTPasswd validates usernames and passwords against a flat file generated using the htpasswd command. -

    - -
    - -
    - - - - - - - -
    ; + return ( +
    + + {title} + +
    +

    {title}

    +

    + HTPasswd validates usernames and passwords against a flat file generated using the + htpasswd command. +

    + +
    + +
    + + + + + + + +
    + ); } } diff --git a/frontend/public/components/cluster-settings/idp-cafile-input.tsx b/frontend/public/components/cluster-settings/idp-cafile-input.tsx index aeaa8374791..31cbcc27b57 100644 --- a/frontend/public/components/cluster-settings/idp-cafile-input.tsx +++ b/frontend/public/components/cluster-settings/idp-cafile-input.tsx @@ -1,9 +1,18 @@ import * as React from 'react'; import { AsyncComponent } from '../utils'; -const DroppableFileInput = (props: any) => import('../utils/file-input').then(c => c.DroppableFileInput)} {...props} />; +const DroppableFileInput = (props: any) => ( + import('../utils/file-input').then((c) => c.DroppableFileInput)} + {...props} + /> +); -export const IDPCAFileInput: React.FC = ({value, onChange, isRequired=false}) => ( +export const IDPCAFileInput: React.FC = ({ + value, + onChange, + isRequired = false, +}) => (
    = ({value, onChange, id="idp-file-input" label="CA File" isRequired={isRequired} - hideContents /> + hideContents + />
    ); diff --git a/frontend/public/components/cluster-settings/idp-name-input.tsx b/frontend/public/components/cluster-settings/idp-name-input.tsx index 39edec76611..1d7f8fd49f1 100644 --- a/frontend/public/components/cluster-settings/idp-name-input.tsx +++ b/frontend/public/components/cluster-settings/idp-name-input.tsx @@ -1,15 +1,19 @@ import * as React from 'react'; -export const IDPNameInput: React.FC = ({value, onChange}) => ( +export const IDPNameInput: React.FC = ({ value, onChange }) => (
    - - + Name + + + required + />

    Unique name of the new identity provider. This cannot be changed later.

    diff --git a/frontend/public/components/cluster-settings/index.ts b/frontend/public/components/cluster-settings/index.ts index bf07b631626..797010d75b1 100644 --- a/frontend/public/components/cluster-settings/index.ts +++ b/frontend/public/components/cluster-settings/index.ts @@ -1,16 +1,8 @@ import * as _ from 'lodash-es'; import { OAuthModel } from '../../models'; -import { - IdentityProvider, - k8sGet, - k8sPatch, - OAuthKind, -} from '../../module/k8s'; -import { - history, - resourcePathFromModel, -} from '../utils'; +import { IdentityProvider, k8sGet, k8sPatch, OAuthKind } from '../../module/k8s'; +import { history, resourcePathFromModel } from '../utils'; // The name of the cluster-scoped OAuth configuration resource. const OAUTH_RESOURCE_NAME = 'cluster'; diff --git a/frontend/public/components/cluster-settings/keystone-idp-form.tsx b/frontend/public/components/cluster-settings/keystone-idp-form.tsx index d078c384281..66f2528ef92 100644 --- a/frontend/public/components/cluster-settings/keystone-idp-form.tsx +++ b/frontend/public/components/cluster-settings/keystone-idp-form.tsx @@ -10,7 +10,12 @@ import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; import { IDPCAFileInput } from './idp-cafile-input'; -export const DroppableFileInput = (props: any) => import('../utils/file-input').then(c => c.DroppableFileInput)} {...props} />; +export const DroppableFileInput = (props: any) => ( + import('../utils/file-input').then((c) => c.DroppableFileInput)} + {...props} + /> +); export class AddKeystonePage extends PromiseComponent<{}, AddKeystonePageState> { readonly state: AddKeystonePageState = { @@ -104,127 +109,136 @@ export class AddKeystonePage extends PromiseComponent<{}, AddKeystonePageState> submit: React.FormEventHandler = (e) => { e.preventDefault(); if (_.isEmpty(this.state.keyFileContent) !== _.isEmpty(this.state.certFileContent)) { - this.setState({errorMessage: 'Values for Certificate and Key should both be either excluded or provided.'}); + this.setState({ + errorMessage: 'Values for Certificate and Key should both be either excluded or provided.', + }); return; } // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { - const promises = [ - this.createTLSSecret(), - this.createCAConfigMap(), - ]; - - Promise.all(promises).then(([tlsSecret, configMap]) => { - const caName = configMap ? configMap.metadata.name : ''; - const secretName = tlsSecret ? tlsSecret.metadata.name: ''; - return this.addKeystoneIDP(oauth, secretName, caName); - }).then(redirectToOAuthPage); + const promises = [this.createTLSSecret(), this.createCAConfigMap()]; + + Promise.all(promises) + .then(([tlsSecret, configMap]) => { + const caName = configMap ? configMap.metadata.name : ''; + const secretName = tlsSecret ? tlsSecret.metadata.name : ''; + return this.addKeystoneIDP(oauth, secretName, caName); + }) + .then(redirectToOAuthPage); }); }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; domainNameChanged: React.ReactEventHandler = (event) => { - this.setState({domainName: event.currentTarget.value}); + this.setState({ domainName: event.currentTarget.value }); }; urlChanged: React.ReactEventHandler = (event) => { - this.setState({url: event.currentTarget.value}); + this.setState({ url: event.currentTarget.value }); }; caFileChanged = (caFileContent: string) => { - this.setState({caFileContent}); + this.setState({ caFileContent }); }; certFileChanged = (certFileContent: string) => { - this.setState({certFileContent}); + this.setState({ certFileContent }); }; keyFileChanged = (keyFileContent: string) => { - this.setState({keyFileContent}); + this.setState({ keyFileContent }); }; render() { const { name, domainName, url, caFileContent, certFileContent, keyFileContent } = this.state; const title = 'Add Identity Provider: Keystone Authentication'; - return
    - - {title} - -
    -

    {title}

    -

    - Adding Keystone enables shared authentication with an OpenStack server configured to store users in an internal Keystone database. -

    - -
    - - -
    -
    - - -

    - The remote URL to connect to. + return ( +

    + + {title} + + +

    {title}

    +

    + Adding Keystone enables shared authentication with an OpenStack server configured to + store users in an internal Keystone database.

    -
    - -
    - -
    -
    - -
    - - - - - - - -
    ; + +
    + + +
    +
    + + +

    + The remote URL to connect to. +

    +
    + +
    + +
    +
    + +
    + + + + + + + +
    + ); } } export type AddKeystonePageState = { name: string; domainName: string; - url: string + url: string; caFileContent: string; certFileContent: string; keyFileContent: string; diff --git a/frontend/public/components/cluster-settings/ldap-idp-form.tsx b/frontend/public/components/cluster-settings/ldap-idp-form.tsx index 014c63a7f9b..aef850e4746 100644 --- a/frontend/public/components/cluster-settings/ldap-idp-form.tsx +++ b/frontend/public/components/cluster-settings/ldap-idp-form.tsx @@ -5,12 +5,7 @@ import { ActionGroup, Button } from '@patternfly/react-core'; import { ConfigMapModel, SecretModel } from '../../models'; import { IdentityProvider, k8sCreate, K8sResourceKind, OAuthKind } from '../../module/k8s'; -import { - ButtonBar, - ListInput, - PromiseComponent, - history, -} from '../utils'; +import { ButtonBar, ListInput, PromiseComponent, history } from '../utils'; import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; import { IDPCAFileInput } from './idp-cafile-input'; @@ -28,7 +23,7 @@ export class AddLDAPPage extends PromiseComponent<{}, AddLDAPPageState> { caFileContent: '', inProgress: false, errorMessage: '', - } + }; getOAuthResource(): Promise { return this.handlePromise(getOAuthResource()); @@ -76,8 +71,20 @@ export class AddLDAPPage extends PromiseComponent<{}, AddLDAPPageState> { return this.handlePromise(k8sCreate(SecretModel, secret)); } - addLDAPIDP(oauth: OAuthKind, bindPasswordSecretName: string, caConfigMapName: string): Promise { - const { name, url, bindDN, attributesID, attributesPreferredUsername, attributesName, attributesEmail } = this.state; + addLDAPIDP( + oauth: OAuthKind, + bindPasswordSecretName: string, + caConfigMapName: string, + ): Promise { + const { + name, + url, + bindDN, + attributesID, + attributesPreferredUsername, + attributesName, + attributesEmail, + } = this.state; const idp: IdentityProvider = { name, mappingMethod: 'claim', @@ -116,134 +123,169 @@ export class AddLDAPPage extends PromiseComponent<{}, AddLDAPPageState> { submit: React.FormEventHandler = (e) => { e.preventDefault(); // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { - const promises = [ - this.createBindPasswordSecret(), - this.createCAConfigMap(), - ]; + const promises = [this.createBindPasswordSecret(), this.createCAConfigMap()]; - Promise.all(promises).then(([bindPasswordSecret, caConfigMap]) => { - const bindPasswordSecretName = _.get(bindPasswordSecret, 'metadata.name'); - const caConfigMapName = _.get(caConfigMap, 'metadata.name'); - return this.addLDAPIDP(oauth, bindPasswordSecretName, caConfigMapName); - }).then(redirectToOAuthPage); + Promise.all(promises) + .then(([bindPasswordSecret, caConfigMap]) => { + const bindPasswordSecretName = _.get(bindPasswordSecret, 'metadata.name'); + const caConfigMapName = _.get(caConfigMap, 'metadata.name'); + return this.addLDAPIDP(oauth, bindPasswordSecretName, caConfigMapName); + }) + .then(redirectToOAuthPage); }); - } + }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; urlChanged: React.ReactEventHandler = (event) => { - this.setState({url: event.currentTarget.value}); + this.setState({ url: event.currentTarget.value }); }; bindDNChanged: React.ReactEventHandler = (event) => { - this.setState({bindDN: event.currentTarget.value}); + this.setState({ bindDN: event.currentTarget.value }); }; bindPasswordChanged: React.ReactEventHandler = (event) => { - this.setState({bindPassword: event.currentTarget.value}); + this.setState({ bindPassword: event.currentTarget.value }); }; attributesIDChanged = (attributesID: string[]) => { - this.setState({attributesID}); + this.setState({ attributesID }); }; attributesPreferredUsernameChanged = (attributesPreferredUsername: string[]) => { - this.setState({attributesPreferredUsername}); + this.setState({ attributesPreferredUsername }); }; attributesNameChanged = (attributesName: string[]) => { - this.setState({attributesName}); + this.setState({ attributesName }); }; attributesEmailChanged = (attributesEmail: string[]) => { - this.setState({attributesEmail}); + this.setState({ attributesEmail }); }; caFileChanged = (caFileContent: string) => { - this.setState({caFileContent}); + this.setState({ caFileContent }); }; render() { - const { name, url, bindDN, bindPassword, attributesID, attributesPreferredUsername, attributesName, caFileContent } = this.state; + const { + name, + url, + bindDN, + bindPassword, + attributesID, + attributesPreferredUsername, + attributesName, + caFileContent, + } = this.state; const title = 'Add Identity Provider: LDAP'; - return
    - - {title} - -
    -

    {title}

    -

    - Integrate with an LDAP identity provider. -

    - -
    - - -
    - An RFC 2255 URL which specifies the LDAP search parameters to use. + return ( +
    + + {title} + + +

    {title}

    +

    Integrate with an LDAP identity provider.

    + +
    + + +
    + An RFC 2255 URL which specifies the LDAP search parameters to use. +
    -
    -
    - - -
    - DN to bind with during the search phase. +
    + + +
    + DN to bind with during the search phase. +
    -
    -
    - - -
    - Password to bind with during the search phase. +
    + + +
    + Password to bind with during the search phase. +
    -
    -
    -

    Attributes

    -

    Attributes map LDAP attributes to identities.

    - - - - -
    -

    More Options

    - - - - - - - - -
    ; +
    +

    Attributes

    +

    Attributes map LDAP attributes to identities.

    + + + + +
    +

    More Options

    + + + + + + + + +
    + ); } } diff --git a/frontend/public/components/cluster-settings/oauth.tsx b/frontend/public/components/cluster-settings/oauth.tsx index be3c61438db..2987e570a28 100644 --- a/frontend/public/components/cluster-settings/oauth.tsx +++ b/frontend/public/components/cluster-settings/oauth.tsx @@ -21,12 +21,14 @@ const menuActions = [...Kebab.getExtensionsActionsForKind(OAuthModel), ...common const oAuthReference = referenceForModel(OAuthModel); // Convert to ms for formatDuration -const tokenDuration = (seconds: number) => _.isNil(seconds) ? '-' : formatDuration(seconds * 1000); +const tokenDuration = (seconds: number) => + _.isNil(seconds) ? '-' : formatDuration(seconds * 1000); -const IdentityProviders: React.SFC = ({identityProviders}) => { - return _.isEmpty(identityProviders) - ? - :
    +const IdentityProviders: React.SFC = ({ identityProviders }) => { + return _.isEmpty(identityProviders) ? ( + + ) : ( +
    @@ -36,7 +38,7 @@ const IdentityProviders: React.SFC = ({identityProviders - {_.map(identityProviders, idp => ( + {_.map(identityProviders, (idp) => ( @@ -45,7 +47,8 @@ const IdentityProviders: React.SFC = ({identityProviders ))}
    {idp.name} {idp.type}
    -
    ; +
    + ); }; export const addIDPItems = Object.freeze({ @@ -60,37 +63,42 @@ export const addIDPItems = Object.freeze({ requestheader: 'Request Header', }); -const OAuthDetails: React.SFC = ({obj}: {obj: OAuthKind}) => { +const OAuthDetails: React.SFC = ({ obj }: { obj: OAuthKind }) => { const { identityProviders, tokenConfig } = obj.spec; - return -
    - - - {tokenConfig && -
    Access Token Max Age
    -
    {tokenDuration(tokenConfig.accessTokenMaxAgeSeconds)}
    -
    } -
    -
    -
    - -

    - Identity providers determine how users log into the cluster. -

    - history.push(`/settings/idp/${name}`)} /> - -
    -
    ; + return ( + +
    + + + {tokenConfig && ( + +
    Access Token Max Age
    +
    {tokenDuration(tokenConfig.accessTokenMaxAgeSeconds)}
    +
    + )} +
    +
    +
    + +

    + Identity providers determine how users log into the cluster. +

    + history.push(`/settings/idp/${name}`)} + /> + +
    +
    + ); }; -export const OAuthDetailsPage: React.SFC = props => ( +export const OAuthDetailsPage: React.SFC = (props) => ( { return this.handlePromise(k8sCreate(ConfigMapModel, ca)); } - addOpenIDIDP(oauth: OAuthKind, clientSecretName: string, caName: string): Promise { - const { name, clientID, issuer, extraScopes, claimPreferredUsernames, claimNames, claimEmails } = this.state; + addOpenIDIDP( + oauth: OAuthKind, + clientSecretName: string, + caName: string, + ): Promise { + const { + name, + clientID, + issuer, + extraScopes, + claimPreferredUsernames, + claimNames, + claimEmails, + } = this.state; const idp: IdentityProvider = { name, type: 'OpenID', @@ -110,128 +112,167 @@ export class AddOpenIDPage extends PromiseComponent<{}, AddOpenIDIDPPageState> { e.preventDefault(); // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { - const promises = [ - this.createClientSecret(), - this.createCAConfigMap(), - ]; - - Promise.all(promises).then(([secret, configMap]) => { - const caName = configMap ? configMap.metadata.name : ''; - return this.addOpenIDIDP(oauth, secret.metadata.name, caName); - }).then(redirectToOAuthPage); + const promises = [this.createClientSecret(), this.createCAConfigMap()]; + + Promise.all(promises) + .then(([secret, configMap]) => { + const caName = configMap ? configMap.metadata.name : ''; + return this.addOpenIDIDP(oauth, secret.metadata.name, caName); + }) + .then(redirectToOAuthPage); }); }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; clientIDChanged: React.ReactEventHandler = (event) => { - this.setState({clientID: event.currentTarget.value}); + this.setState({ clientID: event.currentTarget.value }); }; clientSecretChanged: React.ReactEventHandler = (event) => { - this.setState({clientSecret: event.currentTarget.value}); + this.setState({ clientSecret: event.currentTarget.value }); }; issuerChanged: React.ReactEventHandler = (event) => { - this.setState({issuer: event.currentTarget.value}); + this.setState({ issuer: event.currentTarget.value }); }; claimPreferredUsernamesChanged = (claimPreferredUsernames: string[]) => { - this.setState({claimPreferredUsernames}); + this.setState({ claimPreferredUsernames }); }; claimNamesChanged = (claimNames: string[]) => { - this.setState({claimNames}); + this.setState({ claimNames }); }; claimEmailsChanged = (claimEmails: string[]) => { - this.setState({claimEmails}); + this.setState({ claimEmails }); }; extraScopesChanged = (extraScopes: string[]) => { - this.setState({extraScopes}); + this.setState({ extraScopes }); }; caFileChanged = (caFileContent: string) => { - this.setState({caFileContent}); + this.setState({ caFileContent }); }; render() { - const { name, clientID, clientSecret, issuer, claimPreferredUsernames, claimNames, claimEmails, caFileContent } = this.state; + const { + name, + clientID, + clientSecret, + issuer, + claimPreferredUsernames, + claimNames, + claimEmails, + caFileContent, + } = this.state; const title = 'Add Identity Provider: OpenID Connect'; - return
    - - {title} - -
    -

    {title}

    -

    - Integrate with an OpenID Connect identity provider using an Authorization Code Flow. -

    - -
    - - -
    -
    - - -
    -
    - - -
    - The URL that the OpenID Provider asserts as its Issuer Identifier. - It must use the https scheme with no URL query parameters or fragment. + return ( +
    + + {title} + + +

    {title}

    +

    + Integrate with an OpenID Connect identity provider using an Authorization Code Flow. +

    + +
    + + +
    +
    + + +
    +
    + + +
    + The URL that the OpenID Provider asserts as its Issuer Identifier. It must use the + https scheme with no URL query parameters or fragment. +
    -
    -
    -

    Claims

    -

    Claims map metadata from the OpenID provider to an OpenShift user. The first non-empty claim is used.

    - - - -
    -

    More Options

    - - - - - - - - - -
    ; +
    +

    Claims

    +

    + Claims map metadata from the OpenID provider to an OpenShift user. The first non-empty + claim is used. +

    + + + +
    +

    More Options

    + + + + + + + + + +
    + ); } } diff --git a/frontend/public/components/cluster-settings/request-header-idp-form.tsx b/frontend/public/components/cluster-settings/request-header-idp-form.tsx index 4001a1197d7..c3c6595313b 100644 --- a/frontend/public/components/cluster-settings/request-header-idp-form.tsx +++ b/frontend/public/components/cluster-settings/request-header-idp-form.tsx @@ -4,17 +4,11 @@ import { ActionGroup, Button } from '@patternfly/react-core'; import { ConfigMapModel } from '../../models'; import { IdentityProvider, k8sCreate, K8sResourceKind, OAuthKind } from '../../module/k8s'; -import { - ButtonBar, - ListInput, - PromiseComponent, - history, -} from '../utils'; +import { ButtonBar, ListInput, PromiseComponent, history } from '../utils'; import { addIDP, getOAuthResource, redirectToOAuthPage } from './'; import { IDPNameInput } from './idp-name-input'; import { IDPCAFileInput } from './idp-cafile-input'; - export class AddRequestHeaderPage extends PromiseComponent<{}, AddRequestHeaderPageState> { readonly state: AddRequestHeaderPageState = { name: 'request-header', @@ -56,7 +50,16 @@ export class AddRequestHeaderPage extends PromiseComponent<{}, AddRequestHeaderP } addRequestHeaderIDP(oauth: OAuthKind, caName: string): Promise { - const { name, loginURL, challengeURL, clientCommonNames, headers, preferredUsernameHeaders, nameHeaders, emailHeaders } = this.state; + const { + name, + loginURL, + challengeURL, + clientCommonNames, + headers, + preferredUsernameHeaders, + nameHeaders, + emailHeaders, + } = this.state; const idp: IdentityProvider = { name, type: 'RequestHeader', @@ -81,122 +84,151 @@ export class AddRequestHeaderPage extends PromiseComponent<{}, AddRequestHeaderP submit: React.FormEventHandler = (e) => { e.preventDefault(); if (!this.state.caFileContent) { - this.setState({errorMessage: 'You must specify a CA File.'}); + this.setState({ errorMessage: 'You must specify a CA File.' }); return; } // Clear any previous errors. - this.setState({errorMessage: ''}); + this.setState({ errorMessage: '' }); this.getOAuthResource().then((oauth: OAuthKind) => { return this.createCAConfigMap() - .then((configMap: K8sResourceKind) => this.addRequestHeaderIDP(oauth, configMap.metadata.name)) + .then((configMap: K8sResourceKind) => + this.addRequestHeaderIDP(oauth, configMap.metadata.name), + ) .then(redirectToOAuthPage); }); }; nameChanged: React.ReactEventHandler = (event) => { - this.setState({name: event.currentTarget.value}); + this.setState({ name: event.currentTarget.value }); }; challengeURLChanged: React.ReactEventHandler = (event) => { - this.setState({challengeURL: event.currentTarget.value}); + this.setState({ challengeURL: event.currentTarget.value }); }; loginURLChanged: React.ReactEventHandler = (event) => { - this.setState({loginURL: event.currentTarget.value}); + this.setState({ loginURL: event.currentTarget.value }); }; clientCommonNamesChanged = (clientCommonNames: string[]) => { - this.setState({clientCommonNames}); + this.setState({ clientCommonNames }); }; headersChanged = (headers: string[]) => { - this.setState({headers}); + this.setState({ headers }); }; preferredUsernameHeadersChanged = (preferredUsernameHeaders: string[]) => { - this.setState({preferredUsernameHeaders}); + this.setState({ preferredUsernameHeaders }); }; nameHeadersChanged = (nameHeaders: string[]) => { - this.setState({nameHeaders}); + this.setState({ nameHeaders }); }; emailHeadersChanged = (emailHeaders: string[]) => { - this.setState({emailHeaders}); + this.setState({ emailHeaders }); }; caFileChanged = (caFileContent: string) => { - this.setState({caFileContent}); + this.setState({ caFileContent }); }; render() { const { name, challengeURL, loginURL, caFileContent } = this.state; const title = 'Add Identity Provider: Request Header'; - return
    - - {title} - -
    -

    {title}

    -

    - Use request header to identify users from request header values. It is typically used in combination with an authenticating proxy, which sets the request header value. -

    - -
    -

    URLs

    -

    - At least one URL must be provided. -

    -
    - - -
    - The URL to redirect unauthenticated requests from OAuth clients which expect interactive logins. + return ( +
    + + {title} + + +

    {title}

    +

    + Use request header to identify users from request header values. It is typically used in + combination with an authenticating proxy, which sets the request header value. +

    + +
    +

    URLs

    +

    At least one URL must be provided.

    +
    + + +
    + The URL to redirect unauthenticated requests from OAuth clients which expect + interactive logins. +
    -
    -
    - - -
    - The URL to redirect unauthenticated requests from OAuth clients which expect WWW-Authenticate challenges. +
    + + +
    + The URL to redirect unauthenticated requests from OAuth clients which expect + WWW-Authenticate challenges. +
    -
    -
    -

    More Options

    - - - - - - - - - - - - - -
    ; +
    +

    More Options

    + + + + + + + + + + + + + +
    + ); } } diff --git a/frontend/public/components/command-line-tools.tsx b/frontend/public/components/command-line-tools.tsx index b734455f563..45d0dac6380 100644 --- a/frontend/public/components/command-line-tools.tsx +++ b/frontend/public/components/command-line-tools.tsx @@ -9,67 +9,114 @@ import { ConsoleCLIDownloadModel } from '../models'; import { referenceForModel } from '../module/k8s'; import { SyncMarkdownView } from './markdown-view'; -const CommandLineTools: React.FC = ({obj}) => { +const CommandLineTools: React.FC = ({ obj }) => { const title = 'Command Line Tools'; - const additionalCommandLineTools = _.map(_.sortBy(_.get(obj, 'data'), 'spec.displayName'), (tool) => { - const displayName = tool.spec.displayName; - const defaultLinkText = `Download ${displayName}`; - return -
    -

    {displayName}

    - - {tool.spec.links.length === 1 &&

    } - {tool.spec.links.length > 1 &&
      - {_.map(tool.spec.links, (link, i) =>
    • )} -
    } -
    ; - }); + const additionalCommandLineTools = _.map( + _.sortBy(_.get(obj, 'data'), 'spec.displayName'), + (tool) => { + const displayName = tool.spec.displayName; + const defaultLinkText = `Download ${displayName}`; + return ( + +
    +

    + {displayName} +

    + + {tool.spec.links.length === 1 && ( +

    + +

    + )} + {tool.spec.links.length > 1 && ( +
      + {_.map(tool.spec.links, (link, i) => ( +
    • + +
    • + ))} +
    + )} +
    + ); + }, + ); - return - - {title} - -
    -

    -
    - {title} -
    -

    -

    oc - OpenShift Command Line Interface (CLI)

    -

    With the OpenShift command line interface, you can create applications and manage OpenShift projects from a terminal.

    -

    The oc binary offers the same capabilities as the kubectl binary, but it is further extended to natively support OpenShift Container Platform features.

    -

    - - {(window as any).SERVER_FLAGS.requestTokenURL && ( - -   -   - - )} -

    -
    -

    odo - Developer-focused CLI for OpenShift

    -

    OpenShift Do (odo) is a fast, iterative, and straightforward CLI tool for developers who write, build, and deploy applications on OpenShift.

    -

    odo abstracts away complex Kubernetes and OpenShift concepts, thus allowing developers to focus on what is most important to them: code.

    -

    - {additionalCommandLineTools} -
    -
    ; + return ( + + + {title} + +
    +

    +
    {title}
    +

    +

    oc - OpenShift Command Line Interface (CLI)

    +

    + With the OpenShift command line interface, you can create applications and manage + OpenShift projects from a terminal. +

    +

    + The oc binary offers the same capabilities as the kubectl binary, but it is further + extended to natively support OpenShift Container Platform features. +

    +

    + + {(window as any).SERVER_FLAGS.requestTokenURL && ( + +   + +   + + + )} +

    +
    +

    odo - Developer-focused CLI for OpenShift

    +

    + OpenShift Do (odo) is a fast, iterative, and straightforward CLI tool for developers who + write, build, and deploy applications on OpenShift. +

    +

    + odo abstracts away complex Kubernetes and OpenShift concepts, thus allowing developers to + focus on what is most important to them: code. +

    +

    + +

    + {additionalCommandLineTools} +
    +
    + ); }; -export const CommandLineToolsPage = connectToFlags(FLAGS.CONSOLE_CLI_DOWNLOAD)(({ flags, ...props }) => { - const resources = flags[FLAGS.CONSOLE_CLI_DOWNLOAD] - ? [{ - kind: referenceForModel(ConsoleCLIDownloadModel), - isList: true, - prop: 'obj', - }] - : []; +export const CommandLineToolsPage = connectToFlags(FLAGS.CONSOLE_CLI_DOWNLOAD)( + ({ flags, ...props }) => { + const resources = flags[FLAGS.CONSOLE_CLI_DOWNLOAD] + ? [ + { + kind: referenceForModel(ConsoleCLIDownloadModel), + isList: true, + prop: 'obj', + }, + ] + : []; - return - - ; -}); + return ( + + + + ); + }, +); type CommandLineToolsProps = { obj: FirehoseResult; diff --git a/frontend/public/components/conditions.tsx b/frontend/public/components/conditions.tsx index 88f411e458b..8d166f789f7 100644 --- a/frontend/public/components/conditions.tsx +++ b/frontend/public/components/conditions.tsx @@ -4,44 +4,48 @@ import * as _ from 'lodash-es'; import { Timestamp } from './utils'; import { CamelCaseWrap } from './utils/camel-case-wrap'; -export const Conditions: React.SFC = ({conditions}) => { - const rows = _.map(conditions, (condition, i) =>
    -
    - -
    -
    - {condition.status} -
    -
    - -
    -
    - -
    - {/* remove initial newline which appears in route messages */} -
    - {_.trim(condition.message) || '-'} +export const Conditions: React.SFC = ({ conditions }) => { + const rows = _.map(conditions, (condition, i) => ( +
    +
    + +
    +
    + {condition.status} +
    +
    + +
    +
    + +
    + {/* remove initial newline which appears in route messages */} +
    + {_.trim(condition.message) || '-'} +
    -
    ); + )); - return - {conditions - ?
    -
    -
    Type
    -
    Status
    -
    Updated
    -
    Reason
    -
    Message
    + return ( + + {conditions ? ( +
    +
    +
    Type
    +
    Status
    +
    Updated
    +
    Reason
    +
    Message
    +
    +
    {rows}
    -
    - {rows} + ) : ( +
    +
    No Conditions Found
    -
    - :
    -
    No Conditions Found
    -
    } -
    ; + )} + + ); }; export type conditionProps = { diff --git a/frontend/public/components/configmap-and-secret-data.tsx b/frontend/public/components/configmap-and-secret-data.tsx index 0d8e0999a13..e11f995cd29 100644 --- a/frontend/public/components/configmap-and-secret-data.tsx +++ b/frontend/public/components/configmap-and-secret-data.tsx @@ -5,10 +5,12 @@ import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons'; import { CopyToClipboard, EmptyBox, SectionHeading } from './utils'; -export const MaskedData: React.FC<{}> = () => - Value hidden - -; +export const MaskedData: React.FC<{}> = () => ( + + Value hidden + + +); const downloadBinary = (key, value) => { const rawBinary = window.atob(value); @@ -17,33 +19,51 @@ const downloadBinary = (key, value) => { for (let i = 0; i < rawBinaryLength; i++) { array[i] = rawBinary.charCodeAt(i); } - const blob = new Blob([array], {type: 'data:application/octet-stream;'}); + const blob = new Blob([array], { type: 'data:application/octet-stream;' }); saveAs(blob, key); }; -export const ConfigMapBinaryData: React.FC = ({data}) => { +export const ConfigMapBinaryData: React.FC = ({ data }) => { const dl = []; - Object.keys(data || {}).sort().forEach(k => { - const value = data[k]; - dl.push(
    {k}
    ); - dl.push(
    ); - }); + Object.keys(data || {}) + .sort() + .forEach((k) => { + const value = data[k]; + dl.push(
    {k}
    ); + dl.push( +
    + +
    , + ); + }); return dl.length ?
    {dl}
    : ; }; ConfigMapBinaryData.displayName = 'ConfigMapBinaryData'; -export const ConfigMapData: React.FC = ({data, label}) => { +export const ConfigMapData: React.FC = ({ data, label }) => { const dl = []; - Object.keys(data || {}).sort().forEach(k => { - const value = data[k]; - dl.push(
    {k}
    ); - dl.push(
    ); - }); + Object.keys(data || {}) + .sort() + .forEach((k) => { + const value = data[k]; + dl.push(
    {k}
    ); + dl.push( +
    + +
    , + ); + }); return dl.length ?
    {dl}
    : ; }; ConfigMapData.displayName = 'ConfigMapData'; -export const SecretValue: React.FC = ({value, reveal, encoded = true}) => { +export const SecretValue: React.FC = ({ value, reveal, encoded = true }) => { if (!value) { return No value; } @@ -54,25 +74,39 @@ export const SecretValue: React.FC = ({value, reveal, encoded }; SecretValue.displayName = 'SecretValue'; -export const SecretData: React.FC = ({data}) => { +export const SecretData: React.FC = ({ data }) => { const [reveal, setReveal] = React.useState(false); const dl = []; - Object.keys(data || {}).sort().forEach(k => { - dl.push(
    {k}
    ); - dl.push(
    ); - }); + Object.keys(data || {}) + .sort() + .forEach((k) => { + dl.push(
    {k}
    ); + dl.push( +
    + +
    , + ); + }); return ( - {dl.length - ? - : null} + ) : null} {dl.length ?
    {dl}
    : }
    diff --git a/frontend/public/components/configmap.jsx b/frontend/public/components/configmap.jsx index 341fcdc8bef..c63f59d0623 100644 --- a/frontend/public/components/configmap.jsx +++ b/frontend/public/components/configmap.jsx @@ -4,14 +4,18 @@ import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { ConfigMapData, ConfigMapBinaryData } from './configmap-and-secret-data'; -import { Kebab, SectionHeading, navFactory, ResourceKebab, ResourceLink, ResourceSummary } from './utils'; +import { + Kebab, + SectionHeading, + navFactory, + ResourceKebab, + ResourceLink, + ResourceSummary, +} from './utils'; import { fromNow } from './utils/datetime'; import { ConfigMapModel } from '../models'; -const menuActions = [ - ...Kebab.getExtensionsActionsForKind(ConfigMapModel), - ...Kebab.factory.common, -]; +const menuActions = [...Kebab.getExtensionsActionsForKind(ConfigMapModel), ...Kebab.factory.common]; const kind = 'ConfigMap'; @@ -26,36 +30,54 @@ const tableColumnClasses = [ const ConfigMapTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Size', sortFunc: 'dataSize', transforms: [sortable], + title: 'Size', + sortFunc: 'dataSize', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: '', props: { className: tableColumnClasses[4] }, + title: '', + props: { className: tableColumnClasses[4] }, }, ]; }; ConfigMapTableHeader.displayName = 'ConfigMapTableHeader'; -const ConfigMapTableRow = ({obj: configMap, index, key, style}) => { +const ConfigMapTableRow = ({ obj: configMap, index, key, style }) => { return ( - + - + {_.size(configMap.data) + _.size(configMap.binaryData)} @@ -71,31 +93,44 @@ const ConfigMapTableRow = ({obj: configMap, index, key, style}) => { }; ConfigMapTableRow.displayName = 'ConfigMapTableRow'; - -const ConfigMapDetails = ({obj: configMap}) => { - return -
    - - -
    -
    - - -
    -
    - - -
    -
    ; +const ConfigMapDetails = ({ obj: configMap }) => { + return ( + +
    + + +
    +
    + + +
    +
    + + +
    +
    + ); }; -const ConfigMaps = props => ; +const ConfigMaps = (props) => ( +
    +); -const ConfigMapsPage = props => ; -const ConfigMapsDetailsPage = props => ; +const ConfigMapsPage = (props) => ( + +); +const ConfigMapsDetailsPage = (props) => ( + +); -export {ConfigMaps, ConfigMapsPage, ConfigMapsDetailsPage}; +export { ConfigMaps, ConfigMapsPage, ConfigMapsDetailsPage }; diff --git a/frontend/public/components/console-notifier.tsx b/frontend/public/components/console-notifier.tsx index 550e71ba094..4f9536baf3a 100644 --- a/frontend/public/components/console-notifier.tsx +++ b/frontend/public/components/console-notifier.tsx @@ -7,47 +7,64 @@ import { Firehose, FirehoseResult } from './utils'; import { referenceForModel } from '../module/k8s'; import { ConsoleNotificationModel } from '../models/index'; -const ConsoleNotifier_: React.FC = ({obj, location}) => { +const ConsoleNotifier_: React.FC = ({ obj, location }) => { if (_.isEmpty(obj)) { return null; } - return - {_.map(_.get(obj, 'data'), notification => (notification.spec.location === location || notification.spec.location === 'BannerTopBottom') - ?
    -
    -

    - {notification.spec.text} {_.get(notification.spec, ['link', 'href']) - && {notification.spec.link.text || 'More info'}} -

    -
    -
    - : null)} -
    ; + return ( + + {_.map(_.get(obj, 'data'), (notification) => + notification.spec.location === location || + notification.spec.location === 'BannerTopBottom' ? ( +
    +
    +

    + {notification.spec.text}{' '} + {_.get(notification.spec, ['link', 'href']) && ( + + {notification.spec.link.text || 'More info'} + + )} +

    +
    +
    + ) : null, + )} +
    + ); }; ConsoleNotifier_.displayName = 'ConsoleNotifier_'; export const ConsoleNotifier = connectToFlags(FLAGS.CONSOLE_NOTIFICATION)(({ flags, ...props }) => { const resources = flags[FLAGS.CONSOLE_NOTIFICATION] - ? [{ - kind: referenceForModel(ConsoleNotificationModel), - isList: true, - prop: 'obj', - }] + ? [ + { + kind: referenceForModel(ConsoleNotificationModel), + isList: true, + prop: 'obj', + }, + ] : []; - return - - ; + return ( + + + + ); }); ConsoleNotifier.displayName = 'ConsoleNotifier'; diff --git a/frontend/public/components/container-selector.tsx b/frontend/public/components/container-selector.tsx index d2e62bab1ff..a02ab1f25a2 100644 --- a/frontend/public/components/container-selector.tsx +++ b/frontend/public/components/container-selector.tsx @@ -3,15 +3,23 @@ import * as React from 'react'; import { ContainerSpec } from '../module/k8s'; import { Checkbox } from '@patternfly/react-core'; -export const ContainerSelector: React.FC = ({ containers, onChange, selected }) =>
    - {containers.map((container: ContainerSpec) => )} -
    ; +export const ContainerSelector: React.FC = ({ + containers, + onChange, + selected, +}) => ( +
    + {containers.map((container: ContainerSpec) => ( + + ))} +
    +); export type ContainerSelectorProps = { containers: ContainerSpec[]; diff --git a/frontend/public/components/container.tsx b/frontend/public/components/container.tsx index 624108a91ca..0f3bd4f7564 100644 --- a/frontend/public/components/container.tsx +++ b/frontend/public/components/container.tsx @@ -15,11 +15,7 @@ import { VolumeMount, } from '../module/k8s'; import * as k8sProbe from '../module/k8s/probe'; -import { - getContainerState, - getContainerStatus, - getPullPolicyLabel, -} from '../module/k8s/container'; +import { getContainerState, getContainerStatus, getPullPolicyLabel } from '../module/k8s/container'; import { Firehose, HorizontalNav, @@ -33,7 +29,8 @@ import { } from './utils'; import { resourcePath } from './utils/resource-link'; -const formatComputeResources = (resources: ResourceList) => _.map(resources, (v, k) => `${k}: ${v}`).join(', '); +const formatComputeResources = (resources: ResourceList) => + _.map(resources, (v, k) => `${k}: ${v}`).join(', '); const getResourceRequestsValue = (container: ContainerSpec) => { const requests: ResourceList = _.get(container, 'resources.requests'); @@ -45,21 +42,32 @@ const getResourceLimitsValue = (container: ContainerSpec) => { return formatComputeResources(limits); }; -const Lifecycle: React.FC = ({lifecycle}) => { +const Lifecycle: React.FC = ({ lifecycle }) => { const fields = lifecycle && k8sProbe.mapLifecycleConfigToFields(lifecycle); const postStart = _.get(fields, 'postStart.cmd'); const preStop = _.get(fields, 'preStop.cmd'); - const label = (stage: ContainerLifecycleStage) => lifecycle && k8sProbe.getLifecycleHookLabel(lifecycle, stage); - return
    - {postStart &&
    PostStart: {label('postStart')} {postStart}
    } - {preStop &&
    PreStop: {label('preStop')} {preStop}
    } - {!postStart && !preStop && '-'} -
    ; + const label = (stage: ContainerLifecycleStage) => + lifecycle && k8sProbe.getLifecycleHookLabel(lifecycle, stage); + return ( +
    + {postStart && ( +
    + PostStart: {label('postStart')} {postStart} +
    + )} + {preStop && ( +
    + PreStop: {label('preStop')} {preStop} +
    + )} + {!postStart && !preStop && '-'} +
    + ); }; Lifecycle.displayName = 'Lifecycle'; -const Probe: React.FC = ({probe, podIP}) => { +const Probe: React.FC = ({ probe, podIP }) => { const label = probe && k8sProbe.getActionLabelFromObject(probe); const value = probe && _.get(k8sProbe.mapProbeToFields(probe, podIP), 'cmd'); if (!value) { @@ -67,58 +75,94 @@ const Probe: React.FC = ({probe, podIP}) => { } const isMultiline = value.indexOf('\n') !== -1; const formattedValue = isMultiline ?
    {value}
    : {value}; - return {label} {formattedValue}; + return ( + + {label} {formattedValue} + + ); }; Probe.displayName = 'Probe'; -const Ports: React.FC = ({ports}) => { +const Ports: React.FC = ({ ports }) => { if (!ports || !ports.length) { - return ; + return ( + + ); } - return
    - - - - - - - - {ports.map((p: ContainerPort, i: number) => - - - )} - -
    NameContainer
    {p.name || '-'}{p.containerPort}
    ; + return ( + + + + + + + + + {ports.map((p: ContainerPort, i: number) => ( + + + + + ))} + +
    NameContainer
    {p.name || '-'}{p.containerPort}
    + ); }; -const VolumeMounts: React.FC = ({volumeMounts}) => { +const VolumeMounts: React.FC = ({ volumeMounts }) => { if (!volumeMounts || !volumeMounts.length) { - return ; + return ( + + ); } - return - - - - - - - - - {volumeMounts.map((v: VolumeMount) => - - - - )} - -
    AccessLocationMount Path
    {v.readOnly === true ? 'Read Only' : 'Read / Write'}{v.name}{v.mountPath ?
    {v.mountPath}
    : '-'}
    ; + return ( + + + + + + + + + + {volumeMounts.map((v: VolumeMount) => ( + + + + + + ))} + +
    AccessLocationMount Path
    {v.readOnly === true ? 'Read Only' : 'Read / Write'}{v.name} + {v.mountPath ? ( +
    {v.mountPath}
    + ) : ( + '-' + )} +
    + ); }; VolumeMounts.displayName = 'VolumeMounts'; -const Env: React.FC = ({env}) => { +const Env: React.FC = ({ env }) => { if (!env || !env.length) { - return ; + return ( + + ); } const value = (e: EnvVar) => { @@ -135,27 +179,31 @@ const Env: React.FC = ({env}) => { return e.value; }; - return - - - - - - - - {env.map((e: EnvVar, i: number) => - - - )} - -
    NameValue
    {e.name}{value(e)}
    ; + return ( + + + + + + + + + {env.map((e: EnvVar, i: number) => ( + + + + + ))} + +
    NameValue
    {e.name}{value(e)}
    + ); }; Env.displayName = 'Env'; // Split image string into the image name and tag. const getImageNameAndTag = (image: string) => { if (!image) { - return {imageName: null, imageTag: null}; + return { imageName: null, imageTag: null }; } const index = image.lastIndexOf(':'); if (index === -1 || _.includes(image, '@sha256:')) { @@ -169,126 +217,188 @@ const getImageNameAndTag = (image: string) => { const ContainerDetails: React.FC = (props) => { const pod = props.obj; const container = - _.find(pod.spec.containers, {name: props.match.params.name}) as ContainerSpec || - _.find(pod.spec.initContainers, {name: props.match.params.name}) as ContainerSpec; + (_.find(pod.spec.containers, { name: props.match.params.name }) as ContainerSpec) || + (_.find(pod.spec.initContainers, { name: props.match.params.name }) as ContainerSpec); if (!container) { return null; } - const status: ContainerStatus = getContainerStatus(pod, container.name) || {} as ContainerStatus; + const status: ContainerStatus = + getContainerStatus(pod, container.name) || ({} as ContainerStatus); const state = getContainerState(status); - const stateValue = state.value === 'terminated' && _.isFinite(state.exitCode) - ? `${state.label} with exit code ${state.exitCode}` - : state.label; + const stateValue = + state.value === 'terminated' && _.isFinite(state.exitCode) + ? `${state.label} with exit code ${state.exitCode}` + : state.label; const { imageName, imageTag } = getImageNameAndTag(container.image); - return
    - - -
    -
    - -
    -
    State
    -
    -
    ID
    -
    {status.containerID ?
    {status.containerID}
    : '-'}
    -
    Restarts
    -
    {status.restartCount}
    -
    Resource Requests
    -
    {getResourceRequestsValue(container) || '-'}
    -
    Resource Limits
    -
    {getResourceLimitsValue(container) || '-'}
    -
    Lifecycle Hooks
    -
    -
    Readiness Probe
    -
    -
    Liveness Probe
    -
    -
    Started
    -
    -
    Finished
    -
    -
    Pod
    -
    -
    -
    + return ( +
    + + +
    +
    + +
    +
    State
    +
    + +
    +
    ID
    +
    + {status.containerID ? ( +
    {status.containerID}
    + ) : ( + '-' + )} +
    +
    Restarts
    +
    {status.restartCount}
    +
    Resource Requests
    +
    {getResourceRequestsValue(container) || '-'}
    +
    Resource Limits
    +
    {getResourceLimitsValue(container) || '-'}
    +
    Lifecycle Hooks
    +
    + +
    +
    Readiness Probe
    +
    + +
    +
    Liveness Probe
    +
    + +
    +
    Started
    +
    + +
    +
    Finished
    +
    + +
    +
    Pod
    +
    + +
    +
    +
    -
    - -
    -
    Image
    -
    {imageName ?
    {imageName}
    : '-'}
    -
    Image Version/Tag
    -
    {imageTag || '-'}
    -
    Command
    -
    {container.command ?
    {container.command.join(' ')}
    : -}
    -
    Args
    -
    {container.args ?
    {container.args.join(' ')}
    : -}
    -
    Pull Policy
    -
    {getPullPolicyLabel(container)}
    -
    -
    +
    + +
    +
    Image
    +
    + {imageName ?
    {imageName}
    : '-'} +
    +
    Image Version/Tag
    +
    {imageTag || '-'}
    +
    Command
    +
    + {container.command ? ( +
    +                  {container.command.join(' ')}
    +                
    + ) : ( + - + )} +
    +
    Args
    +
    + {container.args ? ( +
    +                  {container.args.join(' ')}
    +                
    + ) : ( + - + )} +
    +
    Pull Policy
    +
    {getPullPolicyLabel(container)}
    +
    +
    -
    - -
    -
    Node
    -
    -
    Pod IP
    -
    {pod.status.podIP || '-'}
    -
    +
    + +
    +
    Node
    +
    + +
    +
    Pod IP
    +
    {pod.status.podIP || '-'}
    +
    +
    -
    -
    +
    -
    -
    - -
    - +
    +
    + +
    + +
    -
    -
    - -
    - +
    + +
    + +
    -
    -
    - -
    - +
    + +
    + +
    -
    ; + ); }; ContainerDetails.displayName = 'ContainerDetails'; -export const ContainersDetailsPage: React.FC = (props) =>
    - - [ - {name: 'Pods', path: `/k8s/ns/${props.match.params.ns}/pods`}, - {name: props.match.params.podName, path: resourcePath('Pod', props.match.params.podName, props.match.params.ns)}, - {name: 'Container Details', path: props.match.url}, - ]} /> - - -
    ; +export const ContainersDetailsPage: React.FC = (props) => ( +
    + + [ + { name: 'Pods', path: `/k8s/ns/${props.match.params.ns}/pods` }, + { + name: props.match.params.podName, + path: resourcePath('Pod', props.match.params.podName, props.match.params.ns), + }, + { name: 'Container Details', path: props.match.url }, + ]} + /> + + +
    +); ContainersDetailsPage.displayName = 'ContainersDetailsPage'; type LifecycleProps = { diff --git a/frontend/public/components/create-yaml.tsx b/frontend/public/components/create-yaml.tsx index 82e4d67331c..0d41abda9ff 100644 --- a/frontend/public/components/create-yaml.tsx +++ b/frontend/public/components/create-yaml.tsx @@ -6,12 +6,18 @@ import { yamlTemplates } from '../models/yaml-templates'; import { connectToPlural } from '../kinds'; import { AsyncComponent } from './utils/async'; import { Firehose, LoadingBox } from './utils'; -import { K8sKind, apiVersionForModel, referenceForModel, K8sResourceKindReference, K8sResourceKind } from '../module/k8s'; +import { + K8sKind, + apiVersionForModel, + referenceForModel, + K8sResourceKindReference, + K8sResourceKind, +} from '../module/k8s'; import { ErrorPage404 } from './error'; export const CreateYAML = connectToPlural((props: CreateYAMLProps) => { - const {match, kindsInFlight, kindObj, hideHeader = false, resourceObjPath} = props; - const {params} = match; + const { match, kindsInFlight, kindObj, hideHeader = false, resourceObjPath } = props; + const { params } = match; if (!kindObj) { if (kindsInFlight) { @@ -21,7 +27,10 @@ export const CreateYAML = connectToPlural((props: CreateYAMLProps) => { } const namespace = params.ns || 'default'; - const template = props.template || yamlTemplates.getIn([referenceForModel(kindObj), 'default']) || yamlTemplates.getIn(['DEFAULT', 'default']); + const template = + props.template || + yamlTemplates.getIn([referenceForModel(kindObj), 'default']) || + yamlTemplates.getIn(['DEFAULT', 'default']); const obj = safeLoad(template); obj.kind = kindObj.kind; @@ -37,18 +46,47 @@ export const CreateYAML = connectToPlural((props: CreateYAMLProps) => { // TODO: if someone edits namespace, we'll redirect to old namespace - return import('./droppable-edit-yaml').then(c => c.DroppableEditYAML)} obj={obj} create={true} kind={kindObj.kind} header={header} hideHeader={hideHeader} resourceObjPath={resourceObjPath} />; + return ( + import('./droppable-edit-yaml').then((c) => c.DroppableEditYAML)} + obj={obj} + create={true} + kind={kindObj.kind} + header={header} + hideHeader={hideHeader} + resourceObjPath={resourceObjPath} + /> + ); }); export const EditYAMLPage: React.SFC = (props) => { - const Wrapper = (wrapperProps) => import('./edit-yaml').then(c => c.EditYAML)} create={false} />; - return - - ; + const Wrapper = (wrapperProps) => ( + import('./edit-yaml').then((c) => c.EditYAML)} + create={false} + /> + ); + return ( + + + + ); }; export type CreateYAMLProps = { - match: RouterMatch<{ns: string, plural: string, appName?: string}>; + match: RouterMatch<{ ns: string; plural: string; appName?: string }>; kindsInFlight: boolean; kindObj: K8sKind; template?: string; @@ -59,7 +97,7 @@ export type CreateYAMLProps = { }; export type EditYAMLPageProps = { - match: RouterMatch<{ns: string, name: string}>; + match: RouterMatch<{ ns: string; name: string }>; kind: string; }; diff --git a/frontend/public/components/cron-job.jsx b/frontend/public/components/cron-job.jsx index f9d277a9517..4539fa893ae 100644 --- a/frontend/public/components/cron-job.jsx +++ b/frontend/public/components/cron-job.jsx @@ -3,7 +3,16 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; -import { Kebab, ContainerTable, navFactory, ResourceKebab, SectionHeading, ResourceLink, ResourceSummary, Timestamp } from './utils'; +import { + Kebab, + ContainerTable, + navFactory, + ResourceKebab, + SectionHeading, + ResourceLink, + ResourceSummary, + Timestamp, +} from './utils'; import { ResourceEventStream } from './events'; import { CronJobModel } from '../models'; @@ -24,44 +33,62 @@ const tableColumnClasses = [ const CronJobTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Schedule', sortField: 'spec.schedule', transforms: [sortable], + title: 'Schedule', + sortField: 'spec.schedule', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Concurrency Policy', sortField: 'spec.schedule', transforms: [sortable], + title: 'Concurrency Policy', + sortField: 'spec.schedule', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Starting Deadline Seconds', sortField: 'spec.schedule', transforms: [sortable], + title: 'Starting Deadline Seconds', + sortField: 'spec.schedule', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; CronJobTableHeader.displayName = 'CronJobTableHeader'; -const CronJobTableRow = ({obj: cronjob, index, key, style}) => { +const CronJobTableRow = ({ obj: cronjob, index, key, style }) => { return ( - + - - - - {cronjob.spec.schedule} + + {cronjob.spec.schedule} {_.get(cronjob.spec, 'concurrencyPolicy', '-')} @@ -76,50 +103,72 @@ const CronJobTableRow = ({obj: cronjob, index, key, style}) => { }; CronJobTableRow.displayName = 'CronJobTableRow'; -const Details = ({obj: cronjob}) => { +const Details = ({ obj: cronjob }) => { const job = cronjob.spec.jobTemplate; - return -
    -
    -
    - - -
    Schedule
    -
    {cronjob.spec.schedule}
    -
    Concurrency Policy
    -
    {cronjob.spec.concurrencyPolicy || '-'}
    -
    Starting Deadline Seconds
    -
    {cronjob.spec.startingDeadlineSeconds || '-'}
    -
    Last Schedule Time
    -
    -
    -
    -
    - -
    -
    Desired Completions
    -
    {job.spec.completions || '-'}
    -
    Parallelism
    -
    {job.spec.parallelism || '-'}
    -
    Deadline
    -
    {job.spec.activeDeadlineSeconds ? `${job.spec.activeDeadlineSeconds} seconds` : '-'}
    -
    + return ( + +
    +
    +
    + + +
    Schedule
    +
    {cronjob.spec.schedule}
    +
    Concurrency Policy
    +
    {cronjob.spec.concurrencyPolicy || '-'}
    +
    Starting Deadline Seconds
    +
    {cronjob.spec.startingDeadlineSeconds || '-'}
    +
    Last Schedule Time
    +
    + +
    +
    +
    +
    + +
    +
    Desired Completions
    +
    {job.spec.completions || '-'}
    +
    Parallelism
    +
    {job.spec.parallelism || '-'}
    +
    Deadline
    +
    + {job.spec.activeDeadlineSeconds ? `${job.spec.activeDeadlineSeconds} seconds` : '-'} +
    +
    +
    -
    -
    - - -
    - ; +
    + + +
    + + ); }; -export const CronJobsList = props => ; +export const CronJobsList = (props) => ( +
    +); -export const CronJobsPage = props => ; +export const CronJobsPage = (props) => ( + +); -export const CronJobsDetailsPage = props => ; +export const CronJobsDetailsPage = (props) => ( + +); diff --git a/frontend/public/components/custom-resource-definition.tsx b/frontend/public/components/custom-resource-definition.tsx index 893c3125b83..20def60dbad 100644 --- a/frontend/public/components/custom-resource-definition.tsx +++ b/frontend/public/components/custom-resource-definition.tsx @@ -5,7 +5,15 @@ import { sortable } from '@patternfly/react-table'; import { BanIcon } from '@patternfly/react-icons'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; -import { AsyncComponent, Kebab, navFactory, ResourceKebab, ResourceLink, ResourceSummary, SectionHeading } from './utils'; +import { + AsyncComponent, + Kebab, + navFactory, + ResourceKebab, + ResourceLink, + ResourceSummary, + SectionHeading, +} from './utils'; import { K8sResourceKind, referenceForCRD, CustomResourceDefinitionKind } from '../module/k8s'; import { CustomResourceDefinitionModel } from '../models'; import { resourceListPages } from './resource-pages'; @@ -14,16 +22,21 @@ import { GreenCheckCircleIcon } from '@console/shared'; const { common } = Kebab.factory; -const crdInstancesPath = crd => _.get(crd, 'spec.scope') === 'Namespaced' - ? `/k8s/all-namespaces/${referenceForCRD(crd)}` - : `/k8s/cluster/${referenceForCRD(crd)}`; +const crdInstancesPath = (crd) => + _.get(crd, 'spec.scope') === 'Namespaced' + ? `/k8s/all-namespaces/${referenceForCRD(crd)}` + : `/k8s/cluster/${referenceForCRD(crd)}`; const instances = (kind, obj) => ({ label: 'View Instances', href: crdInstancesPath(obj), }); -const menuActions = [instances, ...Kebab.getExtensionsActionsForKind(CustomResourceDefinitionModel), ...common]; +const menuActions = [ + instances, + ...Kebab.getExtensionsActionsForKind(CustomResourceDefinitionModel), + ...common, +]; const tableColumnClasses = [ classNames('col-lg-3', 'col-md-4', 'col-sm-4', 'col-xs-6'), @@ -37,61 +50,76 @@ const tableColumnClasses = [ const CRDTableHeader = () => { return [ { - title: 'Name', sortField: 'spec.names.kind', transforms: [sortable], + title: 'Name', + sortField: 'spec.names.kind', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Group', sortField: 'spec.group', transforms: [sortable], + title: 'Group', + sortField: 'spec.group', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Version', sortField: 'spec.version', transforms: [sortable], + title: 'Version', + sortField: 'spec.version', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Namespaced', sortField: 'spec.scope', transforms: [sortable], + title: 'Namespaced', + sortField: 'spec.scope', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Established', props: { className: tableColumnClasses[4] }, + title: 'Established', + props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; CRDTableHeader.displayName = 'CRDTableHeader'; -const isEstablished = conditions => { - const condition = _.find(conditions, c => c.type === 'Established'); +const isEstablished = (conditions) => { + const condition = _.find(conditions, (c) => c.type === 'Established'); return condition && condition.status === 'True'; }; -const namespaced = crd => crd.spec.scope === 'Namespaced'; +const namespaced = (crd) => crd.spec.scope === 'Namespaced'; -const CRDTableRow: React.FC = ({obj: crd, index, key, style}) => { +const CRDTableRow: React.FC = ({ obj: crd, index, key, style }) => { return ( - + - { crd.spec.group } - - - { crd.spec.version } - - - { namespaced(crd) ? 'Yes' : 'No' } + {crd.spec.group} + {crd.spec.version} + {namespaced(crd) ? 'Yes' : 'No'} - { - isEstablished(crd.status.conditions) - ? - : - } + {isEstablished(crd.status.conditions) ? ( + + + + ) : ( + + + + )} @@ -107,29 +135,67 @@ type CRDTableRowProps = { style: object; }; -const Details = ({obj: crd}) => { - return
    - - -
    ; +const Details = ({ obj: crd }) => { + return ( +
    + + +
    + ); }; -const Instances: React.FC = ({obj, namespace}) => { +const Instances: React.FC = ({ obj, namespace }) => { const crdKind = referenceForCRD(obj); const componentLoader = resourceListPages.get(crdKind, () => Promise.resolve(DefaultPage)); - return ; -}; - -export const CustomResourceDefinitionsList: React.FC = props =>
    ; - -export const CustomResourceDefinitionsPage: React.FC = props => ; -export const CustomResourceDefinitionsDetailsPage = props => ; - -export type CustomResourceDefinitionsListProps = { + return ( + + ); }; -export type CustomResourceDefinitionsPageProps = { -}; +export const CustomResourceDefinitionsList: React.FC = ( + props, +) => ( +
    +); + +export const CustomResourceDefinitionsPage: React.FC = ( + props, +) => ( + +); +export const CustomResourceDefinitionsDetailsPage = (props) => ( + +); + +export type CustomResourceDefinitionsListProps = {}; + +export type CustomResourceDefinitionsPageProps = {}; type InstancesProps = { obj: CustomResourceDefinitionKind; diff --git a/frontend/public/components/daemon-set.jsx b/frontend/public/components/daemon-set.jsx index 595cc1e4527..2ca5be20b1c 100644 --- a/frontend/public/components/daemon-set.jsx +++ b/frontend/public/components/daemon-set.jsx @@ -2,13 +2,7 @@ import * as React from 'react'; import { Link } from 'react-router-dom'; import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; -import { - DetailsPage, - ListPage, - Table, - TableRow, - TableData, -} from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { AsyncComponent, Kebab, @@ -46,51 +40,75 @@ const tableColumnClasses = [ const DaemonSetTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Status', sortFunc: 'daemonsetNumScheduled', transforms: [sortable], + title: 'Status', + sortFunc: 'daemonsetNumScheduled', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Pod Selector', sortField: 'spec.selector', transforms: [sortable], + title: 'Pod Selector', + sortField: 'spec.selector', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; DaemonSetTableHeader.displayName = 'DaemonSetTableHeader'; -const DaemonSetTableRow = ({obj: daemonset, index, key, style}) => { +const DaemonSetTableRow = ({ obj: daemonset, index, key, style }) => { return ( - + - + - - {daemonset.status.currentNumberScheduled} of {daemonset.status.desiredNumberScheduled} pods + + {daemonset.status.currentNumberScheduled} of {daemonset.status.desiredNumberScheduled}{' '} + pods - + @@ -100,51 +118,79 @@ const DaemonSetTableRow = ({obj: daemonset, index, key, style}) => { }; DaemonSetTableRow.displayName = 'DaemonSetTableRow'; -export const DaemonSetDetailsList = ({ds}) => +export const DaemonSetDetailsList = ({ ds }) => (
    Current Count
    {ds.status.currentNumberScheduled || '-'}
    Desired Count
    {ds.status.desiredNumberScheduled || '-'}
    -
    ; + +); -const Details = ({obj: daemonset}) => -
    - -
    -
    - -
    -
    - +const Details = ({ obj: daemonset }) => ( + +
    + +
    +
    + +
    +
    + +
    -
    -
    - - -
    -
    - -
    -; +
    + + +
    +
    + +
    + +); -const EnvironmentPage = (props) => import('./environment.jsx').then(c => c.EnvironmentPage)} {...props} />; +const EnvironmentPage = (props) => ( + import('./environment.jsx').then((c) => c.EnvironmentPage)} + {...props} + /> +); -const envPath = ['spec','template','spec','containers']; -const environmentComponent = (props) => ; -const {details, pods, editYaml, envEditor, events} = navFactory; -const DaemonSets = props =>
    ; +const envPath = ['spec', 'template', 'spec', 'containers']; +const environmentComponent = (props) => ( + +); +const { details, pods, editYaml, envEditor, events } = navFactory; +const DaemonSets = (props) => ( +
    +); -const DaemonSetsPage = props => ; -const DaemonSetsDetailsPage = props => ; -export {DaemonSets, DaemonSetsPage, DaemonSetsDetailsPage}; +const DaemonSetsPage = (props) => ( + +); +const DaemonSetsDetailsPage = (props) => ( + +); +export { DaemonSets, DaemonSetsPage, DaemonSetsDetailsPage }; diff --git a/frontend/public/components/dashboard/capacity-card/capacity-body.tsx b/frontend/public/components/dashboard/capacity-card/capacity-body.tsx index 0f068c2984a..9ce9675a778 100644 --- a/frontend/public/components/dashboard/capacity-card/capacity-body.tsx +++ b/frontend/public/components/dashboard/capacity-card/capacity-body.tsx @@ -6,4 +6,4 @@ export const CapacityBody: React.FC = ({ children }) => ( type CapacityBodyProps = { children: React.ReactNode; -} +}; diff --git a/frontend/public/components/dashboard/capacity-card/capacity-item.tsx b/frontend/public/components/dashboard/capacity-card/capacity-item.tsx index cfd3b175307..cde9cf57cf4 100644 --- a/frontend/public/components/dashboard/capacity-card/capacity-item.tsx +++ b/frontend/public/components/dashboard/capacity-card/capacity-item.tsx @@ -6,43 +6,58 @@ import { GaugeChart } from '../../graphs/gauge'; const NOT_AVAILABLE = 'Not available'; -export const CapacityItem: React.FC = React.memo(({ title, used, total, formatValue, isLoading = false, error }) => { - const errorMsg = (error || !_.isFinite(used) || !_.isFinite(total)) ? 'No Data' : ''; - const totalFormatted = formatValue(total || 0); - const usedFormatted = formatValue(used || 0, null, totalFormatted.unit); - const available = formatValue(totalFormatted.value - usedFormatted.value, totalFormatted.unit, totalFormatted.unit); - const percentageUsed = total > 0 ? Math.round((100 * usedFormatted.value) / totalFormatted.value) : 0; - const data = { - x: usedFormatted.string, - y: percentageUsed, - }; - const description = errorMsg ? NOT_AVAILABLE : ( - <> - {available.string} - {' available out of '} - {totalFormatted.string} - - ); - return ( -
    -
    {title}
    -
    {isLoading ? : description}
    - -
    - ); -}); +export const CapacityItem: React.FC = React.memo( + ({ title, used, total, formatValue, isLoading = false, error }) => { + const errorMsg = error || !_.isFinite(used) || !_.isFinite(total) ? 'No Data' : ''; + const totalFormatted = formatValue(total || 0); + const usedFormatted = formatValue(used || 0, null, totalFormatted.unit); + const available = formatValue( + totalFormatted.value - usedFormatted.value, + totalFormatted.unit, + totalFormatted.unit, + ); + const percentageUsed = + total > 0 ? Math.round((100 * usedFormatted.value) / totalFormatted.value) : 0; + const data = { + x: usedFormatted.string, + y: percentageUsed, + }; + const description = errorMsg ? ( + NOT_AVAILABLE + ) : ( + <> + + {available.string} + + {' available out of '} + + {totalFormatted.string} + + + ); + return ( +
    +
    {title}
    +
    + {isLoading ? : description} +
    + +
    + ); + }, +); type CapacityItemProps = { title: string; used?: React.ReactText; total?: React.ReactText; - formatValue: Humanize, + formatValue: Humanize; isLoading: boolean; error: boolean; }; diff --git a/frontend/public/components/dashboard/dashboard-card/card-body.tsx b/frontend/public/components/dashboard/dashboard-card/card-body.tsx index 50f0aa8f05c..b8cef7ca808 100644 --- a/frontend/public/components/dashboard/dashboard-card/card-body.tsx +++ b/frontend/public/components/dashboard/dashboard-card/card-body.tsx @@ -4,11 +4,13 @@ import { CardBody, CardBodyProps } from '@patternfly/react-core'; import { LoadingInline } from '../../utils/status-box'; -export const DashboardCardBody: React.FC = React.memo(({ isLoading, classname, children, ...props }) => ( - - {isLoading ? : children} - -)); +export const DashboardCardBody: React.FC = React.memo( + ({ isLoading, classname, children, ...props }) => ( + + {isLoading ? : children} + + ), +); type DashboardCardBodyProps = CardBodyProps & { classname?: string; diff --git a/frontend/public/components/dashboard/dashboard-card/card-header.tsx b/frontend/public/components/dashboard/dashboard-card/card-header.tsx index c8f67799964..185397c2c42 100644 --- a/frontend/public/components/dashboard/dashboard-card/card-header.tsx +++ b/frontend/public/components/dashboard/dashboard-card/card-header.tsx @@ -1,11 +1,11 @@ import * as React from 'react'; import classNames from 'classnames'; -export const DashboardCardHeader: React.FC = React.memo(({ className, children }) => ( -
    - {children} -
    -)); +export const DashboardCardHeader: React.FC = React.memo( + ({ className, children }) => ( +
    {children}
    + ), +); type DashboardCardHeaderProps = { className?: string; diff --git a/frontend/public/components/dashboard/dashboard-card/card-link.tsx b/frontend/public/components/dashboard/dashboard-card/card-link.tsx index 707fe67e62a..cbbeb489b6c 100644 --- a/frontend/public/components/dashboard/dashboard-card/card-link.tsx +++ b/frontend/public/components/dashboard/dashboard-card/card-link.tsx @@ -3,15 +3,21 @@ import { Link } from 'react-router-dom'; import { Button } from 'patternfly-react'; import { Popover, PopoverPosition } from '@patternfly/react-core'; -const DashboardCardButtonLink: React.FC = React.memo(({ children, ...rest }) => ( - -)); +const DashboardCardButtonLink: React.FC = React.memo( + ({ children, ...rest }) => ( + + ), +); -export const DashboardCardLink: React.FC = React.memo(({ children, to }) => ( - - {children} - -)); +export const DashboardCardLink: React.FC = React.memo( + ({ children, to }) => ( + + {children} + + ), +); export const DashboardCardPopupLink: React.FC = React.memo( ({ linkTitle, popupTitle, children }) => { @@ -30,7 +36,7 @@ export const DashboardCardPopupLink: React.FC = Rea {linkTitle} ); - } + }, ); type DashboardCardButtonLinkProps = { diff --git a/frontend/public/components/dashboard/dashboard-card/card-title.tsx b/frontend/public/components/dashboard/dashboard-card/card-title.tsx index 7f5b154c237..aeade4882c2 100644 --- a/frontend/public/components/dashboard/dashboard-card/card-title.tsx +++ b/frontend/public/components/dashboard/dashboard-card/card-title.tsx @@ -1,13 +1,13 @@ import * as React from 'react'; import classNames from 'classnames'; -export const DashboardCardTitle: React.FC = React.memo(({ className, children }) => ( -

    - {children} -

    -)); +export const DashboardCardTitle: React.FC = React.memo( + ({ className, children }) => ( +

    {children}

    + ), +); type DashboardCardTitleProps = { className?: string; children: React.ReactNode; -} +}; diff --git a/frontend/public/components/dashboard/dashboard-card/card.tsx b/frontend/public/components/dashboard/dashboard-card/card.tsx index 3dbc43008fd..2f625c7f49a 100644 --- a/frontend/public/components/dashboard/dashboard-card/card.tsx +++ b/frontend/public/components/dashboard/dashboard-card/card.tsx @@ -2,11 +2,13 @@ import * as React from 'react'; import classNames from 'classnames'; import { Card, CardProps } from '@patternfly/react-core'; -export const DashboardCard: React.FC = React.memo(({ className, children, ...props }) => ( - - {children} - -)); +export const DashboardCard: React.FC = React.memo( + ({ className, children, ...props }) => ( + + {children} + + ), +); type DashboardCardProps = CardProps & { className?: string; diff --git a/frontend/public/components/dashboard/details-card/detail-item.tsx b/frontend/public/components/dashboard/details-card/detail-item.tsx index f83b6564f89..f465fd6cfcf 100644 --- a/frontend/public/components/dashboard/details-card/detail-item.tsx +++ b/frontend/public/components/dashboard/details-card/detail-item.tsx @@ -17,7 +17,7 @@ export const DetailItem: React.FC = React.memo(
    {status}
    ); - } + }, ); type DetailItemProps = { diff --git a/frontend/public/components/dashboard/details-card/details-body.tsx b/frontend/public/components/dashboard/details-card/details-body.tsx index a4a92de999e..d2707613ea1 100644 --- a/frontend/public/components/dashboard/details-card/details-body.tsx +++ b/frontend/public/components/dashboard/details-card/details-body.tsx @@ -1,9 +1,11 @@ import * as React from 'react'; export const DetailsBody: React.FC = ({ children }) => ( -
    {children}
    +
    + {children} +
    ); type DetailsBodyProps = { children: React.ReactNode; -} +}; diff --git a/frontend/public/components/dashboard/events-card/event-item.tsx b/frontend/public/components/dashboard/events-card/event-item.tsx index a4bf8ca10d7..4917a15ad1f 100644 --- a/frontend/public/components/dashboard/events-card/event-item.tsx +++ b/frontend/public/components/dashboard/events-card/event-item.tsx @@ -12,7 +12,11 @@ export const EventItem: React.FC = React.memo(({ event }) = return (
    - +
    {isError && } diff --git a/frontend/public/components/dashboard/events-card/events-body.tsx b/frontend/public/components/dashboard/events-card/events-body.tsx index 6a1b7756b45..5dbe840a567 100644 --- a/frontend/public/components/dashboard/events-card/events-body.tsx +++ b/frontend/public/components/dashboard/events-card/events-body.tsx @@ -12,21 +12,28 @@ export const EventsBody: React.FC = ({ events, filter }) => { if (events && events.loadError) { eventsBody = ; } else if (!(events && events.loaded)) { - eventsBody =
    ; + eventsBody = ( +
    + +
    + ); } else { const filteredEvents = filter ? events.data.filter(filter) : events.data; const sortedEvents = _.orderBy(filteredEvents, ['lastTimestamp', 'name'], ['desc', 'asc']); - eventsBody = filteredEvents.length === 0 ? ( - - ) : ( - - ); + eventsBody = + filteredEvents.length === 0 ? ( + + ) : ( + + ); } return (
    -
    - {eventsBody} -
    +
    {eventsBody}
    ); }; diff --git a/frontend/public/components/dashboard/grid.tsx b/frontend/public/components/dashboard/grid.tsx index a32491fdd1d..e273fb4b6c5 100644 --- a/frontend/public/components/dashboard/grid.tsx +++ b/frontend/public/components/dashboard/grid.tsx @@ -11,48 +11,46 @@ export enum GridPosition { RIGHT = 'RIGHT', } -const mapCardsToGrid = (cards: GridDashboardCard[], keyPrefix: string, ignoreCardSpan: boolean = false): React.ReactNode[] => - cards.map(({Card, span = 12}, index) => ( - +const mapCardsToGrid = ( + cards: GridDashboardCard[], + keyPrefix: string, + ignoreCardSpan: boolean = false, +): React.ReactNode[] => + cards.map(({ Card, span = 12 }, index) => ( + + + )); -export const DashboardGrid: React.FC = ({ mainCards, leftCards = [], rightCards = [] }) => { +export const DashboardGrid: React.FC = ({ + mainCards, + leftCards = [], + rightCards = [], +}) => { const [containerRef, width] = useRefWidth(); - const grid = width <= parseInt(breakpointLG.value, 10) ? - ( + const grid = + width <= parseInt(breakpointLG.value, 10) ? ( - - {mapCardsToGrid(mainCards, 'main', true)} - + {mapCardsToGrid(mainCards, 'main', true)} - - {mapCardsToGrid(leftCards, 'left', true)} - + {mapCardsToGrid(leftCards, 'left', true)} - - {mapCardsToGrid(rightCards, 'right', true)} - + {mapCardsToGrid(rightCards, 'right', true)} ) : ( - - {mapCardsToGrid(leftCards, 'left')} - + {mapCardsToGrid(leftCards, 'left')} - - {mapCardsToGrid(mainCards, 'main')} - + {mapCardsToGrid(mainCards, 'main')} - - {mapCardsToGrid(rightCards, 'right')} - + {mapCardsToGrid(rightCards, 'right')} ); @@ -63,10 +61,10 @@ export const DashboardGrid: React.FC = ({ mainCards, leftCar export type GridDashboardCard = { Card: React.ComponentType; span?: DashboardCardSpan; -} +}; type DashboardGridProps = { - mainCards: GridDashboardCard[], - leftCards?: GridDashboardCard[], - rightCards?: GridDashboardCard[], + mainCards: GridDashboardCard[]; + leftCards?: GridDashboardCard[]; + rightCards?: GridDashboardCard[]; }; diff --git a/frontend/public/components/dashboard/health-card/alert-item.tsx b/frontend/public/components/dashboard/health-card/alert-item.tsx index 710ec113b11..99f52613090 100644 --- a/frontend/public/components/dashboard/health-card/alert-item.tsx +++ b/frontend/public/components/dashboard/health-card/alert-item.tsx @@ -21,7 +21,9 @@ export const AlertItem: React.FC = ({ alert }) => { return (
    {getSeverityIcon(getAlertSeverity(alert))} - {getAlertDescription(alert) || getAlertMessage(alert)} + + {getAlertDescription(alert) || getAlertMessage(alert)} +
    ); }; diff --git a/frontend/public/components/dashboard/health-card/health-body.tsx b/frontend/public/components/dashboard/health-card/health-body.tsx index 12a36d59d27..6946de282dd 100644 --- a/frontend/public/components/dashboard/health-card/health-body.tsx +++ b/frontend/public/components/dashboard/health-card/health-body.tsx @@ -2,7 +2,11 @@ import * as React from 'react'; import classNames from 'classnames'; export const HealthBody: React.FC = React.memo(({ children, className }) => ( -
    {children}
    +
    + {children} +
    )); type HealthBodyProps = { diff --git a/frontend/public/components/dashboard/health-card/health-item.tsx b/frontend/public/components/dashboard/health-card/health-item.tsx index da5a654be99..310520363ef 100644 --- a/frontend/public/components/dashboard/health-card/health-item.tsx +++ b/frontend/public/components/dashboard/health-card/health-item.tsx @@ -30,8 +30,14 @@ export const HealthItem: React.FC = React.memo(
    {state === HealthState.LOADING ? : }
    - {message && {message}} - {details &&
    {details}
    } + {message && ( + {message} + )} + {details && ( +
    + {details} +
    + )}
    ), diff --git a/frontend/public/components/dashboard/health-card/utils.tsx b/frontend/public/components/dashboard/health-card/utils.tsx index ac2e26e4a3e..012b49742cb 100644 --- a/frontend/public/components/dashboard/health-card/utils.tsx +++ b/frontend/public/components/dashboard/health-card/utils.tsx @@ -5,10 +5,11 @@ import { RequestMap } from '../../../reducers/dashboards'; export const getAlertSeverity = (alert: Alert): string => _.get(alert, 'labels.severity'); export const getAlertMessage = (alert: Alert): string => _.get(alert, 'annotations.message'); -export const getAlertDescription = (alert: Alert): string => _.get(alert, 'annotations.description'); +export const getAlertDescription = (alert: Alert): string => + _.get(alert, 'annotations.description'); export const filterAlerts = (alerts: Alert[]): Alert[] => - alerts.filter(alert => _.get(alert, 'labels.alertname') !== 'Watchdog'); + alerts.filter((alert) => _.get(alert, 'labels.alertname') !== 'Watchdog'); export const getAlerts = (alertsResults: RequestMap): Alert[] => { const alertsResponse = alertsResults ? alertsResults.getIn([ALERTS_KEY, 'data'], []) : []; diff --git a/frontend/public/components/dashboard/inventory-card/inventory-body.tsx b/frontend/public/components/dashboard/inventory-card/inventory-body.tsx index 07beee6d144..117479b8b70 100644 --- a/frontend/public/components/dashboard/inventory-card/inventory-body.tsx +++ b/frontend/public/components/dashboard/inventory-card/inventory-body.tsx @@ -1,3 +1,5 @@ import * as React from 'react'; -export const InventoryBody = ({ children }) =>
    {children}
    ; +export const InventoryBody = ({ children }) => ( +
    {children}
    +); diff --git a/frontend/public/components/dashboard/inventory-card/inventory-item.tsx b/frontend/public/components/dashboard/inventory-card/inventory-item.tsx index bb766e91b56..cfd102dfbc6 100644 --- a/frontend/public/components/dashboard/inventory-card/inventory-item.tsx +++ b/frontend/public/components/dashboard/inventory-card/inventory-item.tsx @@ -8,24 +8,16 @@ import { YellowExclamationTriangleIcon, } from '@console/shared'; import * as plugins from '../../../plugins'; -import { - LoadingInline, -} from '../../utils'; +import { LoadingInline } from '../../utils'; import { K8sResourceKind, K8sKind } from '../../../module/k8s'; import { InventoryStatusGroup } from './status-group'; import { connectToFlags, FlagsObject, WithFlagsProps } from '../../../reducers/features'; import { getFlagsForExtensions, isDashboardExtensionInUse } from '../../dashboards-page/utils'; const defaultStatusGroupIcons = { - [InventoryStatusGroup.OK]: ( - - ), - [InventoryStatusGroup.WARN]: ( - - ), - [InventoryStatusGroup.ERROR]: ( - - ), + [InventoryStatusGroup.OK]: , + [InventoryStatusGroup.WARN]: , + [InventoryStatusGroup.ERROR]: , [InventoryStatusGroup.PROGRESS]: ( ), @@ -35,12 +27,15 @@ const defaultStatusGroupIcons = { }; const getStatusGroupIcons = (flags: FlagsObject) => { - const groupStatusIcons = {...defaultStatusGroupIcons}; - plugins.registry.getDashboardsInventoryItemGroups().filter(e => isDashboardExtensionInUse(e, flags)).forEach(group => { - if (!groupStatusIcons[group.properties.id]) { - groupStatusIcons[group.properties.id] = group.properties.icon; - } - }); + const groupStatusIcons = { ...defaultStatusGroupIcons }; + plugins.registry + .getDashboardsInventoryItemGroups() + .filter((e) => isDashboardExtensionInUse(e, flags)) + .forEach((group) => { + if (!groupStatusIcons[group.properties.id]) { + groupStatusIcons[group.properties.id] = group.properties.icon; + } + }); return groupStatusIcons; }; @@ -57,11 +52,13 @@ export const InventoryItem: React.FC = React.memo( } return (
    -
    {isLoading || error ? title : `${count} ${title}`}
    +
    + {isLoading || error ? title : `${count} ${title}`} +
    {status}
    ); - } + }, ); export const Status: React.FC = React.memo(({ groupID, count, flags }) => { @@ -79,60 +76,86 @@ const StatusLink: React.FC = React.memo( ({ groupID, count, statusIDs, kind, namespace, filterType, flags }) => { const statusItems = encodeURIComponent(statusIDs.join(',')); const namespacePath = namespace ? `ns/${namespace}` : 'all-namespaces'; - const to = filterType && statusItems.length > 0 ? `/k8s/${namespacePath}/${kind.plural}?rowFilter-${filterType}=${statusItems}` : `/k8s/${namespacePath}/${kind.plural}`; + const to = + filterType && statusItems.length > 0 + ? `/k8s/${namespacePath}/${kind.plural}?rowFilter-${filterType}=${statusItems}` + : `/k8s/${namespacePath}/${kind.plural}`; const statusGroupIcons = getStatusGroupIcons(flags); - const groupIcon = statusGroupIcons[groupID] || statusGroupIcons[InventoryStatusGroup.NOT_MAPPED]; + const groupIcon = + statusGroupIcons[groupID] || statusGroupIcons[InventoryStatusGroup.NOT_MAPPED]; return (
    - + {groupIcon} {count}
    ); - } + }, ); export const ResourceInventoryItem = connectToFlags( ...getFlagsForExtensions(plugins.registry.getDashboardsInventoryItemGroups()), -)(React.memo( - ({ kind, useAbbr, resources, additionalResources, isLoading, mapper, namespace, error, showLink = true, flags = {}}) => { - const groups = mapper(resources, additionalResources); - const [singularTitle, pluralTitle] = useAbbr ? [kind.abbr, `${kind.abbr}s`] : [kind.label, kind.labelPlural]; - return ( - - {Object.keys(groups).filter(key => groups[key].count > 0).map(key => showLink ? - ( - - ) : ( - - ) - )} - - ); - } -)); +)( + React.memo( + ({ + kind, + useAbbr, + resources, + additionalResources, + isLoading, + mapper, + namespace, + error, + showLink = true, + flags = {}, + }) => { + const groups = mapper(resources, additionalResources); + const [singularTitle, pluralTitle] = useAbbr + ? [kind.abbr, `${kind.abbr}s`] + : [kind.label, kind.labelPlural]; + return ( + + {Object.keys(groups) + .filter((key) => groups[key].count > 0) + .map((key) => + showLink ? ( + + ) : ( + + ), + )} + + ); + }, + ), +); -export type StatusGroupMapper = (resources: K8sResourceKind[], additionalResources?: {[key: string]: K8sResourceKind[]}) => {[key in InventoryStatusGroup | string]: {filterType?: string, statusIDs: string[], count: number}}; +export type StatusGroupMapper = ( + resources: K8sResourceKind[], + additionalResources?: { [key: string]: K8sResourceKind[] }, +) => { + [key in InventoryStatusGroup | string]: { + filterType?: string; + statusIDs: string[]; + count: number; + } +}; type InventoryItemProps = { isLoading: boolean; @@ -146,18 +169,18 @@ type InventoryItemProps = { type StatusProps = WithFlagsProps & { groupID: InventoryStatusGroup | string; count: number; -} +}; type StatusLinkProps = StatusProps & { statusIDs: string[]; kind: K8sKind; namespace?: string; filterType?: string; -} +}; type ResourceInventoryItemProps = WithFlagsProps & { resources: K8sResourceKind[]; - additionalResources?: {[key: string]: K8sResourceKind[]}; + additionalResources?: { [key: string]: K8sResourceKind[] }; mapper: StatusGroupMapper; kind: K8sKind; useAbbr?: boolean; @@ -165,4 +188,4 @@ type ResourceInventoryItemProps = WithFlagsProps & { namespace?: string; error: boolean; showLink?: boolean; -} +}; diff --git a/frontend/public/components/dashboard/inventory-card/utils.ts b/frontend/public/components/dashboard/inventory-card/utils.ts index 2f164c4371d..aa9374e860b 100644 --- a/frontend/public/components/dashboard/inventory-card/utils.ts +++ b/frontend/public/components/dashboard/inventory-card/utils.ts @@ -34,7 +34,7 @@ const getStatusGroups = (resources, mapping, mapper, filterType) => { count: 0, }, }; - Object.keys(mapping).forEach(key => { + Object.keys(mapping).forEach((key) => { groups[key] = { statusIDs: [...mapping[key]], count: 0, @@ -42,16 +42,22 @@ const getStatusGroups = (resources, mapping, mapper, filterType) => { }; }); - resources.forEach(resource => { + resources.forEach((resource) => { const status = mapper(resource); - const group = Object.keys(mapping).find(key => mapping[key].includes(status)) || InventoryStatusGroup.NOT_MAPPED; + const group = + Object.keys(mapping).find((key) => mapping[key].includes(status)) || + InventoryStatusGroup.NOT_MAPPED; groups[group].count++; }); return groups; }; -export const getPodStatusGroups: StatusGroupMapper = resources => getStatusGroups(resources, POD_PHASE_GROUP_MAPPING, podPhaseFilterReducer, 'pod-status'); -export const getNodeStatusGroups: StatusGroupMapper = resources => getStatusGroups(resources, NODE_STATUS_GROUP_MAPPING, nodeStatus, 'node-status'); -export const getPVCStatusGroups: StatusGroupMapper = resources => getStatusGroups(resources, PVC_STATUS_GROUP_MAPPING, pvcPhase, 'pvc-status'); -export const getPVStatusGroups: StatusGroupMapper = resources => getStatusGroups(resources, PV_STATUS_GROUP_MAPPING, (pv) => pv.status.phase, 'pv-status'); +export const getPodStatusGroups: StatusGroupMapper = (resources) => + getStatusGroups(resources, POD_PHASE_GROUP_MAPPING, podPhaseFilterReducer, 'pod-status'); +export const getNodeStatusGroups: StatusGroupMapper = (resources) => + getStatusGroups(resources, NODE_STATUS_GROUP_MAPPING, nodeStatus, 'node-status'); +export const getPVCStatusGroups: StatusGroupMapper = (resources) => + getStatusGroups(resources, PVC_STATUS_GROUP_MAPPING, pvcPhase, 'pvc-status'); +export const getPVStatusGroups: StatusGroupMapper = (resources) => + getStatusGroups(resources, PV_STATUS_GROUP_MAPPING, (pv) => pv.status.phase, 'pv-status'); diff --git a/frontend/public/components/dashboard/top-consumers-card/consumers-body.tsx b/frontend/public/components/dashboard/top-consumers-card/consumers-body.tsx index ed60cc28dcf..e5bdff583c7 100644 --- a/frontend/public/components/dashboard/top-consumers-card/consumers-body.tsx +++ b/frontend/public/components/dashboard/top-consumers-card/consumers-body.tsx @@ -1,9 +1,7 @@ import * as React from 'react'; export const ConsumersBody: React.FC = React.memo(({ children }) => ( -
    - {children} -
    +
    {children}
    )); type ConsumersBodyProps = { diff --git a/frontend/public/components/dashboard/top-consumers-card/consumers-filter.tsx b/frontend/public/components/dashboard/top-consumers-card/consumers-filter.tsx index baa23736a40..ddb59ccbb8e 100644 --- a/frontend/public/components/dashboard/top-consumers-card/consumers-filter.tsx +++ b/frontend/public/components/dashboard/top-consumers-card/consumers-filter.tsx @@ -1,7 +1,12 @@ import * as React from 'react'; import { CPU_DESC, MEMORY_DESC, STORAGE_DESC, NETWORK_DESC } from './strings'; -import { Humanize, humanizeBinaryBytesWithoutB, humanizeDecimalBytesPerSec, humanizeSeconds } from '../../utils'; +import { + Humanize, + humanizeBinaryBytesWithoutB, + humanizeDecimalBytesPerSec, + humanizeSeconds, +} from '../../utils'; import { MetricType } from './metric-type'; const toNanoSeconds = (value: React.ReactText) => { @@ -12,7 +17,7 @@ const toNanoSeconds = (value: React.ReactText) => { export const metricTypeMap: MetricTypeMap = { [MetricType.CPU]: { description: CPU_DESC, - humanize: value => humanizeSeconds(toNanoSeconds(value)), + humanize: (value) => humanizeSeconds(toNanoSeconds(value)), }, [MetricType.MEMORY]: { description: MEMORY_DESC, @@ -20,7 +25,7 @@ export const metricTypeMap: MetricTypeMap = { }, [MetricType.STORAGE]: { description: STORAGE_DESC, - humanize: value => humanizeSeconds(toNanoSeconds(value)), + humanize: (value) => humanizeSeconds(toNanoSeconds(value)), }, [MetricType.NETWORK]: { description: NETWORK_DESC, @@ -28,8 +33,9 @@ export const metricTypeMap: MetricTypeMap = { }, }; -export const ConsumersFilter: React.FC = ({ children }) => -
    {children}
    ; +export const ConsumersFilter: React.FC = ({ children }) => ( +
    {children}
    +); type ConsumersFilterProps = { children: React.ReactNode; @@ -37,7 +43,7 @@ type ConsumersFilterProps = { type MetricTypeMap = { [key: string]: { - description: string, - humanize: Humanize, - }, + description: string; + humanize: Humanize; + }; }; diff --git a/frontend/public/components/dashboard/utilization-card/utilization-body.tsx b/frontend/public/components/dashboard/utilization-card/utilization-body.tsx index ec7b2b4fc9a..1ce3b64f0ba 100644 --- a/frontend/public/components/dashboard/utilization-card/utilization-body.tsx +++ b/frontend/public/components/dashboard/utilization-card/utilization-body.tsx @@ -22,7 +22,7 @@ const UtilizationAxis: React.FC = ({ timestamps }) => { width={width} padding={{ top: 30, bottom: 0, left: 70, right: 0 }} style={{ - axis: {visibility: 'hidden'}, + axis: { visibility: 'hidden' }, }} fixLabelOverlap /> @@ -33,16 +33,18 @@ const UtilizationAxis: React.FC = ({ timestamps }) => { export const UtilizationBody: React.FC = ({ timestamps, children }) => { const [containerRef, width] = useRefWidth(); - const axis = width < parseInt(breakpointSM.value, 10) ? - timestamps.length === 0 ? null : ( -
    -
    - + const axis = + width < parseInt(breakpointSM.value, 10) ? ( + timestamps.length === 0 ? null : ( +
    +
    + +
    -
    + ) ) : (
    -
    +
    {timestamps.length > 0 && }
    @@ -66,4 +68,4 @@ type UtilizationBodyProps = { type UtilizationAxisProps = { timestamps: Date[]; -} +}; diff --git a/frontend/public/components/dashboard/utilization-card/utilization-item.tsx b/frontend/public/components/dashboard/utilization-card/utilization-item.tsx index bb5772bacee..8f8ffd901fe 100644 --- a/frontend/public/components/dashboard/utilization-card/utilization-item.tsx +++ b/frontend/public/components/dashboard/utilization-card/utilization-item.tsx @@ -28,43 +28,46 @@ export const UtilizationItem: React.FC = React.memo( /> ); - const rows = width < parseInt(breakpointSM.value, 10) ? ( -
    -
    -
    + const rows = + width < parseInt(breakpointSM.value, 10) ? ( +
    +
    +
    + {title} +
    +
    + {current} +
    +
    +
    +
    + {chart} +
    +
    +
    + ) : ( +
    +
    {title}
    -
    +
    {current}
    +
    + {chart} +
    -
    -
    {chart}
    -
    -
    - ) : ( -
    -
    - {title} -
    -
    - {current} -
    -
    - {chart} -
    -
    - ); + ); return
    {rows}
    ; - } + }, ); type UtilizationItemProps = { title: string; data?: DataPoint[]; - isLoading: boolean, - humanizeValue: Humanize, - query: string, + isLoading: boolean; + humanizeValue: Humanize; + query: string; error: boolean; }; diff --git a/frontend/public/components/dashboards-page/dashboards.tsx b/frontend/public/components/dashboards-page/dashboards.tsx index ad44cbc0a9e..35614fc28fa 100644 --- a/frontend/public/components/dashboards-page/dashboards.tsx +++ b/frontend/public/components/dashboards-page/dashboards.tsx @@ -10,34 +10,46 @@ import { HorizontalNav, PageHeading, LoadingBox, Page, AsyncComponent } from '.. import { Dashboard } from '../dashboard/dashboard'; import { DashboardGrid, GridPosition, GridDashboardCard } from '../dashboard/grid'; import { DashboardsCard } from '@console/plugin-sdk'; -import { featureReducerName, connectToFlags, FlagsObject, WithFlagsProps } from '../../reducers/features'; +import { + featureReducerName, + connectToFlags, + FlagsObject, + WithFlagsProps, +} from '../../reducers/features'; import { RootState } from '../../redux'; import { getFlagsForExtensions, isDashboardExtensionInUse } from './utils'; const getCardsOnPosition = (cards: DashboardsCard[], position: GridPosition): GridDashboardCard[] => - cards.filter(c => c.properties.position === position).map(c => ({ - Card: () => , - span: c.properties.span, - })); + cards + .filter((c) => c.properties.position === position) + .map((c) => ({ + Card: () => , + span: c.properties.span, + })); const getPluginTabPages = (flags: FlagsObject): Page[] => { - const cards = plugins.registry.getDashboardsCards().filter(e => isDashboardExtensionInUse(e, flags)); - return plugins.registry.getDashboardsTabs().filter(e => isDashboardExtensionInUse(e, flags)).map(tab => { - const tabCards = cards.filter(c => c.properties.tab === tab.properties.id); - return { - href: tab.properties.id, - name: tab.properties.title, - component: () => ( - - - - ), - }; - }); + const cards = plugins.registry + .getDashboardsCards() + .filter((e) => isDashboardExtensionInUse(e, flags)); + return plugins.registry + .getDashboardsTabs() + .filter((e) => isDashboardExtensionInUse(e, flags)) + .map((tab) => { + const tabCards = cards.filter((c) => c.properties.tab === tab.properties.id); + return { + href: tab.properties.id, + name: tab.properties.title, + component: () => ( + + + + ), + }; + }); }; const getTabs = (flags: FlagsObject): Page[] => [ @@ -49,19 +61,24 @@ const getTabs = (flags: FlagsObject): Page[] => [ ...getPluginTabPages(flags), ]; -const DashboardsPage_: React.FC = ({ match, kindsInFlight, k8sModels, flags }) => { +const DashboardsPage_: React.FC = ({ + match, + kindsInFlight, + k8sModels, + flags, +}) => { const title = 'Dashboards'; - return kindsInFlight && k8sModels.size === 0 - ? - : ( - <> - - {title} - - - - - ); + return kindsInFlight && k8sModels.size === 0 ? ( + + ) : ( + <> + + {title} + + + + + ); }; const mapStateToProps = (state: RootState) => ({ @@ -71,13 +88,16 @@ const mapStateToProps = (state: RootState) => ({ }); export const DashboardsPage = connect(mapStateToProps)( - connectToFlags(...getFlagsForExtensions([ - ...plugins.registry.getDashboardsCards(), - ...plugins.registry.getDashboardsTabs(), - ]))(DashboardsPage_) + connectToFlags( + ...getFlagsForExtensions([ + ...plugins.registry.getDashboardsCards(), + ...plugins.registry.getDashboardsTabs(), + ]), + )(DashboardsPage_), ); -type DashboardsPageProps = RouteComponentProps & WithFlagsProps & { - kindsInFlight: boolean; - k8sModels: ImmutableMap; -}; +type DashboardsPageProps = RouteComponentProps & + WithFlagsProps & { + kindsInFlight: boolean; + k8sModels: ImmutableMap; + }; diff --git a/frontend/public/components/dashboards-page/overview-dashboard/capacity-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/capacity-card.tsx index 2cf618eb876..b0decc3805d 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/capacity-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/capacity-card.tsx @@ -11,7 +11,12 @@ import { } from '../../dashboard/dashboard-card'; import { CapacityBody, CapacityItem } from '../../dashboard/capacity-card'; import { withDashboardResources, DashboardItemProps } from '../with-dashboard-resources'; -import { humanizePercentage, humanizeDecimalBytesPerSec, humanizeBinaryBytesWithoutB, useRefWidth } from '../../utils'; +import { + humanizePercentage, + humanizeDecimalBytesPerSec, + humanizeBinaryBytesWithoutB, + useRefWidth, +} from '../../utils'; import { getInstantVectorStats, getRangeVectorStats, GetStats } from '../../graphs/utils'; import { OverviewQuery, capacityQueries } from './queries'; import { connectToFlags, FlagsObject, WithFlagsProps } from '../../../reducers/features'; @@ -24,12 +29,15 @@ const getLastStats = (response, getStats: GetStats): React.ReactText => { const getQueries = (flags: FlagsObject) => { const pluginQueries = {}; - plugins.registry.getDashboardsOverviewQueries().filter(e => isDashboardExtensionInUse(e, flags)).forEach(pluginQuery => { - const queryKey = pluginQuery.properties.queryKey; - if (!pluginQueries[queryKey]) { - pluginQueries[queryKey] = pluginQuery.properties.query; - } - }); + plugins.registry + .getDashboardsOverviewQueries() + .filter((e) => isDashboardExtensionInUse(e, flags)) + .forEach((pluginQuery) => { + const queryKey = pluginQuery.properties.queryKey; + if (!pluginQueries[queryKey]) { + pluginQueries[queryKey] = pluginQuery.properties.query; + } + }); return _.defaults(pluginQueries, capacityQueries); }; @@ -42,27 +50,51 @@ export const CapacityCard_: React.FC = ({ const [containerRef, width] = useRefWidth(); React.useEffect(() => { const queries = getQueries(flags); - Object.keys(queries).forEach(key => watchPrometheus(queries[key])); - return () => Object.keys(queries).forEach(key => stopWatchPrometheusQuery(queries[key])); + Object.keys(queries).forEach((key) => watchPrometheus(queries[key])); + return () => Object.keys(queries).forEach((key) => stopWatchPrometheusQuery(queries[key])); // TODO: to be removed: use JSON.stringify(flags) to avoid deep comparison of flags object // eslint-disable-next-line react-hooks/exhaustive-deps }, [watchPrometheus, stopWatchPrometheusQuery, JSON.stringify(flags)]); const queries = getQueries(flags); const cpuUtilization = prometheusResults.getIn([queries[OverviewQuery.CPU_UTILIZATION], 'data']); - const cpuUtilizationError = prometheusResults.getIn([queries[OverviewQuery.CPU_UTILIZATION], 'loadError']); - const memoryUtilization = prometheusResults.getIn([queries[OverviewQuery.MEMORY_UTILIZATION], 'data']); - const memoryUtilizationError = prometheusResults.getIn([queries[OverviewQuery.MEMORY_UTILIZATION], 'loadError']); + const cpuUtilizationError = prometheusResults.getIn([ + queries[OverviewQuery.CPU_UTILIZATION], + 'loadError', + ]); + const memoryUtilization = prometheusResults.getIn([ + queries[OverviewQuery.MEMORY_UTILIZATION], + 'data', + ]); + const memoryUtilizationError = prometheusResults.getIn([ + queries[OverviewQuery.MEMORY_UTILIZATION], + 'loadError', + ]); const memoryTotal = prometheusResults.getIn([queries[OverviewQuery.MEMORY_TOTAL], 'data']); - const memoryTotalError = prometheusResults.getIn([queries[OverviewQuery.MEMORY_TOTAL], 'loadError']); + const memoryTotalError = prometheusResults.getIn([ + queries[OverviewQuery.MEMORY_TOTAL], + 'loadError', + ]); const storageUsed = prometheusResults.getIn([queries[OverviewQuery.STORAGE_UTILIZATION], 'data']); - const storageUsedError = prometheusResults.getIn([queries[OverviewQuery.STORAGE_UTILIZATION], 'loadError']); + const storageUsedError = prometheusResults.getIn([ + queries[OverviewQuery.STORAGE_UTILIZATION], + 'loadError', + ]); const storageTotal = prometheusResults.getIn([queries[OverviewQuery.STORAGE_TOTAL], 'data']); - const storageTotalError = prometheusResults.getIn([queries[OverviewQuery.STORAGE_TOTAL], 'loadError']); + const storageTotalError = prometheusResults.getIn([ + queries[OverviewQuery.STORAGE_TOTAL], + 'loadError', + ]); const networkUsed = prometheusResults.getIn([queries[OverviewQuery.NETWORK_UTILIZATION], 'data']); - const networkUsedError = prometheusResults.getIn([queries[OverviewQuery.NETWORK_UTILIZATION], 'loadError']); + const networkUsedError = prometheusResults.getIn([ + queries[OverviewQuery.NETWORK_UTILIZATION], + 'loadError', + ]); const networkTotal = prometheusResults.getIn([queries[OverviewQuery.NETWORK_TOTAL], 'data']); - const networkTotalError = prometheusResults.getIn([queries[OverviewQuery.NETWORK_TOTAL], 'loadError']); + const networkTotalError = prometheusResults.getIn([ + queries[OverviewQuery.NETWORK_TOTAL], + 'loadError', + ]); const CPUItem = ( = ({ /> ); - let grid; if (width <= 300) { grid = ( <> - - {CPUItem} - - - {MemoryItem} - - - {StorageItem} - - - {NetworkItem} - + {CPUItem} + {MemoryItem} + {StorageItem} + {NetworkItem} ); } else if (width <= 650) { diff --git a/frontend/public/components/dashboards-page/overview-dashboard/details-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/details-card.tsx index 221c950c952..fa6979d592d 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/details-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/details-card.tsx @@ -11,7 +11,14 @@ import { import { DetailsBody, DetailItem } from '../../dashboard/details-card'; import { DashboardItemProps, withDashboardResources } from '../with-dashboard-resources'; import { InfrastructureModel, ClusterVersionModel } from '../../../models'; -import { referenceForModel, K8sResourceKind, getOpenShiftVersion, getK8sGitVersion, ClusterVersionKind, getClusterID } from '../../../module/k8s'; +import { + referenceForModel, + K8sResourceKind, + getOpenShiftVersion, + getK8sGitVersion, + ClusterVersionKind, + getClusterID, +} from '../../../module/k8s'; import { FLAGS } from '../../../const'; import { flagPending, featureReducerName } from '../../../reducers/features'; import { FirehoseResource } from '../../utils'; @@ -37,100 +44,102 @@ const mapStateToProps = (state: RootState) => ({ openshiftFlag: state[featureReducerName].get(FLAGS.OPENSHIFT), }); -export const DetailsCard_ = connect(mapStateToProps)(({ - watchURL, - stopWatchURL, - watchK8sResource, - stopWatchK8sResource, - resources, - urlResults, - openshiftFlag, -}: DetailsCardProps) => { - React.useEffect(() => { - if (flagPending(openshiftFlag)) { - return; - } - if (openshiftFlag) { - watchK8sResource(clusterVersionResource); - watchK8sResource(infrastructureResource); +export const DetailsCard_ = connect(mapStateToProps)( + ({ + watchURL, + stopWatchURL, + watchK8sResource, + stopWatchK8sResource, + resources, + urlResults, + openshiftFlag, + }: DetailsCardProps) => { + React.useEffect(() => { + if (flagPending(openshiftFlag)) { + return; + } + if (openshiftFlag) { + watchK8sResource(clusterVersionResource); + watchK8sResource(infrastructureResource); + return () => { + stopWatchK8sResource(clusterVersionResource); + stopWatchK8sResource(infrastructureResource); + }; + } + watchURL('version'); return () => { - stopWatchK8sResource(clusterVersionResource); - stopWatchK8sResource(infrastructureResource); + stopWatchURL('version'); }; - } - watchURL('version'); - return () => { - stopWatchURL('version'); - }; - }, [openshiftFlag, watchK8sResource, stopWatchK8sResource, watchURL, stopWatchURL]); + }, [openshiftFlag, watchK8sResource, stopWatchK8sResource, watchURL, stopWatchURL]); - const clusterVersionLoaded = _.get(resources.cv, 'loaded', false); - const clusterVersionError = _.get(resources.cv, 'loadError'); - const clusterVersionData = _.get(resources.cv, 'data') as ClusterVersionKind; - const clusterId = getClusterID(clusterVersionData); - const openShiftVersion = getOpenShiftVersion(clusterVersionData); + const clusterVersionLoaded = _.get(resources.cv, 'loaded', false); + const clusterVersionError = _.get(resources.cv, 'loadError'); + const clusterVersionData = _.get(resources.cv, 'data') as ClusterVersionKind; + const clusterId = getClusterID(clusterVersionData); + const openShiftVersion = getOpenShiftVersion(clusterVersionData); - const infrastructureLoaded = _.get(resources.infrastructure, 'loaded', false); - const infrastructureError = _.get(resources.infrastructure, 'loadError'); - const infrastructureData = _.get(resources.infrastructure, 'data') as K8sResourceKind; - const infrastructurePlatform = getInfrastructurePlatform(infrastructureData); + const infrastructureLoaded = _.get(resources.infrastructure, 'loaded', false); + const infrastructureError = _.get(resources.infrastructure, 'loadError'); + const infrastructureData = _.get(resources.infrastructure, 'data') as K8sResourceKind; + const infrastructurePlatform = getInfrastructurePlatform(infrastructureData); - const kubernetesVersionData = urlResults.getIn(['version', 'data']); - const kubernetesVersionError = urlResults.getIn(['version', 'loadError']); - const k8sGitVersion = getK8sGitVersion(kubernetesVersionData); + const kubernetesVersionData = urlResults.getIn(['version', 'data']); + const kubernetesVersionError = urlResults.getIn(['version', 'loadError']); + const k8sGitVersion = getK8sGitVersion(kubernetesVersionData); - return ( - - - Details - - - - {openshiftFlag ? ( - <> + return ( + + + Details + + + + {openshiftFlag ? ( + <> + + {clusterId} + + + {infrastructurePlatform} + + + {openShiftVersion} + + + ) : ( - {clusterId} + {k8sGitVersion} - - {infrastructurePlatform} - - - {openShiftVersion} - - - ) : ( - - {k8sGitVersion} - - )} - - - - ); -}); + )} + + + + ); + }, +); export const DetailsCard = withDashboardResources(DetailsCard_); type DetailsCardProps = DashboardItemProps & { openshiftFlag: boolean; -} +}; diff --git a/frontend/public/components/dashboards-page/overview-dashboard/events-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/events-card.tsx index ea105a01a27..8eab0d0dcef 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/events-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/events-card.tsx @@ -1,15 +1,25 @@ import * as React from 'react'; -import { DashboardCard, DashboardCardHeader, DashboardCardBody, DashboardCardTitle, DashboardCardLink } from '../../dashboard/dashboard-card'; +import { + DashboardCard, + DashboardCardHeader, + DashboardCardBody, + DashboardCardTitle, + DashboardCardLink, +} from '../../dashboard/dashboard-card'; import { EventsBody } from '../../dashboard/events-card/events-body'; import { DashboardItemProps, withDashboardResources } from '../with-dashboard-resources'; import { EventModel } from '../../../models'; import { FirehoseResource, FirehoseResult } from '../../utils'; import { EventKind } from '../../../module/k8s'; -const eventsResource: FirehoseResource = {isList: true, kind: EventModel.kind, prop: 'events'}; +const eventsResource: FirehoseResource = { isList: true, kind: EventModel.kind, prop: 'events' }; -const EventsCard_: React.FC = ({ watchK8sResource, stopWatchK8sResource, resources }) => { +const EventsCard_: React.FC = ({ + watchK8sResource, + stopWatchK8sResource, + resources, +}) => { React.useEffect(() => { watchK8sResource(eventsResource); return () => stopWatchK8sResource(eventsResource); diff --git a/frontend/public/components/dashboards-page/overview-dashboard/health-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/health-card.tsx index 1a801028613..8e657182888 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/health-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/health-card.tsx @@ -9,17 +9,32 @@ import { DashboardCardTitle, DashboardCardPopupLink, } from '../../dashboard/dashboard-card'; -import { AlertsBody, AlertItem, getAlerts, HealthBody, HealthItem } from '../../dashboard/health-card'; +import { + AlertsBody, + AlertItem, + getAlerts, + HealthBody, + HealthItem, +} from '../../dashboard/health-card'; import { HealthState } from '../../dashboard/health-card/states'; import { coFetch } from '../../../co-fetch'; import { FLAGS } from '../../../const'; import { DashboardItemProps, withDashboardResources } from '../with-dashboard-resources'; import { getBrandingDetails } from '../../masthead'; import { RootState } from '../../../redux'; -import { connectToFlags, flagPending, featureReducerName, FlagsObject, WithFlagsProps } from '../../../reducers/features'; +import { + connectToFlags, + flagPending, + featureReducerName, + FlagsObject, + WithFlagsProps, +} from '../../../reducers/features'; import { getFlagsForExtensions, isDashboardExtensionInUse } from '../utils'; import { uniqueResource } from './utils'; -import { isDashboardsOverviewHealthURLSubsystem, isDashboardsOverviewHealthPrometheusSubsystem } from '@console/plugin-sdk'; +import { + isDashboardsOverviewHealthURLSubsystem, + isDashboardsOverviewHealthPrometheusSubsystem, +} from '@console/plugin-sdk'; import { PrometheusResponse } from '../../graphs'; export const HEALTHY = 'is healthy'; @@ -28,9 +43,9 @@ export const ERROR = 'is in an error state'; const getClusterHealth = (subsystemStates: Array): ClusterHealth => { let healthState: ClusterHealth = { state: HealthState.OK, message: 'Cluster is healthy' }; const subsystemBySeverity = { - error: subsystemStates.filter(subsystem => subsystem.state === HealthState.ERROR), - warning: subsystemStates.filter(subsystem => subsystem.state === HealthState.WARNING), - loading: subsystemStates.filter(subsystem => subsystem.state === HealthState.LOADING), + error: subsystemStates.filter((subsystem) => subsystem.state === HealthState.ERROR), + warning: subsystemStates.filter((subsystem) => subsystem.state === HealthState.WARNING), + loading: subsystemStates.filter((subsystem) => subsystem.state === HealthState.LOADING), }; if (subsystemBySeverity.loading.length > 0) { @@ -39,18 +54,27 @@ const getClusterHealth = (subsystemStates: Array): ClusterHealt healthState = subsystemBySeverity.error.length === 1 ? subsystemBySeverity.error[0] - : { state: HealthState.ERROR, message: 'Multiple errors', details: 'Cluster health is degraded' }; + : { + state: HealthState.ERROR, + message: 'Multiple errors', + details: 'Cluster health is degraded', + }; } else if (subsystemBySeverity.warning.length > 0) { healthState = subsystemBySeverity.warning.length === 1 ? subsystemBySeverity.warning[0] - : { state: HealthState.WARNING, message: 'Multiple warnings', details: 'Cluster health is degraded' }; + : { + state: HealthState.WARNING, + message: 'Multiple warnings', + details: 'Cluster health is degraded', + }; } return healthState; }; -const getName = (openshiftFlag: boolean): string => openshiftFlag ? getBrandingDetails().productName : 'Kubernetes'; +const getName = (openshiftFlag: boolean): string => + openshiftFlag ? getBrandingDetails().productName : 'Kubernetes'; const getK8sHealthState = (openshiftFlag: boolean, k8sHealth: any): SubsystemHealth => { if (!k8sHealth) { @@ -61,7 +85,7 @@ const getK8sHealthState = (openshiftFlag: boolean, k8sHealth: any): SubsystemHea : { message: `${getName(openshiftFlag)} ${ERROR}`, state: HealthState.ERROR }; }; -const fetchK8sHealth = async(url) => { +const fetchK8sHealth = async (url) => { const response = await coFetch(url); return response.text(); }; @@ -71,157 +95,162 @@ const mapStateToProps = (state: RootState) => ({ }); const getSubsystems = (flags: FlagsObject) => - plugins.registry.getDashboardsOverviewHealthSubsystems().filter(e => isDashboardExtensionInUse(e, flags)); - -const HealthCard_ = connect(mapStateToProps)(({ - watchURL, - stopWatchURL, - watchPrometheus, - stopWatchPrometheusQuery, - watchAlerts, - stopWatchAlerts, - watchK8sResource, - stopWatchK8sResource, - resources, - urlResults, - prometheusResults, - alertsResults, - openshiftFlag, - flags = {}, -}: HealthProps) => { - React.useEffect(() => { - const subsystems = getSubsystems(flags); - watchURL('healthz', fetchK8sHealth); - watchAlerts(); - - subsystems.forEach((subsystem, index) => { - if (isDashboardsOverviewHealthURLSubsystem(subsystem)) { - const { url, fetch } = subsystem.properties; - watchURL(url, fetch); - } else if (isDashboardsOverviewHealthPrometheusSubsystem(subsystem)) { - const { query, resource } = subsystem.properties; - watchPrometheus(query); - if (resource) { - watchK8sResource(uniqueResource(resource, index)); - } - } - }); + plugins.registry + .getDashboardsOverviewHealthSubsystems() + .filter((e) => isDashboardExtensionInUse(e, flags)); - return () => { - stopWatchURL('healthz'); - stopWatchAlerts(); +const HealthCard_ = connect(mapStateToProps)( + ({ + watchURL, + stopWatchURL, + watchPrometheus, + stopWatchPrometheusQuery, + watchAlerts, + stopWatchAlerts, + watchK8sResource, + stopWatchK8sResource, + resources, + urlResults, + prometheusResults, + alertsResults, + openshiftFlag, + flags = {}, + }: HealthProps) => { + React.useEffect(() => { + const subsystems = getSubsystems(flags); + watchURL('healthz', fetchK8sHealth); + watchAlerts(); subsystems.forEach((subsystem, index) => { if (isDashboardsOverviewHealthURLSubsystem(subsystem)) { - stopWatchURL(subsystem.properties.url); + const { url, fetch } = subsystem.properties; + watchURL(url, fetch); } else if (isDashboardsOverviewHealthPrometheusSubsystem(subsystem)) { const { query, resource } = subsystem.properties; - stopWatchPrometheusQuery(query); + watchPrometheus(query); if (resource) { - stopWatchK8sResource(uniqueResource(resource, index)); + watchK8sResource(uniqueResource(resource, index)); } } }); - }; - }, - // TODO: to be removed: use JSON.stringify(flags) to avoid deep comparison of flags object - /* eslint-disable react-hooks/exhaustive-deps */ - [ - watchURL, - stopWatchURL, - watchPrometheus, - stopWatchPrometheusQuery, - watchAlerts, - stopWatchAlerts, - watchK8sResource, - stopWatchK8sResource, - stopWatchAlerts, - JSON.stringify(flags), - ]); - /* eslint-enable react-hooks/exhaustive-deps */ - - const subsystems = getSubsystems(flags); - const k8sHealth = urlResults.getIn(['healthz', 'data']); - const k8sHealthError = urlResults.getIn(['healthz', 'loadError']); - const k8sHealthState = getK8sHealthState(openshiftFlag, k8sHealth || k8sHealthError); - - const subsystemsHealths = subsystems.map((subsystem, index) => { - if (isDashboardsOverviewHealthURLSubsystem(subsystem)) { - const urlData = urlResults.getIn([subsystem.properties.url, 'data']); - const urlError = urlResults.getIn([subsystem.properties.url, 'loadError']); - return subsystem.properties.healthHandler(urlData, !!urlError); - } - const queryData = prometheusResults.getIn([subsystem.properties.query, 'data']) as PrometheusResponse; - const queryError = prometheusResults.getIn([subsystem.properties.query, 'loadError']); - const resource = subsystem.properties.resource - ? resources[uniqueResource(subsystem.properties.resource, index).prop] - : null; - return subsystem.properties.healthHandler(queryData, !!queryError, resource); - }); - - const healthState = getClusterHealth([k8sHealthState, ...subsystemsHealths]); - const alerts = getAlerts(alertsResults); - - return ( - - - Cluster Health - {subsystems.length > 0 && !flagPending(openshiftFlag) && ( - + + return () => { + stopWatchURL('healthz'); + stopWatchAlerts(); + + subsystems.forEach((subsystem, index) => { + if (isDashboardsOverviewHealthURLSubsystem(subsystem)) { + stopWatchURL(subsystem.properties.url); + } else if (isDashboardsOverviewHealthPrometheusSubsystem(subsystem)) { + const { query, resource } = subsystem.properties; + stopWatchPrometheusQuery(query); + if (resource) { + stopWatchK8sResource(uniqueResource(resource, index)); + } + } + }); + }; + } /* eslint-disable react-hooks/exhaustive-deps */, [ + // TODO: to be removed: use JSON.stringify(flags) to avoid deep comparison of flags object + watchURL, + stopWatchURL, + watchPrometheus, + stopWatchPrometheusQuery, + watchAlerts, + stopWatchAlerts, + watchK8sResource, + stopWatchK8sResource, + stopWatchAlerts, + JSON.stringify(flags), + ]); + /* eslint-enable react-hooks/exhaustive-deps */ + + const subsystems = getSubsystems(flags); + const k8sHealth = urlResults.getIn(['healthz', 'data']); + const k8sHealthError = urlResults.getIn(['healthz', 'loadError']); + const k8sHealthState = getK8sHealthState(openshiftFlag, k8sHealth || k8sHealthError); + + const subsystemsHealths = subsystems.map((subsystem, index) => { + if (isDashboardsOverviewHealthURLSubsystem(subsystem)) { + const urlData = urlResults.getIn([subsystem.properties.url, 'data']); + const urlError = urlResults.getIn([subsystem.properties.url, 'loadError']); + return subsystem.properties.healthHandler(urlData, !!urlError); + } + const queryData = prometheusResults.getIn([ + subsystem.properties.query, + 'data', + ]) as PrometheusResponse; + const queryError = prometheusResults.getIn([subsystem.properties.query, 'loadError']); + const resource = subsystem.properties.resource + ? resources[uniqueResource(subsystem.properties.resource, index).prop] + : null; + return subsystem.properties.healthHandler(queryData, !!queryError, resource); + }); + + const healthState = getClusterHealth([k8sHealthState, ...subsystemsHealths]); + const alerts = getAlerts(alertsResults); + + return ( + + + Cluster Health + {subsystems.length > 0 && !flagPending(openshiftFlag) && ( + + + {subsystemsHealths.map((subsystem, index) => ( +
    +
    + +
    + ))} + + )} + + + - {subsystemsHealths.map((subsystem, index) => ( -
    -
    - -
    - ))} - + + + + {alerts.length > 0 && ( + + + Alerts + + + + {alerts.map((alert) => ( + + ))} + + + )} - - - - - - - - {alerts.length > 0 && - - - Alerts - - - - {alerts.map(alert => ( - - ))} - - - - } - - ); -}); + + ); + }, +); export const HealthCard = connectToFlags( - ...getFlagsForExtensions(plugins.registry.getDashboardsOverviewHealthSubsystems()) + ...getFlagsForExtensions(plugins.registry.getDashboardsOverviewHealthSubsystems()), )(withDashboardResources(HealthCard_)); type ClusterHealth = { state: HealthState; message?: string; - details?: string, + details?: string; }; export type SubsystemHealth = { @@ -229,6 +258,7 @@ export type SubsystemHealth = { state: HealthState; }; -type HealthProps = DashboardItemProps & WithFlagsProps & { - openshiftFlag: boolean; -} +type HealthProps = DashboardItemProps & + WithFlagsProps & { + openshiftFlag: boolean; + }; diff --git a/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx index 6294251979b..0fac819b216 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/inventory-card.tsx @@ -12,7 +12,11 @@ import { ResourceInventoryItem } from '../../dashboard/inventory-card/inventory- import { DashboardItemProps, withDashboardResources } from '../with-dashboard-resources'; import { PodModel, NodeModel, PersistentVolumeClaimModel } from '../../../models'; import { K8sResourceKind, PodKind } from '../../../module/k8s'; -import { getPodStatusGroups, getNodeStatusGroups, getPVCStatusGroups } from '../../dashboard/inventory-card/utils'; +import { + getPodStatusGroups, + getNodeStatusGroups, + getPVCStatusGroups, +} from '../../dashboard/inventory-card/utils'; import { FirehoseResource } from '../../utils'; import { connectToFlags, FlagsObject, WithFlagsProps } from '../../../reducers/features'; import { getFlagsForExtensions, isDashboardExtensionInUse } from '../utils'; @@ -38,14 +42,18 @@ const k8sResources: FirehoseResource[] = [ ]; const getItems = (flags: FlagsObject) => - plugins.registry.getDashboardsOverviewInventoryItems().filter(e => isDashboardExtensionInUse(e, flags)); + plugins.registry + .getDashboardsOverviewInventoryItems() + .filter((e) => isDashboardExtensionInUse(e, flags)); const getResourcesToWatch = (flags: FlagsObject): FirehoseResource[] => { const allResources = [...k8sResources]; getItems(flags).forEach((item, index) => { allResources.push(uniqueResource(item.properties.resource, index)); if (item.properties.additionalResources) { - item.properties.additionalResources.forEach(ar => allResources.push(uniqueResource(ar, index))); + item.properties.additionalResources.forEach((ar) => + allResources.push(uniqueResource(ar, index)), + ); } }); return allResources; @@ -59,10 +67,10 @@ const InventoryCard_: React.FC = ({ }) => { React.useEffect(() => { const resourcesToWatch = getResourcesToWatch(flags); - resourcesToWatch.forEach(r => watchK8sResource(r)); + resourcesToWatch.forEach((r) => watchK8sResource(r)); return () => { - resourcesToWatch.forEach(r => stopWatchK8sResource(r)); + resourcesToWatch.forEach((r) => stopWatchK8sResource(r)); }; // TODO: to be removed: use JSON.stringify(flags) to avoid deep comparison of flags object // eslint-disable-next-line react-hooks/exhaustive-deps @@ -89,9 +97,28 @@ const InventoryCard_: React.FC = ({ - - - + + + {pluginItems.map((item, index) => { const resource = _.get(resources, uniqueResource(item.properties.resource, index).prop); const resourceLoaded = _.get(resource, 'loaded'); @@ -100,16 +127,21 @@ const InventoryCard_: React.FC = ({ const additionalResources = {}; if (item.properties.additionalResources) { - item.properties.additionalResources.forEach(ar => { + item.properties.additionalResources.forEach((ar) => { additionalResources[ar.prop] = _.get(resources, uniqueResource(ar, index).prop); }); } - const additionalResourcesLoaded = Object.keys(additionalResources).every(key => - !additionalResources[key] || additionalResources[key].loaded || additionalResources[key].loadError + const additionalResourcesLoaded = Object.keys(additionalResources).every( + (key) => + !additionalResources[key] || + additionalResources[key].loaded || + additionalResources[key].loadError, ); const additionalResourcesData = {}; - Object.keys(additionalResources).forEach(key => additionalResourcesData[key] = _.get(additionalResources[key], 'data', [])); + Object.keys(additionalResources).forEach( + (key) => (additionalResourcesData[key] = _.get(additionalResources[key], 'data', [])), + ); return ( = () => { - const mainCards = [{Card: HealthCard}, {Card: CapacityCard}, {Card: UtilizationCard}]; - const leftCards = [{Card: DetailsCard}, {Card: InventoryCard}]; - const rightCards = [{Card: EventsCard}, {Card: TopConsumersCard}]; + const mainCards = [{ Card: HealthCard }, { Card: CapacityCard }, { Card: UtilizationCard }]; + const leftCards = [{ Card: DetailsCard }, { Card: InventoryCard }]; + const rightCards = [{ Card: EventsCard }, { Card: TopConsumersCard }]; return ( diff --git a/frontend/public/components/dashboards-page/overview-dashboard/queries.ts b/frontend/public/components/dashboards-page/overview-dashboard/queries.ts index 243eac2db79..ef99308065c 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/queries.ts +++ b/frontend/public/components/dashboards-page/overview-dashboard/queries.ts @@ -18,21 +18,32 @@ export enum OverviewQuery { const overviewQueries = { [OverviewQuery.MEMORY_TOTAL]: 'sum(kube_node_status_capacity_memory_bytes)', - [OverviewQuery.MEMORY_UTILIZATION]: '(sum(kube_node_status_capacity_memory_bytes) - sum(kube_node_status_allocatable_memory_bytes))[60m:5m]', + [OverviewQuery.MEMORY_UTILIZATION]: + '(sum(kube_node_status_capacity_memory_bytes) - sum(kube_node_status_allocatable_memory_bytes))[60m:5m]', [OverviewQuery.NETWORK_TOTAL]: 'sum(avg by(instance)(node_network_speed_bytes))', - [OverviewQuery.NETWORK_UTILIZATION]: 'sum(instance:node_network_transmit_bytes_excluding_lo:rate1m+instance:node_network_receive_bytes_excluding_lo:rate1m)', - [OverviewQuery.CPU_UTILIZATION]: '(avg(instance:node_cpu_utilisation:rate1m{job="node-exporter"}) * 100)[60m:5m]', - [OverviewQuery.STORAGE_UTILIZATION]: '(sum(node_filesystem_size_bytes) - sum(node_filesystem_free_bytes))[60m:5m]', + [OverviewQuery.NETWORK_UTILIZATION]: + 'sum(instance:node_network_transmit_bytes_excluding_lo:rate1m+instance:node_network_receive_bytes_excluding_lo:rate1m)', + [OverviewQuery.CPU_UTILIZATION]: + '(avg(instance:node_cpu_utilisation:rate1m{job="node-exporter"}) * 100)[60m:5m]', + [OverviewQuery.STORAGE_UTILIZATION]: + '(sum(node_filesystem_size_bytes) - sum(node_filesystem_free_bytes))[60m:5m]', [OverviewQuery.STORAGE_TOTAL]: 'sum(node_filesystem_size_bytes)', - [OverviewQuery.PODS_BY_CPU]: 'sort_desc(sum(rate(container_cpu_usage_seconds_total{container_name="",pod!=""}[5m])) BY (pod, namespace))', - [OverviewQuery.PODS_BY_MEMORY]: 'sort_desc(sum(container_memory_working_set_bytes{container="",pod!=""}) BY (pod, namespace))', - [OverviewQuery.PODS_BY_STORAGE]: 'sort_desc(avg by (pod, namespace)(irate(container_fs_io_time_seconds_total{container="POD", pod!=""}[1m])))', - [OverviewQuery.PODS_BY_NETWORK]: 'sort_desc(sum by (pod, namespace)(irate(container_network_receive_bytes_total{container="POD", pod!=""}[1m])' + + [OverviewQuery.PODS_BY_CPU]: + 'sort_desc(sum(rate(container_cpu_usage_seconds_total{container_name="",pod!=""}[5m])) BY (pod, namespace))', + [OverviewQuery.PODS_BY_MEMORY]: + 'sort_desc(sum(container_memory_working_set_bytes{container="",pod!=""}) BY (pod, namespace))', + [OverviewQuery.PODS_BY_STORAGE]: + 'sort_desc(avg by (pod, namespace)(irate(container_fs_io_time_seconds_total{container="POD", pod!=""}[1m])))', + [OverviewQuery.PODS_BY_NETWORK]: + 'sort_desc(sum by (pod, namespace)(irate(container_network_receive_bytes_total{container="POD", pod!=""}[1m])' + ' + irate(container_network_transmit_bytes_total{container="POD", pod!=""}[1m])))', [OverviewQuery.NODES_BY_CPU]: 'sort_desc(instance:node_cpu_utilisation:rate1m)', - [OverviewQuery.NODES_BY_MEMORY]: 'sort_desc(node_memory_MemTotal_bytes{job="node-exporter"} - node_memory_MemAvailable_bytes{job="node-exporter"})', - [OverviewQuery.NODES_BY_STORAGE]: 'sort_desc(avg by (instance) (instance_device:node_disk_io_time_seconds:rate1m))', - [OverviewQuery.NODES_BY_NETWORK]: 'sort_desc(sum by (instance) (instance:node_network_transmit_bytes_excluding_lo:rate1m+instance:node_network_receive_bytes_excluding_lo:rate1m))', + [OverviewQuery.NODES_BY_MEMORY]: + 'sort_desc(node_memory_MemTotal_bytes{job="node-exporter"} - node_memory_MemAvailable_bytes{job="node-exporter"})', + [OverviewQuery.NODES_BY_STORAGE]: + 'sort_desc(avg by (instance) (instance_device:node_disk_io_time_seconds:rate1m))', + [OverviewQuery.NODES_BY_NETWORK]: + 'sort_desc(sum by (instance) (instance:node_network_transmit_bytes_excluding_lo:rate1m+instance:node_network_receive_bytes_excluding_lo:rate1m))', }; export const capacityQueries = { @@ -54,7 +65,7 @@ export const utilizationQueries = { export const topConsumersQueries = { [OverviewQuery.PODS_BY_CPU]: overviewQueries[OverviewQuery.PODS_BY_CPU], [OverviewQuery.PODS_BY_MEMORY]: overviewQueries[OverviewQuery.PODS_BY_MEMORY], - [OverviewQuery.PODS_BY_STORAGE]: overviewQueries [OverviewQuery.PODS_BY_STORAGE], + [OverviewQuery.PODS_BY_STORAGE]: overviewQueries[OverviewQuery.PODS_BY_STORAGE], [OverviewQuery.PODS_BY_NETWORK]: overviewQueries[OverviewQuery.PODS_BY_NETWORK], [OverviewQuery.NODES_BY_CPU]: overviewQueries[OverviewQuery.NODES_BY_CPU], [OverviewQuery.NODES_BY_MEMORY]: overviewQueries[OverviewQuery.NODES_BY_MEMORY], diff --git a/frontend/public/components/dashboards-page/overview-dashboard/top-consumers-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/top-consumers-card.tsx index 3f1bb0694a4..d0fd88c69b0 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/top-consumers-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/top-consumers-card.tsx @@ -10,10 +10,7 @@ import { DashboardCardTitle, } from '../../dashboard/dashboard-card'; import { ConsumersBody, ConsumersFilter, metricTypeMap } from '../../dashboard/top-consumers-card'; -import { - NODES, - PODS, -} from '../../dashboard/top-consumers-card/strings'; +import { NODES, PODS } from '../../dashboard/top-consumers-card/strings'; import { DashboardItemProps, withDashboardResources } from '../with-dashboard-resources'; import { Dropdown, ExternalLink, resourcePathFromModel } from '../../utils'; import { OverviewQuery, topConsumersQueries } from './queries'; @@ -52,17 +49,20 @@ const topConsumersQueryMap: TopConsumersMap = { }; const getTopConsumersQueries = (flags: FlagsObject): TopConsumersMap => { - const topConsumers = {...topConsumersQueryMap}; - plugins.registry.getDashboardsOverviewTopConsumerItems().filter(e => isDashboardExtensionInUse(e, flags)).forEach(pluginItem => { - if (!topConsumers[pluginItem.properties.name]) { - topConsumers[pluginItem.properties.name] = { - model: pluginItem.properties.model, - metric: pluginItem.properties.metric, - queries: pluginItem.properties.queries, - mutator: pluginItem.properties.mutator, - }; - } - }); + const topConsumers = { ...topConsumersQueryMap }; + plugins.registry + .getDashboardsOverviewTopConsumerItems() + .filter((e) => isDashboardExtensionInUse(e, flags)) + .forEach((pluginItem) => { + if (!topConsumers[pluginItem.properties.name]) { + topConsumers[pluginItem.properties.name] = { + model: pluginItem.properties.model, + metric: pluginItem.properties.metric, + queries: pluginItem.properties.queries, + mutator: pluginItem.properties.mutator, + }; + } + }); return topConsumers; }; @@ -76,144 +76,166 @@ const BarLink: React.FC = React.memo(({ model, title, namespace }) {title} )); -const TopConsumersCard_ = connectToURLs(MonitoringRoutes.Prometheus)(({ - watchPrometheus, - stopWatchPrometheusQuery, - prometheusResults, - watchK8sResource, - stopWatchK8sResource, - resources, - urls, - flags = {}, -}: TopConsumersCardProps) => { - const [type, setType] = React.useState(PODS); - const [sortOption, setSortOption] = React.useState(MetricType.CPU); - - React.useEffect(() => { - const topConsumersMap = getTopConsumersQueries(flags); - const currentQuery = topConsumersMap[type].queries[sortOption]; - watchPrometheus(currentQuery); - - const k8sResource = getResourceToWatch(topConsumersMap[type].model); - watchK8sResource(k8sResource); +const TopConsumersCard_ = connectToURLs(MonitoringRoutes.Prometheus)( + ({ + watchPrometheus, + stopWatchPrometheusQuery, + prometheusResults, + watchK8sResource, + stopWatchK8sResource, + resources, + urls, + flags = {}, + }: TopConsumersCardProps) => { + const [type, setType] = React.useState(PODS); + const [sortOption, setSortOption] = React.useState(MetricType.CPU); - return () => { - stopWatchPrometheusQuery(currentQuery); - stopWatchK8sResource(k8sResource); - }; // TODO: to be removed: use JSON.stringify(flags) to avoid deep comparison of flags object - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [watchPrometheus, stopWatchPrometheusQuery, watchK8sResource, stopWatchK8sResource, type, sortOption, JSON.stringify(flags)]); - - const topConsumersMap = getTopConsumersQueries(flags); - const topConsumersType = topConsumersMap[type]; - const metricTypeSort = metricTypeMap[sortOption]; - const currentQuery = topConsumersType.queries[sortOption]; - const topConsumersData = prometheusResults.getIn([currentQuery, 'data']); - const topConsumersError = prometheusResults.getIn([currentQuery, 'loadError']); - - const stats = getInstantVectorStats(topConsumersData, topConsumersType.metric, metricTypeSort.humanize); - const data = topConsumersType.mutator ? topConsumersType.mutator(stats) : stats; - - const top5Data = []; - const consumersLoaded = _.get(resources, ['consumers', 'loaded']); - const consumersLoadError = _.get(resources, ['consumers', 'loadError']); - const consumersData = _.get(resources, ['consumers', 'data']) as K8sResourceKind[]; - - if (consumersLoaded && !consumersLoadError) { - for (const d of data) { - const consumerExists = consumersData.some(consumer => - getName(consumer) === d.metric[topConsumersType.metric] && - (topConsumersType.model.namespaced ? getNamespace(consumer) === d.metric.namespace : true) - ); - if (consumerExists) { - top5Data.push(d); - } - if (top5Data.length === 5) { - break; + /* eslint-disable react-hooks/exhaustive-deps */ + React.useEffect(() => { + const topConsumersMap = getTopConsumersQueries(flags); + const currentQuery = topConsumersMap[type].queries[sortOption]; + watchPrometheus(currentQuery); + + const k8sResource = getResourceToWatch(topConsumersMap[type].model); + watchK8sResource(k8sResource); + + return () => { + stopWatchPrometheusQuery(currentQuery); + stopWatchK8sResource(k8sResource); + }; + }, [ + watchPrometheus, + stopWatchPrometheusQuery, + watchK8sResource, + stopWatchK8sResource, + type, + sortOption, + JSON.stringify(flags), + ]); + /* eslint-enable react-hooks/exhaustive-deps */ + + const topConsumersMap = getTopConsumersQueries(flags); + const topConsumersType = topConsumersMap[type]; + const metricTypeSort = metricTypeMap[sortOption]; + const currentQuery = topConsumersType.queries[sortOption]; + const topConsumersData = prometheusResults.getIn([currentQuery, 'data']); + const topConsumersError = prometheusResults.getIn([currentQuery, 'loadError']); + + const stats = getInstantVectorStats( + topConsumersData, + topConsumersType.metric, + metricTypeSort.humanize, + ); + const data = topConsumersType.mutator ? topConsumersType.mutator(stats) : stats; + + const top5Data = []; + const consumersLoaded = _.get(resources, ['consumers', 'loaded']); + const consumersLoadError = _.get(resources, ['consumers', 'loadError']); + const consumersData = _.get(resources, ['consumers', 'data']) as K8sResourceKind[]; + + if (consumersLoaded && !consumersLoadError) { + for (const d of data) { + const consumerExists = consumersData.some( + (consumer) => + getName(consumer) === d.metric[topConsumersType.metric] && + (topConsumersType.model.namespaced + ? getNamespace(consumer) === d.metric.namespace + : true), + ); + if (consumerExists) { + top5Data.push(d); + } + if (top5Data.length === 5) { + break; + } } } - } - - const url = getPrometheusExpressionBrowserURL(urls, [`topk(20, ${currentQuery})`]); - - const LabelComponent = React.useCallback(({ title, metric }) => ( - - ), [topConsumersType.model]); - - return ( - - - Top Consumers - - - - key)} - onChange={(v: string) => { - setType(v); - if (!topConsumersMap[v].queries[sortOption]) { - setSortOption(Object.keys(topConsumersMap[v].queries)[0] as MetricType); + + const url = getPrometheusExpressionBrowserURL(urls, [`topk(20, ${currentQuery})`]); + + const LabelComponent = React.useCallback( + ({ title, metric }) => ( + + ), + [topConsumersType.model], + ); + + return ( + + + Top Consumers + + + + key)} + onChange={(v: string) => { + setType(v); + if (!topConsumersMap[v].queries[sortOption]) { + setSortOption(Object.keys(topConsumersMap[v].queries)[0] as MetricType); + } + }} + selectedKey={type} + title={type} + /> + key)} + onChange={(v: MetricType) => setSortOption(v)} + selectedKey={sortOption} + title={sortOption} + /> + + + - key)} - onChange={(v: MetricType) => setSortOption(v)} - selectedKey={sortOption} - title={sortOption} - /> - - - - {url &&
    - -
    } -
    -
    -
    - ); -}); + LabelComponent={LabelComponent} + /> + {url && ( +
    + +
    + )} + +
    + + ); + }, +); export const TopConsumersCard = connectToFlags( ...getFlagsForExtensions(plugins.registry.getDashboardsOverviewTopConsumerItems()), )(withDashboardResources(TopConsumersCard_)); -type TopConsumersCardProps = DashboardItemProps & WithFlagsProps & { - urls?: string[]; -}; +type TopConsumersCardProps = DashboardItemProps & + WithFlagsProps & { + urls?: string[]; + }; type BarLinkProps = { title: string; namespace?: string; model: K8sKind; -} +}; export type ConsumerMutator = (data: DataPoint[]) => DataPoint[]; type TopConsumersMap = { [key: string]: { - model: K8sKind, - metric: string, - queries: { [key in MetricType]?: string }, - mutator?: ConsumerMutator, - }, + model: K8sKind; + metric: string; + queries: { [key in MetricType]?: string }; + mutator?: ConsumerMutator; + }; }; diff --git a/frontend/public/components/dashboards-page/overview-dashboard/utilization-card.tsx b/frontend/public/components/dashboards-page/overview-dashboard/utilization-card.tsx index d689fe8f581..a98ba7c8844 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/utilization-card.tsx +++ b/frontend/public/components/dashboards-page/overview-dashboard/utilization-card.tsx @@ -18,17 +18,22 @@ import { getFlagsForExtensions, isDashboardExtensionInUse } from '../utils'; const getQueries = (flags: FlagsObject) => { const pluginQueries = {}; - plugins.registry.getDashboardsOverviewQueries().filter(e => isDashboardExtensionInUse(e, flags)).forEach(pluginQuery => { - const queryKey = pluginQuery.properties.queryKey; - if (!pluginQueries[queryKey]) { - pluginQueries[queryKey] = pluginQuery.properties.query; - } - }); + plugins.registry + .getDashboardsOverviewQueries() + .filter((e) => isDashboardExtensionInUse(e, flags)) + .forEach((pluginQuery) => { + const queryKey = pluginQuery.properties.queryKey; + if (!pluginQueries[queryKey]) { + pluginQueries[queryKey] = pluginQuery.properties.query; + } + }); return _.defaults(pluginQueries, utilizationQueries); }; const getItems = (flags: FlagsObject) => - plugins.registry.getDashboardsOverviewUtilizationItems().filter(e => isDashboardExtensionInUse(e, flags)); + plugins.registry + .getDashboardsOverviewUtilizationItems() + .filter((e) => isDashboardExtensionInUse(e, flags)); const UtilizationCard_: React.FC = ({ watchPrometheus, @@ -38,14 +43,14 @@ const UtilizationCard_: React.FC = ({ }) => { React.useEffect(() => { const queries = getQueries(flags); - Object.keys(queries).forEach(key => watchPrometheus(queries[key])); + Object.keys(queries).forEach((key) => watchPrometheus(queries[key])); const pluginItems = getItems(flags); - pluginItems.forEach(item => watchPrometheus(item.properties.query)); + pluginItems.forEach((item) => watchPrometheus(item.properties.query)); return () => { - Object.keys(queries).forEach(key => stopWatchPrometheusQuery(queries[key])); - pluginItems.forEach(item => stopWatchPrometheusQuery(item.properties.query)); + Object.keys(queries).forEach((key) => stopWatchPrometheusQuery(queries[key])); + pluginItems.forEach((item) => stopWatchPrometheusQuery(item.properties.query)); }; // TODO: to be removed: use JSON.stringify(flags) to avoid deep comparison of flags object // eslint-disable-next-line react-hooks/exhaustive-deps @@ -53,11 +58,26 @@ const UtilizationCard_: React.FC = ({ const queries = getQueries(flags); const cpuUtilization = prometheusResults.getIn([queries[OverviewQuery.CPU_UTILIZATION], 'data']); - const cpuUtilizationError = prometheusResults.getIn([queries[OverviewQuery.CPU_UTILIZATION], 'loadError']); - const memoryUtilization = prometheusResults.getIn([queries[OverviewQuery.MEMORY_UTILIZATION], 'data']); - const memoryUtilizationError = prometheusResults.getIn([queries[OverviewQuery.MEMORY_UTILIZATION], 'loadError']); - const storageUtilization = prometheusResults.getIn([queries[OverviewQuery.STORAGE_UTILIZATION], 'data']); - const storageUtilizationError = prometheusResults.getIn([queries[OverviewQuery.STORAGE_UTILIZATION], 'loadError']); + const cpuUtilizationError = prometheusResults.getIn([ + queries[OverviewQuery.CPU_UTILIZATION], + 'loadError', + ]); + const memoryUtilization = prometheusResults.getIn([ + queries[OverviewQuery.MEMORY_UTILIZATION], + 'data', + ]); + const memoryUtilizationError = prometheusResults.getIn([ + queries[OverviewQuery.MEMORY_UTILIZATION], + 'loadError', + ]); + const storageUtilization = prometheusResults.getIn([ + queries[OverviewQuery.STORAGE_UTILIZATION], + 'data', + ]); + const storageUtilizationError = prometheusResults.getIn([ + queries[OverviewQuery.STORAGE_UTILIZATION], + 'loadError', + ]); const cpuStats = getRangeVectorStats(cpuUtilization); const memoryStats = getRangeVectorStats(memoryUtilization); @@ -71,7 +91,7 @@ const UtilizationCard_: React.FC = ({ Cluster Utilization - stat.x as Date)}> + stat.x as Date)}> = ({ title="Memory" data={memoryStats} error={memoryUtilizationError} - isLoading={!(memoryUtilization)} + isLoading={!memoryUtilization} humanizeValue={humanizeBinaryBytesWithoutB} query={queries[OverviewQuery.MEMORY_UTILIZATION]} /> @@ -92,7 +112,7 @@ const UtilizationCard_: React.FC = ({ title="Disk Usage" data={storageStats} error={storageUtilizationError} - isLoading={!(storageUtilization)} + isLoading={!storageUtilization} humanizeValue={humanizeBinaryBytesWithoutB} query={queries[OverviewQuery.STORAGE_UTILIZATION]} /> @@ -118,7 +138,9 @@ const UtilizationCard_: React.FC = ({ ); }; -export const UtilizationCard = connectToFlags(...getFlagsForExtensions([ - ...plugins.registry.getDashboardsOverviewQueries(), - ...plugins.registry.getDashboardsOverviewUtilizationItems(), -]))(withDashboardResources(UtilizationCard_)); +export const UtilizationCard = connectToFlags( + ...getFlagsForExtensions([ + ...plugins.registry.getDashboardsOverviewQueries(), + ...plugins.registry.getDashboardsOverviewUtilizationItems(), + ]), +)(withDashboardResources(UtilizationCard_)); diff --git a/frontend/public/components/dashboards-page/overview-dashboard/utils.ts b/frontend/public/components/dashboards-page/overview-dashboard/utils.ts index 5eaf7d5b3ca..55f7b8fcae9 100644 --- a/frontend/public/components/dashboards-page/overview-dashboard/utils.ts +++ b/frontend/public/components/dashboards-page/overview-dashboard/utils.ts @@ -1,6 +1,9 @@ import { FirehoseResource } from '../../utils'; -export const uniqueResource = (resource: FirehoseResource, prefix: string | number): FirehoseResource => ({ +export const uniqueResource = ( + resource: FirehoseResource, + prefix: string | number, +): FirehoseResource => ({ ...resource, prop: `${prefix}-${resource.prop}`, }); diff --git a/frontend/public/components/dashboards-page/with-dashboard-resources.tsx b/frontend/public/components/dashboards-page/with-dashboard-resources.tsx index 01efed165a9..c75b60ced73 100644 --- a/frontend/public/components/dashboards-page/with-dashboard-resources.tsx +++ b/frontend/public/components/dashboards-page/with-dashboard-resources.tsx @@ -25,7 +25,7 @@ import { K8sResourceKind } from '../../module/k8s'; import { PrometheusResponse } from '../graphs'; import { Alert } from '../monitoring'; -const mapDispatchToProps: DispatchToProps = dispatch => ({ +const mapDispatchToProps: DispatchToProps = (dispatch) => ({ watchURL: (url, fetch) => dispatch(watchURL(url, fetch)), stopWatchURL: (url) => dispatch(stopWatchURL(url)), watchPrometheusQuery: (query) => dispatch(watchPrometheusQuery(query)), @@ -36,16 +36,26 @@ const mapDispatchToProps: DispatchToProps = dispatch => ({ const mapStateToProps = (state: RootState) => ({ [RESULTS_TYPE.URL]: state.dashboards.get(RESULTS_TYPE.URL), - [RESULTS_TYPE.PROMETHEUS]: state.dashboards.get(RESULTS_TYPE.PROMETHEUS) as RequestMap, + [RESULTS_TYPE.PROMETHEUS]: state.dashboards.get(RESULTS_TYPE.PROMETHEUS) as RequestMap< + PrometheusResponse + >, [RESULTS_TYPE.ALERTS]: state.dashboards.get(RESULTS_TYPE.ALERTS) as RequestMap, }); type StateProps = ReturnType; type DispatchProps = ReturnType; -export const withDashboardResources =

    (WrappedComponent: React.ComponentType

    ) => - connect>(mapStateToProps, mapDispatchToProps)( - class WithDashboardResources extends React.Component { +export const withDashboardResources =

    ( + WrappedComponent: React.ComponentType

    , +) => + connect>( + mapStateToProps, + mapDispatchToProps, + )( + class WithDashboardResources extends React.Component< + WithDashboardResourcesProps, + WithDashboardResourcesState + > { private urls: Array = []; private queries: Array = []; private watchingAlerts: boolean = false; @@ -57,18 +67,29 @@ export const withDashboardResources =

    (WrappedComp }; } - shouldComponentUpdate(nextProps: WithDashboardResourcesProps, nextState: WithDashboardResourcesState) { - const urlResultChanged = this.urls.some(urlKey => - this.props[RESULTS_TYPE.URL].getIn([urlKey, 'data']) !== nextProps[RESULTS_TYPE.URL].getIn([urlKey, 'data']) || - this.props[RESULTS_TYPE.URL].getIn([urlKey, 'loadError']) !== nextProps[RESULTS_TYPE.URL].getIn([urlKey, 'loadError']) + shouldComponentUpdate( + nextProps: WithDashboardResourcesProps, + nextState: WithDashboardResourcesState, + ) { + const urlResultChanged = this.urls.some( + (urlKey) => + this.props[RESULTS_TYPE.URL].getIn([urlKey, 'data']) !== + nextProps[RESULTS_TYPE.URL].getIn([urlKey, 'data']) || + this.props[RESULTS_TYPE.URL].getIn([urlKey, 'loadError']) !== + nextProps[RESULTS_TYPE.URL].getIn([urlKey, 'loadError']), ); - const queryResultChanged = this.queries.some(query => - this.props[RESULTS_TYPE.PROMETHEUS].getIn([query, 'data']) !== nextProps[RESULTS_TYPE.PROMETHEUS].getIn([query, 'data']) || - this.props[RESULTS_TYPE.PROMETHEUS].getIn([query, 'loadError']) !== nextProps[RESULTS_TYPE.PROMETHEUS].getIn([query, 'loadError']) + const queryResultChanged = this.queries.some( + (query) => + this.props[RESULTS_TYPE.PROMETHEUS].getIn([query, 'data']) !== + nextProps[RESULTS_TYPE.PROMETHEUS].getIn([query, 'data']) || + this.props[RESULTS_TYPE.PROMETHEUS].getIn([query, 'loadError']) !== + nextProps[RESULTS_TYPE.PROMETHEUS].getIn([query, 'loadError']), ); const alertsResultChanged = - this.props[RESULTS_TYPE.ALERTS].getIn([ALERTS_KEY, 'data']) !== nextProps[RESULTS_TYPE.PROMETHEUS].getIn([ALERTS_KEY, 'data']) || - this.props[RESULTS_TYPE.ALERTS].getIn([ALERTS_KEY, 'loadError']) !== nextProps[RESULTS_TYPE.PROMETHEUS].getIn([ALERTS_KEY, 'loadError']); + this.props[RESULTS_TYPE.ALERTS].getIn([ALERTS_KEY, 'data']) !== + nextProps[RESULTS_TYPE.PROMETHEUS].getIn([ALERTS_KEY, 'data']) || + this.props[RESULTS_TYPE.ALERTS].getIn([ALERTS_KEY, 'loadError']) !== + nextProps[RESULTS_TYPE.PROMETHEUS].getIn([ALERTS_KEY, 'loadError']); const k8sResourcesChanged = this.state.k8sResources !== nextState.k8sResources; return ( @@ -85,7 +106,7 @@ export const withDashboardResources =

    (WrappedComp this.props.watchURL(url, fetch); }; - watchPrometheus: WatchPrometheus = query => { + watchPrometheus: WatchPrometheus = (query) => { this.queries.push(query); this.props.watchPrometheusQuery(query); }; @@ -100,20 +121,21 @@ export const withDashboardResources =

    (WrappedComp this.props.stopWatchAlerts(); }; - watchK8sResource: WatchK8sResource = resource => { + watchK8sResource: WatchK8sResource = (resource) => { this.setState((state: WithDashboardResourcesState) => ({ k8sResources: [...state.k8sResources, resource], })); }; - stopWatchK8sResource: StopWatchK8sResource = resource => { + stopWatchK8sResource: StopWatchK8sResource = (resource) => { this.setState((state: WithDashboardResourcesState) => ({ - k8sResources: state.k8sResources.filter(r => r.prop !== resource.prop), + k8sResources: state.k8sResources.filter((r) => r.prop !== resource.prop), })); }; getExternalProps = (props) => { - return _.omit(props, + return _.omit( + props, 'watchURL', 'stopWatchURL', 'watchPrometheusQuery', @@ -124,7 +146,7 @@ export const withDashboardResources =

    (WrappedComp RESULTS_TYPE.PROMETHEUS, RESULTS_TYPE.ALERTS, ); - } + }; render() { return ( @@ -146,17 +168,19 @@ export const withDashboardResources =

    (WrappedComp ); } - } + }, ); -type DispatchToProps = (dispatch: any) => { +type DispatchToProps = ( + dispatch: any, +) => { watchURL: WatchURL; stopWatchURL: StopWatchURL; watchPrometheusQuery: WatchPrometheus; stopWatchPrometheusQuery: StopWatchPrometheus; watchAlerts: WatchAlerts; stopWatchAlerts: StopWatchAlerts; -} +}; type WatchURL = (url: string, fetch?: Fetch) => void; type StopWatchURL = (url: string) => void; diff --git a/frontend/public/components/default-resource.jsx b/frontend/public/components/default-resource.jsx index 1a9632ccd61..9acc5f06ff9 100644 --- a/frontend/public/components/default-resource.jsx +++ b/frontend/public/components/default-resource.jsx @@ -28,75 +28,107 @@ const tableColumnClasses = [ const TableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: '', props: { className: tableColumnClasses[3] }, + title: '', + props: { className: tableColumnClasses[3] }, }, ]; }; TableHeader.displayName = 'TableHeader'; -const TableRowForKind = ({obj, index, key, style, customData}) => { +const TableRowForKind = ({ obj, index, key, style, customData }) => { return ( - + - { obj.metadata.namespace - ? - : 'None' - } + {obj.metadata.namespace ? ( + + ) : ( + 'None' + )} - { fromNow(obj.metadata.creationTimestamp) } + {fromNow(obj.metadata.creationTimestamp)} - + ); }; TableRowForKind.displayName = 'TableRowForKind'; -const DetailsForKind = kind => function DetailsForKind_({obj}) { - return -

    - - -
    - ; -}; +const DetailsForKind = (kind) => + function DetailsForKind_({ obj }) { + return ( + +
    + + +
    +
    + ); + }; -export const DefaultList = props => { +export const DefaultList = (props) => { const { kinds } = props; - return
    ; + return ( +
    + ); }; DefaultList.displayName = DefaultList; -export const DefaultPage = props => - ; +export const DefaultPage = (props) => ( + +); DefaultPage.displayName = 'DefaultPage'; - -export const DefaultDetailsPage = props => { +export const DefaultDetailsPage = (props) => { const pages = [navFactory.details(DetailsForKind(props.kind)), navFactory.editYaml()]; return ; }; diff --git a/frontend/public/components/deployment-config.tsx b/frontend/public/components/deployment-config.tsx index c43d7fc309b..02522652e84 100644 --- a/frontend/public/components/deployment-config.tsx +++ b/frontend/public/components/deployment-config.tsx @@ -8,11 +8,7 @@ import { DeploymentConfigModel } from '../models'; import { Conditions } from './conditions'; import { ResourceEventStream } from './events'; import { VolumesTable } from './volumes-table'; -import { - DetailsPage, - ListPage, - Table, -} from './factory'; +import { DetailsPage, ListPage, Table } from './factory'; import { AsyncComponent, history, @@ -26,13 +22,11 @@ import { SectionHeading, togglePaused, WorkloadPausedAlert, - LoadingInline, getExtensionsKebabActionsForKind, + LoadingInline, + getExtensionsKebabActionsForKind, } from './utils'; -import { - WorkloadTableRow, - WorkloadTableHeader, -} from './workload-table'; +import { WorkloadTableRow, WorkloadTableHeader } from './workload-table'; const DeploymentConfigsReference: K8sResourceKindReference = 'DeploymentConfig'; @@ -58,12 +52,21 @@ const determineReplicationControllerName = (dc: K8sResourceKind): string => { const RolloutAction: KebabAction = (kind: K8sKind, obj: K8sResourceKind) => ({ label: 'Start Rollout', - callback: () => rollout(obj).then(deployment => { - history.push(resourcePath('ReplicationController', determineReplicationControllerName(deployment), deployment.metadata.namespace)); - }).catch(err => { - const error = err.message; - errorModal({error}); - }), + callback: () => + rollout(obj) + .then((deployment) => { + history.push( + resourcePath( + 'ReplicationController', + determineReplicationControllerName(deployment), + deployment.metadata.namespace, + ), + ); + }) + .catch((err) => { + const error = err.message; + errorModal({ error }); + }), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -76,7 +79,7 @@ const RolloutAction: KebabAction = (kind: K8sKind, obj: K8sResourceKind) => ({ const PauseAction: KebabAction = (kind: K8sKind, obj: K8sResourceKind) => ({ label: obj.spec.paused ? 'Resume Rollouts' : 'Pause Rollouts', - callback: () => togglePaused(kind, obj).catch((err) => errorModal({error: err.message})), + callback: () => togglePaused(kind, obj).catch((err) => errorModal({ error: err.message })), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -86,7 +89,7 @@ const PauseAction: KebabAction = (kind: K8sKind, obj: K8sResourceKind) => ({ }, }); -const {ModifyCount, AddStorage, common} = Kebab.factory; +const { ModifyCount, AddStorage, common } = Kebab.factory; export const menuActions: KebabAction[] = [ RolloutAction, @@ -97,93 +100,125 @@ export const menuActions: KebabAction[] = [ ...common, ]; -export const DeploymentConfigDetailsList = ({dc}) => { +export const DeploymentConfigDetailsList = ({ dc }) => { const reason = _.get(dc, 'status.details.message'); const timeout = _.get(dc, 'spec.strategy.rollingParams.timeoutSeconds'); const updatePeriod = _.get(dc, 'spec.strategy.rollingParams.updatePeriodSeconds'); const interval = _.get(dc, 'spec.strategy.rollingParams.intervalSeconds'); const isRecreate = 'Recreate' === _.get(dc, 'spec.strategy.type'); const triggers = _.map(dc.spec.triggers, 'type').join(', '); - return
    -
    Latest Version
    -
    {_.get(dc, 'status.latestVersion', '-')}
    - {reason &&
    Reason
    } - {reason &&
    {reason}
    } -
    Update Strategy
    -
    {_.get(dc, 'spec.strategy.type', 'Rolling')}
    - {timeout &&
    Timeout
    } - {timeout &&
    {pluralize(timeout, 'second')}
    } - {updatePeriod &&
    Update Period
    } - {updatePeriod &&
    {pluralize(updatePeriod, 'second')}
    } - {interval &&
    Interval
    } - {interval &&
    {pluralize(interval, 'second')}
    } - {isRecreate ||
    Max Unavailable
    } - {isRecreate ||
    {_.get(dc, 'spec.strategy.rollingParams.maxUnavailable', 1)} of {pluralize(dc.spec.replicas, 'pod')}
    } - {isRecreate ||
    Max Surge
    } - {isRecreate ||
    {_.get(dc, 'spec.strategy.rollingParams.maxSurge', 1)} greater than {pluralize(dc.spec.replicas, 'pod')}
    } -
    Min Ready Seconds
    -
    {dc.spec.minReadySeconds ? pluralize(dc.spec.minReadySeconds, 'second') : 'Not Configured'}
    - {triggers &&
    Triggers
    } - {triggers &&
    {triggers}
    } -
    ; + return ( +
    +
    Latest Version
    +
    {_.get(dc, 'status.latestVersion', '-')}
    + {reason &&
    Reason
    } + {reason &&
    {reason}
    } +
    Update Strategy
    +
    {_.get(dc, 'spec.strategy.type', 'Rolling')}
    + {timeout &&
    Timeout
    } + {timeout &&
    {pluralize(timeout, 'second')}
    } + {updatePeriod &&
    Update Period
    } + {updatePeriod &&
    {pluralize(updatePeriod, 'second')}
    } + {interval &&
    Interval
    } + {interval &&
    {pluralize(interval, 'second')}
    } + {isRecreate ||
    Max Unavailable
    } + {isRecreate || ( +
    + {_.get(dc, 'spec.strategy.rollingParams.maxUnavailable', 1)} of{' '} + {pluralize(dc.spec.replicas, 'pod')} +
    + )} + {isRecreate ||
    Max Surge
    } + {isRecreate || ( +
    + {_.get(dc, 'spec.strategy.rollingParams.maxSurge', 1)} greater than{' '} + {pluralize(dc.spec.replicas, 'pod')} +
    + )} +
    Min Ready Seconds
    +
    + {dc.spec.minReadySeconds ? pluralize(dc.spec.minReadySeconds, 'second') : 'Not Configured'} +
    + {triggers &&
    Triggers
    } + {triggers &&
    {triggers}
    } +
    + ); }; -export const DeploymentConfigsDetails: React.FC<{obj: K8sResourceKind}> = ({obj: dc}) => { - return -
    - - {dc.spec.paused && } - { - return d.loaded ? : ; - }} - /> -
    -
    -
    - -
    Status
    -
    - {dc.status.availableReplicas === dc.status.updatedReplicas && dc.spec.replicas === dc.status.availableReplicas - ? - : } -
    -
    -
    -
    - +export const DeploymentConfigsDetails: React.FC<{ obj: K8sResourceKind }> = ({ obj: dc }) => { + return ( + +
    + + {dc.spec.paused && } + { + return d.loaded ? ( + + ) : ( + + ); + }} + /> +
    +
    +
    + +
    Status
    +
    + {dc.status.availableReplicas === dc.status.updatedReplicas && + dc.spec.replicas === dc.status.availableReplicas ? ( + + ) : ( + + )} +
    +
    +
    +
    + +
    -
    -
    - - -
    -
    - -
    -
    - - -
    - ; +
    + + +
    +
    + +
    +
    + + +
    + + ); }; -const EnvironmentPage = (props) => import('./environment.jsx').then(c => c.EnvironmentPage)} {...props} />; +const EnvironmentPage = (props) => ( + import('./environment.jsx').then((c) => c.EnvironmentPage)} + {...props} + /> +); -const envPath = ['spec','template','spec','containers']; -const environmentComponent = (props) => ; +const envPath = ['spec', 'template', 'spec', 'containers']; +const environmentComponent = (props) => ( + +); const pages = [ navFactory.details(DeploymentConfigsDetails), @@ -193,16 +228,37 @@ const pages = [ navFactory.events(ResourceEventStream), ]; -export const DeploymentConfigsDetailsPage: React.FC = props => { - return ; +export const DeploymentConfigsDetailsPage: React.FC = ( + props, +) => { + return ( + + ); }; DeploymentConfigsDetailsPage.displayName = 'DeploymentConfigsDetailsPage'; const kind = 'DeploymentConfig'; -const DeploymentConfigTableRow: React.FC = ({obj, index, key, style}) => { +const DeploymentConfigTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + ); }; DeploymentConfigTableRow.displayName = 'DeploymentTableRow'; @@ -218,10 +274,25 @@ const DeploymentConfigTableHeader = () => { }; DeploymentConfigTableHeader.displayName = 'DeploymentConfigTableHeader'; -export const DeploymentConfigsList: React.FC = props =>
    ; +export const DeploymentConfigsList: React.FC = (props) => ( +
    +); DeploymentConfigsList.displayName = 'DeploymentConfigsList'; -export const DeploymentConfigsPage: React.FC = props => ; +export const DeploymentConfigsPage: React.FC = (props) => ( + +); DeploymentConfigsPage.displayName = 'DeploymentConfigsListPage'; type DeploymentConfigsPageProps = { diff --git a/frontend/public/components/deployment.tsx b/frontend/public/components/deployment.tsx index aafa89b16a2..1407307fce2 100644 --- a/frontend/public/components/deployment.tsx +++ b/frontend/public/components/deployment.tsx @@ -9,11 +9,7 @@ import { Conditions } from './conditions'; import { ResourceEventStream } from './events'; import { formatDuration } from './utils/datetime'; import { VolumesTable } from './volumes-table'; -import { - DetailsPage, - ListPage, - Table, -} from './factory'; +import { DetailsPage, ListPage, Table } from './factory'; import { AsyncComponent, Kebab, @@ -28,17 +24,14 @@ import { LoadingInline, } from './utils'; -import { - WorkloadTableRow, - WorkloadTableHeader, -} from './workload-table'; +import { WorkloadTableRow, WorkloadTableHeader } from './workload-table'; const deploymentsReference: K8sResourceKindReference = 'Deployment'; const { ModifyCount, AddStorage, common } = Kebab.factory; const UpdateStrategy: KebabAction = (kind: K8sKind, deployment: K8sResourceKind) => ({ label: 'Edit Update Strategy', - callback: () => configureUpdateStrategyModal({deployment}), + callback: () => configureUpdateStrategyModal({ deployment }), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -50,7 +43,7 @@ const UpdateStrategy: KebabAction = (kind: K8sKind, deployment: K8sResourceKind) const PauseAction: KebabAction = (kind: K8sKind, obj: K8sResourceKind) => ({ label: obj.spec.paused ? 'Resume Rollouts' : 'Pause Rollouts', - callback: () => togglePaused(kind, obj).catch((err) => errorModal({error: err.message})), + callback: () => togglePaused(kind, obj).catch((err) => errorModal({ error: err.message })), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -69,89 +62,140 @@ export const menuActions = [ ...common, ]; -export const DeploymentDetailsList: React.FC = ({deployment}) => { - const isRecreate = (deployment.spec.strategy.type === 'Recreate'); +export const DeploymentDetailsList: React.FC = ({ deployment }) => { + const isRecreate = deployment.spec.strategy.type === 'Recreate'; const progressDeadlineSeconds = _.get(deployment, 'spec.progressDeadlineSeconds'); - return
    -
    Update Strategy
    -
    {deployment.spec.strategy.type || 'RollingUpdate'}
    - {isRecreate ||
    Max Unavailable
    } - {isRecreate ||
    {deployment.spec.strategy.rollingUpdate.maxUnavailable || 1} of {pluralize(deployment.spec.replicas, 'pod')}
    } - {isRecreate ||
    Max Surge
    } - {isRecreate ||
    {deployment.spec.strategy.rollingUpdate.maxSurge || 1} greater than {pluralize(deployment.spec.replicas, 'pod')}
    } - {progressDeadlineSeconds &&
    Progress Deadline
    } - {progressDeadlineSeconds &&
    {/* Convert to ms for formatDuration */ formatDuration(progressDeadlineSeconds * 1000)}
    } -
    Min Ready Seconds
    -
    {deployment.spec.minReadySeconds ? pluralize(deployment.spec.minReadySeconds, 'second') : 'Not Configured'}
    -
    ; + return ( +
    +
    Update Strategy
    +
    {deployment.spec.strategy.type || 'RollingUpdate'}
    + {isRecreate ||
    Max Unavailable
    } + {isRecreate || ( +
    + {deployment.spec.strategy.rollingUpdate.maxUnavailable || 1} of{' '} + {pluralize(deployment.spec.replicas, 'pod')} +
    + )} + {isRecreate ||
    Max Surge
    } + {isRecreate || ( +
    + {deployment.spec.strategy.rollingUpdate.maxSurge || 1} greater than{' '} + {pluralize(deployment.spec.replicas, 'pod')} +
    + )} + {progressDeadlineSeconds &&
    Progress Deadline
    } + {progressDeadlineSeconds && ( +
    + {/* Convert to ms for formatDuration */ formatDuration(progressDeadlineSeconds * 1000)} +
    + )} +
    Min Ready Seconds
    +
    + {deployment.spec.minReadySeconds + ? pluralize(deployment.spec.minReadySeconds, 'second') + : 'Not Configured'} +
    +
    + ); }; DeploymentDetailsList.displayName = 'DeploymentDetailsList'; -const DeploymentDetails: React.FC = ({obj: deployment}) => { - return -
    - - {deployment.spec.paused && } - { - return d.loaded ? : ; - }} - /> -
    -
    -
    - -
    Status
    -
    - {deployment.status.availableReplicas === deployment.status.updatedReplicas && deployment.spec.replicas === deployment.status.availableReplicas - ? - : } -
    -
    -
    -
    - +const DeploymentDetails: React.FC = ({ obj: deployment }) => { + return ( + +
    + + {deployment.spec.paused && } + { + return d.loaded ? ( + + ) : ( + + ); + }} + /> +
    +
    +
    + +
    Status
    +
    + {deployment.status.availableReplicas === deployment.status.updatedReplicas && + deployment.spec.replicas === deployment.status.availableReplicas ? ( + + ) : ( + + )} +
    +
    +
    +
    + +
    -
    -
    - - -
    -
    - -
    -
    - - -
    - ; +
    + + +
    +
    + +
    +
    + + +
    + + ); }; DeploymentDetails.displayName = 'DeploymentDetails'; -const EnvironmentPage = (props) => import('./environment.jsx').then(c => c.EnvironmentPage)} {...props} />; - -const envPath = ['spec','template','spec','containers']; -const environmentComponent = (props) => ; - -const {details, editYaml, pods, envEditor, events} = navFactory; -export const DeploymentsDetailsPage: React.FC = props => ; +const EnvironmentPage = (props) => ( + import('./environment.jsx').then((c) => c.EnvironmentPage)} + {...props} + /> +); + +const envPath = ['spec', 'template', 'spec', 'containers']; +const environmentComponent = (props) => ( + +); + +const { details, editYaml, pods, envEditor, events } = navFactory; +export const DeploymentsDetailsPage: React.FC = (props) => ( + +); DeploymentsDetailsPage.displayName = 'DeploymentsDetailsPage'; type DeploymentDetailsListProps = { @@ -164,9 +208,16 @@ type DeploymentDetailsProps = { const kind = 'Deployment'; -const DeploymentTableRow: React.FC = ({obj, index, key, style}) => { +const DeploymentTableRow: React.FC = ({ obj, index, key, style }) => { return ( - + ); }; DeploymentTableRow.displayName = 'DeploymentTableRow'; @@ -175,17 +226,32 @@ type DeploymentTableRowProps = { index: number; key: string; style: object; -} +}; const DeploymentTableHeader = () => { return WorkloadTableHeader(); }; DeploymentTableHeader.displayName = 'DeploymentTableHeader'; -export const DeploymentsList: React.FC = props =>
    ; +export const DeploymentsList: React.FC = (props) => ( +
    +); DeploymentsList.displayName = 'DeploymentsList'; -export const DeploymentsPage: React.FC = props => ; +export const DeploymentsPage: React.FC = (props) => ( + +); DeploymentsPage.displayName = 'DeploymentsPage'; type DeploymentsPageProps = { diff --git a/frontend/public/components/droppable-edit-yaml.tsx b/frontend/public/components/droppable-edit-yaml.tsx index b33d6bcde2c..b33a1196f20 100644 --- a/frontend/public/components/droppable-edit-yaml.tsx +++ b/frontend/public/components/droppable-edit-yaml.tsx @@ -24,77 +24,81 @@ const EditYAMLComponent = DropTarget(NativeTypes.FILE, boxTarget, (connectObj, m canDrop: monitor.canDrop(), }))(EditYAML); - -export const DroppableEditYAML = withDragDropContext(class DroppableEditYAML extends React.Component { - constructor(props) { - super(props); - this.state = { - fileUpload: '', - error: '', - }; - this.handleFileDrop = this.handleFileDrop.bind(this); - } - - containsNonPrintableCharacters(value: string) { - if (!value) { - return false; +export const DroppableEditYAML = withDragDropContext( + class DroppableEditYAML extends React.Component { + constructor(props) { + super(props); + this.state = { + fileUpload: '', + error: '', + }; + this.handleFileDrop = this.handleFileDrop.bind(this); } - // eslint-disable-next-line no-control-regex - return /[\x00-\x09\x0E-\x1F]/.test(value); - } - handleFileDrop(item, monitor) { - if (!monitor) { - return; + containsNonPrintableCharacters(value: string) { + if (!value) { + return false; + } + // eslint-disable-next-line no-control-regex + return /[\x00-\x09\x0E-\x1F]/.test(value); } - const [file] = monitor.getItem().files; - // If unsupported file type is dropped into drop zone, file will be undefined - if (!file) { - return; - } + handleFileDrop(item, monitor) { + if (!monitor) { + return; + } + const [file] = monitor.getItem().files; - // limit size size uploading to 1 mb - if (file.size <= maxFileUploadSize) { - const reader = new FileReader(); - reader.onload = () => { - const input = reader.result as string; - if (this.containsNonPrintableCharacters(input)) { - this.setState({ - error: fileTypeErrorMsg, - }); - } else { - this.setState({ - fileUpload: input, - error: '', - }); - } - }; - reader.readAsText(file, 'UTF-8'); - } else { - this.setState({ - error: fileSizeErrorMsg, - }); + // If unsupported file type is dropped into drop zone, file will be undefined + if (!file) { + return; + } + + // limit size size uploading to 1 mb + if (file.size <= maxFileUploadSize) { + const reader = new FileReader(); + reader.onload = () => { + const input = reader.result as string; + if (this.containsNonPrintableCharacters(input)) { + this.setState({ + error: fileTypeErrorMsg, + }); + } else { + this.setState({ + fileUpload: input, + error: '', + }); + } + }; + reader.readAsText(file, 'UTF-8'); + } else { + this.setState({ + error: fileSizeErrorMsg, + }); + } } - } - render() { - const { obj } = this.props; - const { fileUpload, error } = this.state; - return ; - } -}); + render() { + const { obj } = this.props; + const { fileUpload, error } = this.state; + return ( + + ); + } + }, +); export type DroppableEditYAMLProps = { - obj: string, + obj: string; }; export type DroppableEditYAMLState = { - fileUpload: string | ArrayBuffer, - error: string, + fileUpload: string | ArrayBuffer; + error: string; }; diff --git a/frontend/public/components/edit-yaml.jsx b/frontend/public/components/edit-yaml.jsx index fb5448b8ee3..e6918068abe 100644 --- a/frontend/public/components/edit-yaml.jsx +++ b/frontend/public/components/edit-yaml.jsx @@ -14,13 +14,22 @@ import { } from '@patternfly/react-tokens'; import { ALL_NAMESPACES_KEY } from '../const'; -import { k8sCreate, k8sUpdate, referenceFor, groupVersionFor, referenceForModel } from '../module/k8s'; +import { + k8sCreate, + k8sUpdate, + referenceFor, + groupVersionFor, + referenceForModel, +} from '../module/k8s'; import { checkAccess, history, Loading, resourceObjPath } from './utils'; import { ResourceSidebar } from './sidebars/resource-sidebar'; import { yamlTemplates } from '../models/yaml-templates'; import { getStoredSwagger } from '../module/k8s/swagger'; -import { MonacoToProtocolConverter, ProtocolToMonacoConverter } from 'monaco-languageclient/lib/monaco-converter'; +import { + MonacoToProtocolConverter, + ProtocolToMonacoConverter, +} from 'monaco-languageclient/lib/monaco-converter'; import { getLanguageService, TextDocument } from 'yaml-language-server'; import { openAPItoJSONSchema } from '../module/k8s/openapi-to-json-schema'; import * as URL from 'url'; @@ -51,7 +60,7 @@ const generateObjToLoad = (kind, templateName, namespace = 'default') => { return sampleObj; }; -const stateToProps = ({k8s, UI}) => ({ +const stateToProps = ({ k8s, UI }) => ({ activeNamespace: UI.get('activeNamespace'), impersonate: UI.get('impersonate'), models: k8s.getIn(['RESOURCES', 'models']), @@ -80,9 +89,9 @@ export const EditYAML = connect(stateToProps)( }; this.monacoRef = React.createRef(); this.resize = () => { - this.setState({height: this.height}); + this.setState({ height: this.height }); if (this.monacoRef.current) { - this.monacoRef.current.editor.layout({height: this.editorHeight, width: this.width}); + this.monacoRef.current.editor.layout({ height: this.editorHeight, width: this.width }); } }; // k8s uses strings for resource versions @@ -107,7 +116,7 @@ export const EditYAML = connect(stateToProps)( } handleError(error) { - this.setState({error, success: null}, () => { + this.setState({ error, success: null }, () => { this.resize(); }); } @@ -137,17 +146,20 @@ export const EditYAML = connect(stateToProps)( } const newVersion = _.get(nextProps.obj, 'metadata.resourceVersion'); const stale = this.displayedVersion !== newVersion; - this.setState({stale}); + this.setState({ stale }); if (nextProps.error) { this.handleError(nextProps.error); } else if (this.state.error) { //clear stale error state - this.setState({error: ''}); + this.setState({ error: '' }); } if (nextProps.sampleObj) { this.loadYaml(!_.isEqual(this.state.sampleObj, nextProps.sampleObj), nextProps.sampleObj); } else if (nextProps.fileUpload) { - this.loadYaml(!_.isEqual(this.state.fileUpload, nextProps.fileUpload), nextProps.fileUpload); + this.loadYaml( + !_.isEqual(this.state.fileUpload, nextProps.fileUpload), + nextProps.fileUpload, + ); } else { this.loadYaml(); } @@ -157,7 +169,7 @@ export const EditYAML = connect(stateToProps)( editor.layout(); editor.focus(); this.registerYAMLinMonaco(monaco); - monaco.editor.getModels()[0].updateOptions({ tabSize: 2}); + monaco.editor.getModels()[0].updateOptions({ tabSize: 2 }); } get editorHeight() { @@ -171,16 +183,21 @@ export const EditYAML = connect(stateToProps)( get height() { return Math.floor( // notifications can appear above and below .pf-c-page, so calculate height using the bottom of .pf-c-page - document.getElementsByClassName('pf-c-page')[0].getBoundingClientRect().bottom - this.editor.getBoundingClientRect().top + document.getElementsByClassName('pf-c-page')[0].getBoundingClientRect().bottom - + this.editor.getBoundingClientRect().top, ); } get width() { - const hasSidebarSidebar = _.first(document.getElementsByClassName('co-p-has-sidebar__sidebar')); - const contentScrollableWidth = document.getElementById('content-scrollable').getBoundingClientRect().width; + const hasSidebarSidebar = _.first( + document.getElementsByClassName('co-p-has-sidebar__sidebar'), + ); + const contentScrollableWidth = document + .getElementById('content-scrollable') + .getBoundingClientRect().width; // if viewport width is > 767, also subtract left and right margins on .yaml-editor const hasSidebarBodyWidth = isDesktop() - ? contentScrollableWidth - (desktopGutter * 2) + ? contentScrollableWidth - desktopGutter * 2 : contentScrollableWidth; return hasSidebarSidebar ? hasSidebarBodyWidth - hasSidebarSidebar.getBoundingClientRect().width @@ -216,8 +233,8 @@ export const EditYAML = connect(stateToProps)( name, namespace, }; - checkAccess(resourceAttributes, impersonate).then(resp => { - this.setState({notAllowed: !resp.status.allowed}); + checkAccess(resourceAttributes, impersonate).then((resp) => { + this.setState({ notAllowed: !resp.status.allowed }); }); } @@ -241,7 +258,7 @@ export const EditYAML = connect(stateToProps)( } this.displayedVersion = _.get(obj, 'metadata.resourceVersion'); - this.setState({yaml, initialized: true, stale: false}); + this.setState({ yaml, initialized: true, stale: false }); this.resize(); } @@ -270,7 +287,11 @@ export const EditYAML = connect(stateToProps)( const model = this.getModel(obj); if (!model) { - this.handleError(`The server doesn't have a resource type "kind: ${obj.kind}, apiVersion: ${obj.apiVersion}".`); + this.handleError( + `The server doesn't have a resource type "kind: ${obj.kind}, apiVersion: ${ + obj.apiVersion + }".`, + ); return; } @@ -289,27 +310,37 @@ export const EditYAML = connect(stateToProps)( const { namespace, name } = this.props.obj.metadata; if (name !== newName) { - this.handleError(`Cannot change resource name (original: "${name}", updated: "${newName}").`); + this.handleError( + `Cannot change resource name (original: "${name}", updated: "${newName}").`, + ); return; } if (namespace !== newNamespace) { - this.handleError(`Cannot change resource namespace (original: "${namespace}", updated: "${newNamespace}").`); + this.handleError( + `Cannot change resource namespace (original: "${namespace}", updated: "${newNamespace}").`, + ); return; } if (this.props.obj.kind !== obj.kind) { - this.handleError(`Cannot change resource kind (original: "${this.props.obj.kind}", updated: "${obj.kind}").`); + this.handleError( + `Cannot change resource kind (original: "${this.props.obj.kind}", updated: "${ + obj.kind + }").`, + ); return; } const apiGroup = groupVersionFor(this.props.obj.apiVersion).group; const newAPIGroup = groupVersionFor(obj.apiVersion).group; if (apiGroup !== newAPIGroup) { - this.handleError(`Cannot change API group (original: "${apiGroup}", updated: "${newAPIGroup}").`); + this.handleError( + `Cannot change API group (original: "${apiGroup}", updated: "${newAPIGroup}").`, + ); return; } } - this.setState({success: null, error: null}, () => { + this.setState({ success: null, error: null }, () => { let action = k8sUpdate; let redirect = false; if (this.props.create) { @@ -318,11 +349,13 @@ export const EditYAML = connect(stateToProps)( redirect = true; } action(model, obj, newNamespace, newName) - .then(o => { + .then((o) => { if (redirect) { let url = this.props.redirectURL; if (!url) { - const path = _.isFunction(this.props.resourceObjPath) ? this.props.resourceObjPath : resourceObjPath; + const path = _.isFunction(this.props.resourceObjPath) + ? this.props.resourceObjPath + : resourceObjPath; url = path(o, referenceFor(o)); } history.push(url); @@ -330,11 +363,11 @@ export const EditYAML = connect(stateToProps)( return; } const success = `${newName} has been updated to version ${o.metadata.resourceVersion}`; - this.setState({success, error: null}); + this.setState({ success, error: null }); this.loadYaml(true, o); this.resize(); }) - .catch(e => this.handleError(e.message)); + .catch((e) => this.handleError(e.message)); }); } @@ -354,12 +387,14 @@ export const EditYAML = connect(stateToProps)( loadSampleYaml_(templateName = 'default', kind = referenceForModel(this.props.model)) { const sampleObj = generateObjToLoad(kind, templateName, this.props.obj.metadata.namespace); - this.setState({sampleObj}); + this.setState({ sampleObj }); this.loadYaml(true, sampleObj); } downloadSampleYaml_(templateName = 'default', kind = referenceForModel(this.props.model)) { - const data = safeDump(generateObjToLoad(kind, templateName, this.props.obj.metadata.namespace)); + const data = safeDump( + generateObjToLoad(kind, templateName, this.props.obj.metadata.namespace), + ); this.download(data); } @@ -372,7 +407,12 @@ export const EditYAML = connect(stateToProps)( const p2m = new ProtocolToMonacoConverter(); function createDocument(model) { - return TextDocument.create(MODEL_URI, model.getModeId(), model.getVersionId(), model.getValue()); + return TextDocument.create( + MODEL_URI, + model.getModeId(), + model.getVersionId(), + model.getValue(), + ); } const yamlService = this.createYAMLService(); @@ -392,7 +432,7 @@ export const EditYAML = connect(stateToProps)( * We check that > 1 YAML language exists because one is the default and one is the initial register * that setups our features. */ - if (monaco.languages.getLanguages().filter(x => x.id === LANGUAGE_ID).length > 1) { + if (monaco.languages.getLanguages().filter((x) => x.id === LANGUAGE_ID).length > 1) { return; } @@ -436,11 +476,13 @@ export const EditYAML = connect(stateToProps)( // Convert the openAPI schema to something the language server understands const kubernetesJSONSchema = openAPItoJSONSchema(yamlOpenAPI); - const schemas = [{ - uri: 'inmemory:yaml', - fileMatch: ['*'], - schema: kubernetesJSONSchema, - }]; + const schemas = [ + { + uri: 'inmemory:yaml', + fileMatch: ['*'], + schema: kubernetesJSONSchema, + }, + ]; yamlService.configure({ validate: true, schemas, @@ -454,13 +496,17 @@ export const EditYAML = connect(stateToProps)( monaco.languages.registerCompletionItemProvider(languageID, { provideCompletionItems(model, position) { const document = createDocument(model); - return yamlService.doComplete(document, m2p.asPosition(position.lineNumber, position.column), true).then((list) => { - return p2m.asCompletionResult(list); - }); + return yamlService + .doComplete(document, m2p.asPosition(position.lineNumber, position.column), true) + .then((list) => { + return p2m.asCompletionResult(list); + }); }, resolveCompletionItem(item) { - return yamlService.doResolve(m2p.asCompletionItem(item)).then(result => p2m.asCompletionItem(result)); + return yamlService + .doResolve(m2p.asCompletionItem(item)) + .then((result) => p2m.asCompletionItem(result)); }, }); } @@ -478,18 +524,21 @@ export const EditYAML = connect(stateToProps)( monaco.languages.registerHoverProvider(languageID, { provideHover(model, position) { const doc = createDocument(model); - return yamlService.doHover(doc, m2p.asPosition(position.lineNumber, position.column)).then((hover) => { - return p2m.asHover(hover); - }).then(e => { - for (const el of document.getElementsByClassName('monaco-editor-hover')) { - el.onclick = (event) => event.preventDefault(); - el.onauxclick = (event) => { - window.open(event.target.getAttribute('data-href'), '_blank').opener = null; - event.preventDefault(); - }; - } - return e; - }); + return yamlService + .doHover(doc, m2p.asPosition(position.lineNumber, position.column)) + .then((hover) => { + return p2m.asHover(hover); + }) + .then((e) => { + for (const el of document.getElementsByClassName('monaco-editor-hover')) { + el.onclick = (event) => event.preventDefault(); + el.onauxclick = (event) => { + window.open(event.target.getAttribute('data-href'), '_blank').opener = null; + event.preventDefault(); + }; + } + return e; + }); }, }); } @@ -507,7 +556,8 @@ export const EditYAML = connect(stateToProps)( } }; - const cleanDiagnostics = () => monaco.editor.setModelMarkers(monaco.editor.getModel(monacoURI), 'default', []); + const cleanDiagnostics = () => + monaco.editor.setModelMarkers(monaco.editor.getModel(monacoURI), 'default', []); const doValidate = (document) => { if (document.getText().length === 0) { @@ -523,10 +573,13 @@ export const EditYAML = connect(stateToProps)( getModel().onDidChangeContent(() => { const document = createDocument(getModel()); cleanPendingValidation(document); - pendingValidationRequests.set(document.uri, setTimeout(() => { - pendingValidationRequests.delete(document.uri); - doValidate(document); - })); + pendingValidationRequests.set( + document.uri, + setTimeout(() => { + pendingValidationRequests.delete(document.uri); + doValidate(document); + }), + ); }); } @@ -536,55 +589,134 @@ export const EditYAML = connect(stateToProps)( } const { connectDropTarget, isOver, canDrop } = this.props; - const klass = classNames('co-file-dropzone-container', {'co-file-dropzone--drop-over': isOver}); + const klass = classNames('co-file-dropzone-container', { + 'co-file-dropzone--drop-over': isOver, + }); - const {error, success, stale, yaml, height} = this.state; - const {create, obj, download = true, header} = this.props; + const { error, success, stale, yaml, height } = this.state; + const { create, obj, download = true, header } = this.props; const readOnly = this.props.readOnly || this.state.notAllowed; const options = { readOnly, scrollBeyondLastLine: false }; const model = this.getModel(obj); - const editYamlComponent =
    - { canDrop &&

    Drop file here

    } - -
    - {create && !this.props.hideHeader &&
    -

    {header}

    -

    Create by manually entering YAML or JSON definitions, or by dragging and dropping a file into the editor.

    -
    } -
    -
    -
    -
    this.editor = r}> - this.setState({yaml: newValue})} - /> -
    this.buttons = r}> - {error &&
    {error}
    } - {success && } - {stale && Click reload to see the new version.} - - {create && } - {!create && !readOnly && } - {!create && } - - {download && } - + const editYamlComponent = ( +
    + {canDrop && ( +
    +

    Drop file here

    +
    + )} + +
    + {create && !this.props.hideHeader && ( +
    +

    {header}

    +

    + Create by manually entering YAML or JSON definitions, or by dragging and dropping + a file into the editor. +

    +
    + )} +
    +
    +
    +
    (this.editor = r)}> + this.setState({ yaml: newValue })} + /> +
    (this.buttons = r)}> + {error && ( + +
    {error}
    +
    + )} + {success && ( + + )} + {stale && ( + + Click reload to see the new version. + + )} + + {create && ( + + )} + {!create && !readOnly && ( + + )} + {!create && ( + + )} + + {download && ( + + )} + +
    +
    -
    -
    ; + ); - return _.isFunction(connectDropTarget) ? connectDropTarget(editYamlComponent) : editYamlComponent; + return _.isFunction(connectDropTarget) + ? connectDropTarget(editYamlComponent) + : editYamlComponent; } - } + }, ); diff --git a/frontend/public/components/environment.jsx b/frontend/public/components/environment.jsx index d3466d133ae..5d74043e77a 100644 --- a/frontend/public/components/environment.jsx +++ b/frontend/public/components/environment.jsx @@ -25,8 +25,18 @@ import { ConfigMapModel, SecretModel } from '../models'; * Set up an AsyncComponent to wrap the name-value-editor to allow on demand loading to reduce the * vendor footprint size. */ -const NameValueEditorComponent = (props) => import('./utils/name-value-editor').then(c => c.NameValueEditor)} {...props} />; -const EnvFromEditorComponent = (props) => import('./utils/name-value-editor').then(c => c.EnvFromEditor)} {...props} />; +const NameValueEditorComponent = (props) => ( + import('./utils/name-value-editor').then((c) => c.NameValueEditor)} + {...props} + /> +); +const EnvFromEditorComponent = (props) => ( + import('./utils/name-value-editor').then((c) => c.EnvFromEditor)} + {...props} + /> +); /** * Set up initial value for the environment vars state. Use this in constructor or cancelChanges. @@ -44,7 +54,7 @@ const EnvFromEditorComponent = (props) => import(' */ const getPairsFromObject = (element = {}) => { const returnedPairs = {}; - if ( _.isEmpty(element.env)) { + if (_.isEmpty(element.env)) { returnedPairs.env = [['', '', 0]]; } else { returnedPairs.env = _.map(element.env, (leafNode, i) => { @@ -56,8 +66,8 @@ const getPairsFromObject = (element = {}) => { }); } if (_.isEmpty(element.envFrom)) { - const configMapSecretRef = {name: '', key: ''}; - returnedPairs.envFrom = [['', {configMapSecretRef}, 0]]; + const configMapSecretRef = { name: '', key: '' }; + returnedPairs.envFrom = [['', { configMapSecretRef }, 0]]; } else { returnedPairs.envFrom = _.map(element.envFrom, (leafNode, i) => { if (!_.has(leafNode, 'prefix')) { @@ -89,10 +99,14 @@ const envVarsToArray = (initialPairObjects) => { }; const getContainersObjectForDropdown = (containerArray) => { - return _.reduce(containerArray, (result, elem, order) => { - result[elem.name] = { ...elem, order }; - return result; - }, {}); + return _.reduce( + containerArray, + (result, elem, order) => { + result[elem.name] = { ...elem, order }; + return result; + }, + {}, + ); }; class CurrentEnvVars { @@ -166,29 +180,37 @@ class CurrentEnvVars { const containerEnvPatch = this.currentEnvVars.containers.map((finalPairsForContainer, i) => { const path = `/${envPath.join('/')}/${i}/env`; const value = this._envVarsToNameVal(finalPairsForContainer[EnvType.ENV]); - return {path, op, value}; + return { path, op, value }; }); - const containerEnvFromPatch = this.currentEnvVars.containers.map((finalPairsForContainer, i) => { - const path = `/${envPath.join('/')}/${i}/envFrom`; - const value = this._envFromVarsToResourcePrefix(finalPairsForContainer[EnvType.ENV_FROM]); - return {path, op, value}; - }); + const containerEnvFromPatch = this.currentEnvVars.containers.map( + (finalPairsForContainer, i) => { + const path = `/${envPath.join('/')}/${i}/envFrom`; + const value = this._envFromVarsToResourcePrefix(finalPairsForContainer[EnvType.ENV_FROM]); + return { path, op, value }; + }, + ); let patches = _.concat(containerEnvPatch, containerEnvFromPatch); if (this.hasInitContainers) { - const envPatchForIC = this.currentEnvVars.initContainers.map((finalPairsForContainer, i) => { - const path = `/${envPathForIC.join('/')}/${i}/env`; - const value = this._envVarsToNameVal(finalPairsForContainer[EnvType.ENV]); - return {path, op, value}; - }); - - const envFromPatchForIC = this.currentEnvVars.initContainers.map((finalPairsForContainer, i) => { - const path = `/${envPathForIC.join('/')}/${i}/envFrom`; - const value = this._envFromVarsToResourcePrefix(finalPairsForContainer[EnvType.ENV_FROM]); - return {path, op, value}; - }); + const envPatchForIC = this.currentEnvVars.initContainers.map( + (finalPairsForContainer, i) => { + const path = `/${envPathForIC.join('/')}/${i}/env`; + const value = this._envVarsToNameVal(finalPairsForContainer[EnvType.ENV]); + return { path, op, value }; + }, + ); + + const envFromPatchForIC = this.currentEnvVars.initContainers.map( + (finalPairsForContainer, i) => { + const path = `/${envPathForIC.join('/')}/${i}/envFrom`; + const value = this._envFromVarsToResourcePrefix( + finalPairsForContainer[EnvType.ENV_FROM], + ); + return { path, op, value }; + }, + ); patches = _.concat(patches, envPatchForIC, envFromPatchForIC); } @@ -198,7 +220,7 @@ class CurrentEnvVars { const op = 'add'; const path = `/${envPath.join('/')}/env`; const value = this._envVarsToNameVal(finalPairsForContainer[EnvType.ENV]); - return {path, op, value}; + return { path, op, value }; }); } @@ -209,7 +231,9 @@ class CurrentEnvVars { * @public */ dispatchNewEnvironmentVariables() { - return this.isCreate ? this._envVarsToNameVal(this.currentEnvVars.containers[0][EnvType.ENV]) : null; + return this.isCreate + ? this._envVarsToNameVal(this.currentEnvVars.containers[0][EnvType.ENV]) + : null; } /** @@ -221,14 +245,13 @@ class CurrentEnvVars { * @private */ _envVarsToNameVal(finalEnvPairs) { - return _.filter(finalEnvPairs, finalEnvPair => finalEnvPair[NameValueEditorPair.Name]) - .map(finalPairForContainer => { + return _.filter(finalEnvPairs, (finalEnvPair) => finalEnvPair[NameValueEditorPair.Name]).map( + (finalPairForContainer) => { const name = finalPairForContainer[NameValueEditorPair.Name]; const value = finalPairForContainer[NameValueEditorPair.Value]; - return _.isObject(value) - ? { name, valueFrom: value } - : { name, value }; - }); + return _.isObject(value) ? { name, valueFrom: value } : { name, value }; + }, + ); } /** @@ -240,26 +263,35 @@ class CurrentEnvVars { * @private */ _envFromVarsToResourcePrefix(finalEnvPairs) { - return _.filter(finalEnvPairs, finalEnvPair => (!_.isEmpty(finalEnvPair[EnvFromPair.Resource]) && !finalEnvPair[EnvFromPair.Resource].configMapSecretRef)) - .map(finalPairForContainer => { - return _.assign({'prefix': finalPairForContainer[EnvFromPair.Prefix]}, finalPairForContainer[EnvFromPair.Resource]); - }); + return _.filter( + finalEnvPairs, + (finalEnvPair) => + !_.isEmpty(finalEnvPair[EnvFromPair.Resource]) && + !finalEnvPair[EnvFromPair.Resource].configMapSecretRef, + ).map((finalPairForContainer) => { + return _.assign( + { prefix: finalPairForContainer[EnvFromPair.Prefix] }, + finalPairForContainer[EnvFromPair.Resource], + ); + }); } } /** @type {(state: any, props: {obj?: object, rawEnvData?: any, readOnly: boolean, envPath: any, onChange?: (env: any) => void, addConfigMapSecret?: boolean, useLoadingInline?: boolean}) => {model: K8sKind}} */ -const stateToProps = ({k8s, UI}, {obj}) => ({ - model: k8s.getIn(['RESOURCES', 'models', referenceFor(obj)]) || k8s.getIn(['RESOURCES', 'models', obj.kind]), +const stateToProps = ({ k8s, UI }, { obj }) => ({ + model: + k8s.getIn(['RESOURCES', 'models', referenceFor(obj)]) || + k8s.getIn(['RESOURCES', 'models', obj.kind]), impersonate: UI.get('impersonate'), }); export const EnvironmentPage = connect(stateToProps)( class EnvironmentPage extends PromiseComponent { - /** - * Set initial state and decide which kind of env we are setting up - * - * @param props - */ + /** + * Set initial state and decide which kind of env we are setting up + * + * @param props + */ constructor(props) { super(props); @@ -272,16 +304,18 @@ export const EnvironmentPage = connect(stateToProps)( currentEnvVars, success: null, containerIndex: 0, - containerType: currentEnvVars.isContainerArray || currentEnvVars.isCreate ? 'containers' : 'buildObject', + containerType: + currentEnvVars.isContainerArray || currentEnvVars.isCreate ? 'containers' : 'buildObject', }; } componentDidMount() { this._checkEditAccess(); - const {addConfigMapSecret, readOnly} = this.props; + const { addConfigMapSecret, readOnly } = this.props; if (!addConfigMapSecret || readOnly) { - const configMaps = {}, secrets = {}; - this.setState({configMaps, secrets}); + const configMaps = {}, + secrets = {}; + this.setState({ configMaps, secrets }); return; } const envNamespace = _.get(this.props, 'obj.metadata.namespace'); @@ -290,7 +324,7 @@ export const EnvironmentPage = connect(stateToProps)( k8sGet(ConfigMapModel, null, envNamespace).catch((err) => { if (err.response.status !== 403) { const errorMessage = err.message || 'Could not load config maps.'; - this.setState({errorMessage}); + this.setState({ errorMessage }); } return { configMaps: {}, @@ -299,14 +333,13 @@ export const EnvironmentPage = connect(stateToProps)( k8sGet(SecretModel, null, envNamespace).catch((err) => { if (err.response.status !== 403) { const errorMessage = err.message || 'Could not load secrets.'; - this.setState({errorMessage}); + this.setState({ errorMessage }); } return { secrets: {}, }; }), - ]) - .then((([configMaps, secrets]) => this.setState({configMaps, secrets}))); + ]).then(([configMaps, secrets]) => this.setState({ configMaps, secrets })); } componentDidUpdate(prevProps) { @@ -315,16 +348,18 @@ export const EnvironmentPage = connect(stateToProps)( if (!_.isEqual(rawEnvData, prevProps.rawEnvData)) { this.setState({ - ...!dirty && { currentEnvVars: new CurrentEnvVars(rawEnvData) }, + ...(!dirty && { currentEnvVars: new CurrentEnvVars(rawEnvData) }), stale: dirty, }); } - if (_.get(prevProps.obj, 'metadata.uid') !== _.get(obj, 'metadata.uid') || - _.get(prevProps.model, 'apiGroup') !== _.get(model, 'apiGroup') || - _.get(prevProps.model, 'path') !== _.get(model, 'path') || - prevProps.impersonate !== impersonate || - prevProps.readOnly !== readOnly) { + if ( + _.get(prevProps.obj, 'metadata.uid') !== _.get(obj, 'metadata.uid') || + _.get(prevProps.model, 'apiGroup') !== _.get(model, 'apiGroup') || + _.get(prevProps.model, 'path') !== _.get(model, 'path') || + prevProps.impersonate !== impersonate || + prevProps.readOnly !== readOnly + ) { this._checkEditAccess(); } } @@ -350,7 +385,9 @@ export const EnvironmentPage = connect(stateToProps)( name, namespace, }; - checkAccess(resourceAttributes, impersonate).then(resp => this.setState({ allowed: resp.status.allowed })); + checkAccess(resourceAttributes, impersonate).then((resp) => + this.setState({ allowed: resp.status.allowed }), + ); } /** @@ -359,8 +396,8 @@ export const EnvironmentPage = connect(stateToProps)( * @param i */ _updateEnvVars(env, i = 0, type = EnvType.ENV) { - const {onChange} = this.props; - const {currentEnvVars, containerType} = this.state; + const { onChange } = this.props; + const { currentEnvVars, containerType } = this.state; const currentEnv = _.cloneDeep(currentEnvVars); currentEnv.setFormattedVars(containerType, i, type, env.nameValuePairs); this.setState({ @@ -376,7 +413,7 @@ export const EnvironmentPage = connect(stateToProps)( * @private */ _reload() { - const {rawEnvData} = this.props; + const { rawEnvData } = this.props; this.setState({ currentEnvVars: new CurrentEnvVars(rawEnvData), dirty: false, @@ -388,14 +425,14 @@ export const EnvironmentPage = connect(stateToProps)( _selectContainer(containerName) { const { rawEnvData } = this.props; - let containerIndex = _.findIndex(rawEnvData.containers, {name: containerName}); + let containerIndex = _.findIndex(rawEnvData.containers, { name: containerName }); if (containerIndex !== -1) { return this.setState({ containerIndex, containerType: 'containers', }); } - containerIndex = _.findIndex(rawEnvData.initContainers, {name: containerName}); + containerIndex = _.findIndex(rawEnvData.initContainers, { name: containerName }); if (containerIndex !== -1) { return this.setState({ containerIndex, @@ -414,8 +451,8 @@ export const EnvironmentPage = connect(stateToProps)( * @param e */ _saveChanges(e) { - const {envPath, obj, model} = this.props; - const {currentEnvVars} = this.state; + const { envPath, obj, model } = this.props; + const { currentEnvVars } = this.state; e.preventDefault(); @@ -434,11 +471,22 @@ export const EnvironmentPage = connect(stateToProps)( dismissSuccess = () => { this.setState({ success: null }); - } + }; render() { - const {errorMessage, success, inProgress, currentEnvVars, stale, configMaps, secrets, containerIndex, containerType, allowed} = this.state; - const {rawEnvData, obj, addConfigMapSecret, useLoadingInline} = this.props; + const { + errorMessage, + success, + inProgress, + currentEnvVars, + stale, + configMaps, + secrets, + containerIndex, + containerType, + allowed, + } = this.state; + const { rawEnvData, obj, addConfigMapSecret, useLoadingInline } = this.props; const readOnly = this.props.readOnly || !allowed; if (!configMaps || !currentEnvVars || !secrets) { @@ -449,67 +497,160 @@ export const EnvironmentPage = connect(stateToProps)( } const envVar = currentEnvVars.getEnvVarByTypeAndIndex(containerType, containerIndex); - const containerDropdown = currentEnvVars.isContainerArray ? : null; - - const owners = _.get(obj.metadata, 'ownerReferences', []) - .map((o, i) => ); - const containerVars = + const containerDropdown = currentEnvVars.isContainerArray ? ( + + ) : null; + + const owners = _.get(obj.metadata, 'ownerReferences', []).map((o, i) => ( + + )); + const containerVars = ( - { (readOnly && !_.isEmpty(owners)) && + {readOnly && !_.isEmpty(owners) && (
    - - View environment for resource {owners.length > 1 ? owners: {owners} : owners} + + View environment for resource{' '} + {owners.length > 1 ? owners: {owners} : owners}
    - } - { currentEnvVars.isContainerArray &&
    -
    {containerType === 'containers' ? 'Container:' : 'Init Container:'}
    -
    {containerDropdown}
    -
    - } -
    - { !currentEnvVars.isCreate &&

    Single values (env) - { - !readOnly && - Define environment variables as key-value pairs to store configuration settings. You can enter text or add values from a ConfigMap or Secret. Drag and drop environment variables to change the order in which they are run. A variable can reference any other variables that come before it in the list, for example FULLDOMAIN = $(SUBDOMAIN).example.com. - - } -

    - } - -
    - { currentEnvVars.isContainerArray &&
    -

    All values from existing config maps or secrets (envFrom) - { - !readOnly && - Add new values by referencing an existing config map or secret. Drag and drop environment variables within this section to change the order in which they are run.
    Note: If identical values exist in both lists, the single value in the list above will take precedence. -
    - } -

    - -
    } -
    ; - - return
    - {containerVars} - { !currentEnvVars.isCreate &&
    -
    - {errorMessage && } - {stale && Click Reload to update and lose edits, or Save Changes to overwrite.} - {success && } />} - {!readOnly && - - - } + )} + {currentEnvVars.isContainerArray && ( +
    +
    + {containerType === 'containers' ? 'Container:' : 'Init Container:'} +
    +
    {containerDropdown}
    +
    + )} +
    + {!currentEnvVars.isCreate && ( +

    + Single values (env) + {!readOnly && ( + + Define environment variables as key-value pairs to store configuration settings. + You can enter text or add values from a ConfigMap or Secret. Drag and drop + environment variables to change the order in which they are run. A variable can + reference any other variables that come before it in the list, for example{' '} + FULLDOMAIN = $(SUBDOMAIN).example.com. + + )} +

    + )} +
    -
    } -
    ; + {currentEnvVars.isContainerArray && ( +
    +

    + All values from existing config maps or secrets (envFrom) + {!readOnly && ( + + Add new values by referencing an existing config map or secret. Drag and drop + environment variables within this section to change the order in which they are + run. +
    + Note: If identical values exist in both lists, the single value + in the list above will take precedence. +
    + )} +

    + +
    + )} + + ); + + return ( +
    + {containerVars} + {!currentEnvVars.isCreate && ( +
    +
    + {errorMessage && ( + + )} + {stale && ( + + Click Reload to update and lose edits, or Save Changes to overwrite. + + )} + {success && ( + } + /> + )} + {!readOnly && ( + + + + + )} +
    +
    + )} +
    + ); } - }); + }, +); EnvironmentPage.propTypes = { obj: PropTypes.object, rawEnvData: PropTypes.oneOfType([PropTypes.object, PropTypes.array]), diff --git a/frontend/public/components/error.tsx b/frontend/public/components/error.tsx index 68c6a274512..7943512aaaf 100644 --- a/frontend/public/components/error.tsx +++ b/frontend/public/components/error.tsx @@ -2,29 +2,33 @@ import * as _ from 'lodash-es'; import * as React from 'react'; import { Helmet } from 'react-helmet'; -import {CopyToClipboard, getQueryArgument, PageHeading, ExpandCollapse} from './utils'; -import {ErrorBoundaryFallbackProps} from './utils/error-boundary'; +import { CopyToClipboard, getQueryArgument, PageHeading, ExpandCollapse } from './utils'; +import { ErrorBoundaryFallbackProps } from './utils/error-boundary'; // User messages for error_types returned in auth.go const messages = { auth: { - 'oauth_error': 'There was an error generating OAuth client from OIDC client.', - 'login_state_error': 'There was an error generating login state.', - 'cookie_error': 'There was an error setting login state cookie', - 'missing_code': 'Auth code is missing in query param.', - 'missing_state': 'There was an error parsing your state cookie', - 'invalid_code': 'There was an error logging you in. Please log out and try again.', - 'invalid_state': 'There was an error verifying your session. Please log out and try again.', - 'default': 'There was an authentication error with the system. Please try again or contact support.', - 'logout_error': 'There was an error logging you out. Please try again.', + /* eslint-disable camelcase */ + oauth_error: 'There was an error generating OAuth client from OIDC client.', + login_state_error: 'There was an error generating login state.', + cookie_error: 'There was an error setting login state cookie', + missing_code: 'Auth code is missing in query param.', + missing_state: 'There was an error parsing your state cookie', + invalid_code: 'There was an error logging you in. Please log out and try again.', + invalid_state: 'There was an error verifying your session. Please log out and try again.', + logout_error: 'There was an error logging you out. Please try again.', + /* eslint-enable camelcase */ + default: + 'There was an authentication error with the system. Please try again or contact support.', }, }; -const getMessage = (type, id) => _.get(messages, `${type}.${id}`) || _.get(messages, `${type}.default`) || ''; +const getMessage = (type, id) => + _.get(messages, `${type}.${id}`) || _.get(messages, `${type}.default`) || ''; const urlMessage = () => { const type = getQueryArgument('error_type'); const error = getQueryArgument('error'); - return (type && error) ? getMessage(type, error) : ''; + return type && error ? getMessage(type, error) : ''; }; const getErrMessage = () => { const msg = getQueryArgument('error_msg'); @@ -39,30 +43,44 @@ const getErrMessage = () => { return ''; }; -const ErrorComponent: React.SFC = ({title, message, errMessage}) => - -
    -

    {title}

    - {message &&
    {message}
    } - {errMessage &&
    {errMessage}
    } -
    -
    ; +const ErrorComponent: React.SFC = ({ title, message, errMessage }) => ( + + +
    +

    {title}

    + {message &&
    {message}
    } + {errMessage &&
    {errMessage}
    } +
    +
    +); -export const ErrorPage: React.SFC = () =>
    - - Error - - -
    ; +export const ErrorPage: React.SFC = () => ( +
    + + Error + + +
    +); -export const ErrorPage404: React.SFC = (props) =>
    - - Page Not Found (404) - - -
    ; +export const ErrorPage404: React.SFC = (props) => ( +
    + + Page Not Found (404) + + +
    +); -export const ErrorBoundaryFallback: React.SFC = (props) => +export const ErrorBoundaryFallback: React.SFC = (props) => (

    Oh no! Something went wrong.

    @@ -86,7 +104,8 @@ export const ErrorBoundaryFallback: React.SFC = (pro
    -
    ; +
    +); export type ErrorComponentProps = { title: string; diff --git a/frontend/public/components/events.jsx b/frontend/public/components/events.jsx index 62a09e9c576..2f3581504f4 100644 --- a/frontend/public/components/events.jsx +++ b/frontend/public/components/events.jsx @@ -31,71 +31,96 @@ const maxMessages = 500; const flushInterval = 500; // Predicate function to filter by event "category" (info, error, or all) -export const categoryFilter = (category, {reason}) => { +export const categoryFilter = (category, { reason }) => { if (category === 'all') { return true; } const errorSubstrings = ['error', 'failed', 'unhealthy', 'nodenotready']; - const isError = reason && errorSubstrings.find(substring => reason.toLowerCase().includes(substring)); + const isError = + reason && errorSubstrings.find((substring) => reason.toLowerCase().includes(substring)); return category === 'error' ? isError : !isError; }; -const kindFilter = (kind, {involvedObject}) => { +const kindFilter = (kind, { involvedObject }) => { return kind === 'all' || involvedObject.kind === kind; }; -const Inner = connectToFlags(FLAGS.CAN_LIST_NODE)(class Inner extends React.PureComponent { - render() { - const { event, flags } = this.props; - const { count, firstTimestamp, lastTimestamp, involvedObject: obj, source, message, reason } = event; - const tooltipMsg = `${reason} (${obj.kind})`; - const isError = categoryFilter('error', event); - - return
    -
    - -
    -
    -
    -
    -
    - - {obj.namespace && } - +const Inner = connectToFlags(FLAGS.CAN_LIST_NODE)( + class Inner extends React.PureComponent { + render() { + const { event, flags } = this.props; + const { + count, + firstTimestamp, + lastTimestamp, + involvedObject: obj, + source, + message, + reason, + } = event; + const tooltipMsg = `${reason} (${obj.kind})`; + const isError = categoryFilter('error', event); + + return ( +
    +
    + +
    -
    - - Generated from {source.component} - {source.component === 'kubelet' && on {flags[FLAGS.CAN_LIST_NODE] - ? {source.host} - : {source.host}} - } - - {count > 1 && - {count} times in the last - } +
    +
    +
    + + {obj.namespace && ( + + )} + +
    +
    + + Generated from {source.component} + {source.component === 'kubelet' && ( + + {' '} + on{' '} + {flags[FLAGS.CAN_LIST_NODE] ? ( + + {source.host} + + ) : ( + {source.host} + )} + + )} + + {count > 1 && ( + + {count} times in the last{' '} + + + )} +
    +
    + +
    {message}
    + ); + } + }, +); -
    - {message} -
    -
    -
    ; - } -}); - -const categories = {all: 'All Categories', info: 'Info', error: 'Error'}; +const categories = { all: 'All Categories', info: 'Info', error: 'Error' }; export class EventsList extends React.Component { constructor(props) { @@ -109,50 +134,50 @@ export class EventsList extends React.Component { render() { const { category, kind, textFilter } = this.state; - const { autoFocus=true, mock } = this.props; - - return -
    -
    - this.setState({kind: v})} - selected={kind} - showAll - title="All Types" - /> - this.setState({category: v})} - selectedKey={this.state.category} - title="All Categories" - /> -
    -
    - this.setState({textFilter: e.target.value || ''})} - /> + const { autoFocus = true, mock } = this.props; + + return ( + +
    +
    + this.setState({ kind: v })} + selected={kind} + showAll + title="All Types" + /> + this.setState({ category: v })} + selectedKey={this.state.category} + title="All Categories" + /> +
    +
    + this.setState({ textFilter: e.target.value || '' })} + /> +
    -
    - - ; + + + ); } } export const NoEvents = () => ( -
    - No events in the past hour -
    +
    No events in the past hour
    ); @@ -160,7 +185,8 @@ export const NoMatchingEvents = ({ allCount }) => (
    No matching events
    - {allCount}{allCount >= maxMessages && '+'} events exist, but none match the current filter + {allCount} + {allCount >= maxMessages && '+'} events exist, but none match the current filter
    ); @@ -168,11 +194,13 @@ export const NoMatchingEvents = ({ allCount }) => ( export const ErrorLoadingEvents = () => (
    Error loading events
    -
    An error occurred during event retrieval. Attempting to reconnect...
    +
    + An error occurred during event retrieval. Attempting to reconnect... +
    ); -export const EventStreamPage = withStartGuide(({noProjectsAvailable, ...rest}) => +export const EventStreamPage = withStartGuide(({ noProjectsAvailable, ...rest }) => ( Events @@ -180,7 +208,7 @@ export const EventStreamPage = withStartGuide(({noProjectsAvailable, ...rest}) = -); +)); class EventStream extends React.Component { constructor(props) { @@ -212,8 +240,8 @@ class EventStream extends React.Component { bufferFlushInterval: flushInterval, bufferMax: maxMessages, }) - .onbulkmessage(events => { - events.forEach(({object, type}) => { + .onbulkmessage((events) => { + events.forEach(({ object, type }) => { const uid = object.metadata.uid; switch (type) { @@ -238,18 +266,18 @@ class EventStream extends React.Component { }) .onopen(() => { this.messages = {}; - this.setState({error: false, loading: false, sortedMessages: [], filteredEvents: []}); + this.setState({ error: false, loading: false, sortedMessages: [], filteredEvents: [] }); }) - .onclose(evt => { + .onclose((evt) => { if (evt && evt.wasClean === false) { - this.setState({error: evt.reason || 'Connection did not close cleanly.'}); + this.setState({ error: evt.reason || 'Connection did not close cleanly.' }); } this.messages = {}; - this.setState({sortedMessages: [], filteredEvents: []}); + this.setState({ sortedMessages: [], filteredEvents: [] }); }) .onerror(() => { this.messages = {}; - this.setState({error: true, sortedMessages: [], filteredEvents: []}); + this.setState({ error: true, sortedMessages: [], filteredEvents: [] }); }); } @@ -263,7 +291,7 @@ class EventStream extends React.Component { this.ws && this.ws.destroy(); } - static filterEvents(messages, {kind, category, filter, textFilter}) { + static filterEvents(messages, { kind, category, filter, textFilter }) { // Don't use `fuzzy` because it results in some surprising matches in long event messages. // Instead perform an exact substring match on each word in the text filter. const words = _.uniq(_.toLower(textFilter).match(/\S+/g)).sort((a, b) => { @@ -277,7 +305,7 @@ class EventStream extends React.Component { } const name = _.get(obj, 'involvedObject.name', ''); const message = _.toLower(obj.message); - return _.every(words, word => name.indexOf(word) !== -1 || message.indexOf(word) !== -1); + return _.every(words, (word) => name.indexOf(word) !== -1 || message.indexOf(word) !== -1); }; const f = (obj) => { @@ -287,7 +315,7 @@ class EventStream extends React.Component { if (kind && !kindFilter(kind, obj)) { return false; } - if (filter && !filter.some(flt => flt(obj.involvedObject))) { + if (filter && !filter.some((flt) => flt(obj.involvedObject))) { return false; } if (!textMatches(obj)) { @@ -300,12 +328,14 @@ class EventStream extends React.Component { } static getDerivedStateFromProps(nextProps, prevState) { - const {filter, kind, category, textFilter, loading} = prevState; - - if (_.isEqual(filter, nextProps.filter) - && kind === nextProps.kind - && category === nextProps.category - && textFilter === nextProps.textFilter) { + const { filter, kind, category, textFilter, loading } = prevState; + + if ( + _.isEqual(filter, nextProps.filter) && + kind === nextProps.kind && + category === nextProps.category && + textFilter === nextProps.textFilter + ) { return {}; } @@ -337,7 +367,10 @@ class EventStream extends React.Component { // In addition to sorting by timestamp, secondarily sort by name so that the order is consistent when events have // the same timestamp const sorted = _.orderBy(this.messages, ['lastTimestamp', 'name'], ['desc', 'asc']); - const oldestTimestamp = _.min([this.state.oldestTimestamp, new Date(_.last(sorted).lastTimestamp)]); + const oldestTimestamp = _.min([ + this.state.oldestTimestamp, + new Date(_.last(sorted).lastTimestamp), + ]); sorted.splice(maxMessages); this.setState({ oldestTimestamp, @@ -350,7 +383,7 @@ class EventStream extends React.Component { } toggleStream_() { - this.setState({active: !this.state.active}, () => { + this.setState({ active: !this.state.active }, () => { if (this.state.active) { this.ws && this.ws.unpause(); } else { @@ -361,7 +394,7 @@ class EventStream extends React.Component { render() { const { mock, resourceEventStream } = this.props; - const {active, error, loading, filteredEvents, sortedMessages} = this.state; + const { active, error, loading, filteredEvents, sortedMessages } = this.state; const count = filteredEvents.length; const allCount = sortedMessages.length; const noEvents = allCount === 0 && this.ws && this.ws.bufferSize() === 0; @@ -376,7 +409,11 @@ class EventStream extends React.Component { } if (error) { - statusBtnTxt = Error connecting to event stream{_.isString(error) && `: ${error}`}; + statusBtnTxt = ( + + Error connecting to event stream{_.isString(error) && `: ${error}`} + + ); sysEventStatus = ; } else if (loading) { statusBtnTxt = Loading events...; @@ -390,29 +427,34 @@ class EventStream extends React.Component { const klass = classNames('co-sysevent-stream__timeline', { 'co-sysevent-stream__timeline--empty': !allCount || !count, }); - const messageCount = count < maxMessages ? `Showing ${pluralize(count, 'event')}` : `Showing ${count} of ${allCount}+ events`; - - return
    -
    -
    -
    - { statusBtnTxt } -
    -
    - { messageCount } + const messageCount = + count < maxMessages + ? `Showing ${pluralize(count, 'event')}` + : `Showing ${count} of ${allCount}+ events`; + + return ( +
    +
    +
    +
    {statusBtnTxt}
    +
    {messageCount}
    -
    -
    - -
    - There are no events before +
    + +
    + There are no events before +
    + {count > 0 && } + {sysEventStatus}
    - { count > 0 && } - { sysEventStatus }
    -
    ; + ); } } @@ -432,8 +474,19 @@ EventStream.propTypes = { textFilter: PropTypes.string, }; +export const ResourceEventStream = ({ + obj: { + kind, + metadata: { name, namespace, uid }, + }, +}) => ( + +); -export const ResourceEventStream = ({obj: {kind, metadata: {name, namespace, uid}}}) => - ; - -export const ResourcesEventStream = ({ filters, namespace }) => ; +export const ResourcesEventStream = ({ filters, namespace }) => ( + +); diff --git a/frontend/public/components/factory/details.tsx b/frontend/public/components/factory/details.tsx index d08551b58c9..670fad85ae4 100644 --- a/frontend/public/components/factory/details.tsx +++ b/frontend/public/components/factory/details.tsx @@ -2,7 +2,13 @@ import * as React from 'react'; import { match } from 'react-router-dom'; import * as _ from 'lodash-es'; -import { Firehose, HorizontalNav, PageHeading, FirehoseResource, KebabOptionsCreator } from '../utils'; +import { + Firehose, + HorizontalNav, + PageHeading, + FirehoseResource, + KebabOptionsCreator, +} from '../utils'; import { K8sResourceKindReference, K8sResourceKind, K8sKind } from '../../module/k8s'; import { withFallback } from '../utils/error-boundary'; import { ErrorBoundaryFallback } from '../error'; @@ -12,14 +18,18 @@ export const DetailsPage = withFallback((props) => { const resourceKeys = _.map(props.resources, 'prop'); return ( - + ((props) => { menuActions={props.menuActions} buttonActions={props.buttonActions} kind={props.kind} - breadcrumbsFor={props.breadcrumbsFor ? props.breadcrumbsFor : breadcrumbsForDetailsPage(props.kindObj, props.match)} + breadcrumbsFor={ + props.breadcrumbsFor + ? props.breadcrumbsFor + : breadcrumbsForDetailsPage(props.kindObj, props.match) + } resourceKeys={resourceKeys} customData={props.customData} badge={props.badge} @@ -66,10 +80,10 @@ export type DetailsPageProps = { name?: string; namespace?: string; resources?: FirehoseResource[]; - breadcrumbsFor?: (obj: K8sResourceKind) => {name: string, path: string}[]; + breadcrumbsFor?: (obj: K8sResourceKind) => { name: string; path: string }[]; customData?: any; badge?: React.ReactNode; - icon?: React.ComponentType<{obj: K8sResourceKind}>; + icon?: React.ComponentType<{ obj: K8sResourceKind }>; }; DetailsPage.displayName = 'DetailsPage'; diff --git a/frontend/public/components/factory/list-page.jsx b/frontend/public/components/factory/list-page.jsx index c0502c1367c..81f2c88f5b7 100644 --- a/frontend/public/components/factory/list-page.jsx +++ b/frontend/public/components/factory/list-page.jsx @@ -24,21 +24,27 @@ import { RequireCreatePermission, } from '../utils'; -export const CompactExpandButtons = ({expand = false, onExpandChange = _.noop}) =>
    - - -
    ; +export const CompactExpandButtons = ({ expand = false, onExpandChange = _.noop }) => ( +
    + + +
    +); /** @type {React.SFC<{disabled?: boolean, label: string, onChange: React.ChangeEventHandler, defaultValue?: string, value?: string}}>} */ -export const TextFilter = ({label, onChange, defaultValue, style, className, value}) => { +export const TextFilter = ({ label, onChange, defaultValue, style, className, value }) => { const input = React.useRef(); const onKeyDown = (e) => { const { nodeName } = e.target; - if (nodeName === 'INPUT' || nodeName === 'TEXTAREA' || e.key !== KEYBOARD_SHORTCUTS.focusFilterInput) { + if ( + nodeName === 'INPUT' || + nodeName === 'TEXTAREA' || + e.key !== KEYBOARD_SHORTCUTS.focusFilterInput + ) { return; } @@ -64,13 +70,15 @@ export const TextFilter = ({label, onChange, defaultValue, style, className, val value={value} defaultValue={defaultValue} onChange={onChange} - onKeyDown={e => e.key === 'Escape' && e.target.blur()} + onKeyDown={(e) => e.key === 'Escape' && e.target.blur()} placeholder={`Filter ${label}...`} style={style} tabIndex={0} type="text" /> - / + + / +
    ); }; @@ -81,36 +89,36 @@ TextFilter.displayName = 'TextFilter'; /** @augments {React.PureComponent<{ListComponent: React.ComponentType, kinds: string[], filters?:any, flatten?: function, data?: any[], rowFilters?: any[]}>} */ export class ListPageWrapper_ extends React.PureComponent { render() { - const { - flatten, - kinds, - ListComponent, - reduxIDs, - rowFilters, - } = this.props; + const { flatten, kinds, ListComponent, reduxIDs, rowFilters } = this.props; const data = flatten ? flatten(this.props.resources) : []; - const RowsOfRowFilters = rowFilters && _.map(rowFilters, ({items, reducer, selected, type, numbers}, i) => { - const count = _.isFunction(numbers) ? numbers(data) : undefined; - return ; - }); - - return
    - {!_.isEmpty(data) && RowsOfRowFilters} -
    -
    - + const RowsOfRowFilters = + rowFilters && + _.map(rowFilters, ({ items, reducer, selected, type, numbers }, i) => { + const count = _.isFunction(numbers) ? numbers(data) : undefined; + return ( + + ); + }); + + return ( +
    + {!_.isEmpty(data) && RowsOfRowFilters} +
    +
    + +
    -
    ; + ); } } @@ -125,19 +133,26 @@ ListPageWrapper_.propTypes = { }; /** @type {React.FC<, {canCreate?: Boolean, canExpand?: Boolean, textFilter:string, createAccessReview?: Object, createButtonText?: String, createProps?: Object, fieldSelector?: String, filterLabel?: String, resources: any, badge?: React.ReactNode}>*/ -export const FireMan_ = connect(null, {filterList})( +export const FireMan_ = connect( + null, + { filterList }, +)( class ConnectedFireMan extends React.PureComponent { constructor(props) { super(props); this.onExpandChange = this.onExpandChange.bind(this); this.applyFilter = this.applyFilter.bind(this); - const reduxIDs = props.resources.map(r => makeReduxID(kindObj(r.kind), makeQuery(r.namespace, r.selector, r.fieldSelector, r.name))); + const reduxIDs = props.resources.map((r) => + makeReduxID(kindObj(r.kind), makeQuery(r.namespace, r.selector, r.fieldSelector, r.name)), + ); this.state = { reduxIDs }; } - componentWillReceiveProps({resources}) { - const reduxIDs = resources.map(r => makeReduxID(kindObj(r.kind), makeQuery(r.namespace, r.selector, r.fieldSelector, r.name))); + componentWillReceiveProps({ resources }) { + const reduxIDs = resources.map((r) => + makeReduxID(kindObj(r.kind), makeQuery(r.namespace, r.selector, r.fieldSelector, r.name)), + ); if (_.isEqual(reduxIDs, this.state.reduxIDs)) { return; } @@ -148,7 +163,7 @@ export const FireMan_ = connect(null, {filterList})( } onExpandChange(expand) { - this.setState({expand}); + this.setState({ expand }); } updateURL(filterName, options) { @@ -174,7 +189,7 @@ export const FireMan_ = connect(null, {filterList})( if (filterName.indexOf(storagePrefix) === 0) { return; } - this.state.reduxIDs.forEach(id => this.props.filterList(id, filterName, options)); + this.state.reduxIDs.forEach((id) => this.props.filterList(id, filterName, options)); this.updateURL(filterName, options); } @@ -213,59 +228,95 @@ export const FireMan_ = connect(null, {filterList})( let createLink; if (canCreate) { if (createProps.to) { - createLink = - - ; + createLink = ( + + + + ); } else if (createProps.items) { - createLink =
    - -
    ; + createLink = ( +
    + +
    + ); } else { - createLink =
    - -
    ; + createLink = ( +
    + +
    + ); } if (!_.isEmpty(createAccessReview)) { - createLink = {createLink}; + createLink = ( + + {createLink} + + ); } } - return - {title && } - {/* Show help text above the filter bar if there's a create button. */} - {helpText && createLink &&

    {helpText}

    } -
    - {helpText && !createLink &&
    - {helpText} -
    } - {createLink &&
    - {createLink} -
    } - {canExpand &&
    - -
    } -
    - this.applyFilter(textFilter, e.target.value)} defaultValue={this.defaultValue} tabIndex={1} autoFocus={autoFocus} /> + return ( + + {title && } + {/* Show help text above the filter bar if there's a create button. */} + {helpText && createLink && ( +

    {helpText}

    + )} +
    + {helpText && !createLink && ( +
    + {helpText} +
    + )} + {createLink &&
    {createLink}
    } + {canExpand && ( +
    + +
    + )} +
    + this.applyFilter(textFilter, e.target.value)} + defaultValue={this.defaultValue} + tabIndex={1} + autoFocus={autoFocus} + /> +
    -
    -
    - {inject(this.props.children, { - resources, - expand: this.state.expand, - reduxIDs: this.state.reduxIDs, - applyFilter:this.applyFilter, - })} -
    - ; +
    + {inject(this.props.children, { + resources, + expand: this.state.expand, + reduxIDs: this.state.reduxIDs, + applyFilter: this.applyFilter, + })} +
    + + ); } - } + }, ); FireMan_.displayName = 'FireMan'; @@ -296,7 +347,7 @@ FireMan_.propTypes = { matchLabels: PropTypes.objectOf(PropTypes.string), matchExpressions: PropTypes.arrayOf(PropTypes.object), }), - }) + }), ).isRequired, selectorFilterLabel: PropTypes.string, textFilter: PropTypes.string, @@ -304,7 +355,7 @@ FireMan_.propTypes = { }; /** @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} >} */ -export const ListPage = withFallback(props => { +export const ListPage = withFallback((props) => { const { autoFocus, canCreate, @@ -330,34 +381,37 @@ export const ListPage = withFallback(props => { } = props; let { createProps } = props; const ko = kindObj(kind); - const { - label, - labelPlural, - namespaced, - plural, - } = ko; + const { label, labelPlural, namespaced, plural } = ko; const title = props.title || labelPlural; const usedNamespace = !namespace && namespaced ? _.get(match, 'params.ns') : namespace; - let href = usedNamespace ? `/k8s/ns/${usedNamespace || 'default'}/${plural}/~new` : `/k8s/cluster/${plural}/~new`; + let href = usedNamespace + ? `/k8s/ns/${usedNamespace || 'default'}/${plural}/~new` + : `/k8s/cluster/${plural}/~new`; if (ko.crd) { try { const ref = referenceForModel(ko); - href = usedNamespace ? `/k8s/ns/${usedNamespace || 'default'}/${ref}/~new` : `/k8s/cluster/${ref}/~new`; - } catch (unused) { /**/ } + href = usedNamespace + ? `/k8s/ns/${usedNamespace || 'default'}/${ref}/~new` + : `/k8s/cluster/${ref}/~new`; + } catch (unused) { + /**/ + } } - createProps = createProps || (createHandler ? {onClick: createHandler} : {to: href}); + createProps = createProps || (createHandler ? { onClick: createHandler } : { to: href }); const createAccessReview = skipAccessReview ? null : { model: ko, namespace: usedNamespace }; - const resources = [{ - fieldSelector, - filters, - kind, - limit, - name, - namespaced, - selector, - }]; + const resources = [ + { + fieldSelector, + filters, + kind, + limit, + name, + namespaced, + selector, + }, + ]; // Don't show row filters if props.filters were passed. The content is already filtered and the row filters will have incorrect counts. const rowFilters = _.isEmpty(filters) ? props.rowFilters : null; @@ -366,34 +420,36 @@ export const ListPage = withFallback(props => { return ; } - return _.get(_resources, (name || kind), {}).data} - helpText={helpText} - label={labelPlural} - ListComponent={ListComponent} - mock={mock} - namespace={usedNamespace} - resources={resources} - rowFilters={rowFilters} - selectorFilterLabel="Filter by selector (app=nginx) ..." - showTitle={showTitle} - textFilter={textFilter} - title={title} - badge={badge} - />; + return (( + _.get(_resources, name || kind, {}).data} + helpText={helpText} + label={labelPlural} + ListComponent={ListComponent} + mock={mock} + namespace={usedNamespace} + resources={resources} + rowFilters={rowFilters} + selectorFilterLabel="Filter by selector (app=nginx) ..." + showTitle={showTitle} + textFilter={textFilter} + title={title} + badge={badge} + /> + )); }, ErrorBoundaryFallback); ListPage.displayName = 'ListPage'; /** @type {React.SFC<{canCreate?: boolean, createButtonText?: string, createProps?: any, flatten?: Function, title?: string, showTitle?: boolean, helpText?: any, filterLabel?: string, textFilter?: string, rowFilters?: any[], resources: any[], ListComponent: React.ComponentType, namespace?: string, customData?: any, badge?: React.ReactNode >} */ -export const MultiListPage = props => { +export const MultiListPage = (props) => { const { autoFocus, canCreate, @@ -424,34 +480,35 @@ export const MultiListPage = props => { prop: r.prop || r.kind, })); - - return - - - - ; + return ( + + + + + + ); }; MultiListPage.displayName = 'MultiListPage'; diff --git a/frontend/public/components/factory/list.tsx b/frontend/public/components/factory/list.tsx index e4a08a521db..80c74d48b39 100644 --- a/frontend/public/components/factory/list.tsx +++ b/frontend/public/components/factory/list.tsx @@ -44,7 +44,7 @@ import { tableFilters } from './table-filters'; const rowFiltersToFilterFuncs = (rowFilters) => { return (rowFilters || []) - .filter(f => f.type && _.isFunction(f.filter)) + .filter((f) => f.type && _.isFunction(f.filter)) .reduce((acc, f) => ({ ...acc, [f.type]: f.filter }), {}); }; @@ -62,7 +62,7 @@ const getFilteredRows = (_filters, rowFilters, objects) => { _.each(_filters, (value, name) => { const filter = allTableFilters[name]; if (_.isFunction(filter)) { - objects = _.filter(objects, o => filter(value, o)); + objects = _.filter(objects, (o) => filter(value, o)); } }); @@ -79,30 +79,33 @@ const filterPropType = (props, propName, componentName) => { if (key in allTableFilters || key === 'loadTest') { continue; } - return new Error(`Invalid prop '${propName}' in '${componentName}'. '${key}' is not a valid filter type!`); + 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')) + _.size(_.get(resource, 'binaryData')), + daemonsetNumScheduled: (daemonset) => + _.toInteger(_.get(daemonset, 'status.currentNumberScheduled')), + dataSize: (resource) => _.size(_.get(resource, 'data')) + _.size(_.get(resource, 'binaryData')), ingressValidHosts, serviceCatalogStatus, - jobCompletions: job => getJobTypeAndCompletions(job).completions, - jobType: job => getJobTypeAndCompletions(job).type, - nodeReadiness: node => { + jobCompletions: (job) => getJobTypeAndCompletions(job).completions, + jobType: (job) => getJobTypeAndCompletions(job).type, + nodeReadiness: (node) => { let readiness = _.get(node, 'status.conditions'); - readiness = _.find(readiness, {type: 'Ready'}); + readiness = _.find(readiness, { type: 'Ready' }); return _.get(readiness, 'status'); }, - numReplicas: resource => _.toInteger(_.get(resource, 'status.replicas')), + numReplicas: (resource) => _.toInteger(_.get(resource, 'status.replicas')), planExternalName, podPhase, podReadiness, serviceClassDisplayName, silenceStateOrder, - string: val => JSON.stringify(val), + string: (val) => JSON.stringify(val), getClusterOperatorStatus, getClusterOperatorVersion, getTemplateInstanceStatus, @@ -125,7 +128,7 @@ export class ColHead extends React.Component { }; componentWillMount() { - const {applySort, children, sortField, sortFunc} = this.props; + const { applySort, children, sortField, sortFunc } = this.props; const sp = new URLSearchParams(window.location.search); if (sp.get('sortBy') === children) { @@ -153,23 +156,61 @@ export class ColHead extends React.Component { const isSorted = sortField === currentSortField && sortFunc === currentSortFunc; const newSortOrder = isSorted && currentSortOrder === 'asc' ? 'desc' : 'asc'; const onClick = () => applySort(sortField, sortFunc, newSortOrder, children); - return
    - {children} - {isSorted && (currentSortOrder === 'asc' ? : )} -
    ; + return ( +
    + + {children} + + {isSorted && + (currentSortOrder === 'asc' ? ( + + ) : ( + + ))} +
    + ); } } -export const ListHeader: React.SFC = ({children}) =>
    {children}
    ; +export const ListHeader: React.SFC = ({ children }) => ( +
    {children}
    +); ListHeader.displayName = 'ListHeader'; -export const WorkloadListHeader = props => - Name - Namespace - Labels - Status - Pod Selector -; +export const WorkloadListHeader = (props) => ( + + + Name + + + Namespace + + + Labels + + + Status + + + Pod Selector + + +); const getRowKey = (obj, index) => { return _.get(obj, 'rowKey') || _.get(obj, 'metadata.uid', index); @@ -181,53 +222,71 @@ const VirtualRows: React.SFC = (props) => { const measurementCache = new CellMeasurerCache({ fixedWidth: true, minHeight: 44, - keyMapper: rowIndex => `${_.get(props.data[rowIndex], 'metadata.uid', rowIndex)}-${props.expand ? 'expanded' : 'collapsed'}`, + keyMapper: (rowIndex) => + `${_.get(props.data[rowIndex], 'metadata.uid', rowIndex)}-${ + props.expand ? 'expanded' : 'collapsed' + }`, }); - const rowRenderer = ({index, style, key, parent}) => { - const {data, expand, Row, kindObj} = props; + const rowRenderer = ({ index, style, key, parent }) => { + const { data, expand, Row, kindObj } = props; const obj = data[index]; - return -
    - -
    -
    ; + return ( + +
    + +
    +
    + ); }; // Default `height` to 0 to avoid console errors from https://github.com/bvaughn/react-virtualized/issues/1158 - return
    - { mock - ? - : - {({height, isScrolling, registerChild, onChildScroll, scrollTop}) => - - {({width}) =>
    - -
    } -
    } -
    - } -
    ; + return ( +
    + {mock ? ( + + ) : ( + + {({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => ( + + {({ width }) => ( +
    + +
    + )} +
    + )} +
    + )} +
    + ); }; VirtualRows.propTypes = { @@ -240,13 +299,17 @@ VirtualRows.propTypes = { }; const Rows: React.SFC = (props) => { - const {Row, expand, kindObj} = props; - - return
    - { props.data.map((obj, i) =>
    - -
    ) } -
    ; + const { Row, expand, kindObj } = props; + + return ( +
    + {props.data.map((obj, i) => ( +
    + +
    + ))} +
    + ); }; Rows.propTypes = { @@ -260,13 +323,29 @@ Rows.propTypes = { const skeletonTable =
    ; -const stateToProps = ({UI}, {data = [], defaultSortField = 'metadata.name', defaultSortFunc = undefined, filters = {}, loaded = false, reduxID = null, reduxIDs = null, staticFilters = [{}], rowFilters = []}) => { +const stateToProps = ( + { UI }, + { + data = [], + defaultSortField = 'metadata.name', + defaultSortFunc = undefined, + filters = {}, + loaded = false, + reduxID = null, + reduxIDs = null, + staticFilters = [{}], + rowFilters = [], + }, +) => { const allFilters = staticFilters ? Object.assign({}, filters, ...staticFilters) : filters; let newData = getFilteredRows(allFilters, rowFilters, 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 currentSortField = UI.getIn( + ['listSorts', listId, 'field'], + defaultSortFunc ? undefined : defaultSortField, + ); const currentSortFunc = UI.getIn(['listSorts', listId, 'func'], defaultSortFunc); const currentSortOrder = UI.getIn(['listSorts', listId, 'orderBy'], 'asc'); @@ -274,7 +353,7 @@ const stateToProps = ({UI}, {data = [], defaultSortField = 'metadata.name', defa 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, '')); + sortBy = (resource) => sorts.string(_.get(resource, currentSortField, '')); } else if (currentSortFunc && sorts[currentSortFunc]) { // Sort resources by a function in the 'sorts' object sortBy = sorts[currentSortFunc]; @@ -294,9 +373,14 @@ const stateToProps = ({UI}, {data = [], defaultSortField = 'metadata.name', defa }; }; -export const List = connect(stateToProps, {sortList: UIActions.sortList}, null, { - areStatesEqual: ({UI: next}, {UI: prev}) => next.get('listSorts') === prev.get('listSorts'), -})( +export const List = connect( + stateToProps, + { sortList: UIActions.sortList }, + null, + { + areStatesEqual: ({ UI: next }, { UI: prev }) => next.get('listSorts') === prev.get('listSorts'), + }, +)( class ListInner extends React.Component { static propTypes = { data: PropTypes.array, @@ -328,33 +412,62 @@ export const List = connect(stateToProps, {sortList: UIActions.sortList}, null, }; render() { - const {currentSortField, currentSortFunc, currentSortOrder, expand, Header, label, listId, mock, Row, sortList, virtualize = true} = this.props; - const componentProps: any = _.pick(this.props, ['data', 'filters', 'selected', 'match', 'kindObj']); + const { + currentSortField, + currentSortFunc, + currentSortOrder, + expand, + Header, + label, + listId, + mock, + Row, + sortList, + virtualize = true, + } = this.props; + const componentProps: any = _.pick(this.props, [ + 'data', + 'filters', + 'selected', + 'match', + 'kindObj', + ]); const ListRows = virtualize ? VirtualRows : Rows; - const children = -
    - - ; - - return
    - { mock ? children : {children} } -
    ; + const children = ( + +
    + + + ); + + return ( +
    + {mock ? ( + children + ) : ( + + {children} + + )} +
    + ); } - }); + }, +); export class ResourceRow extends React.Component { shouldComponentUpdate(nextProps) { @@ -378,32 +491,43 @@ export class ResourceRow extends React.Component { } render() { - return
    {this.props.children}
    ; + return ( +
    + {this.props.children} +
    + ); } } -export const WorkloadListRow: React.SFC = ({kind, actions, obj: o}) => -
    - -
    -
    - -
    -
    - -
    -
    - - {o.status.replicas || 0} of {o.spec.replicas} pods - -
    -
    - -
    -
    - -
    -
    ; +export const WorkloadListRow: React.SFC = ({ kind, actions, obj: o }) => ( + +
    + +
    +
    + +
    +
    + +
    +
    + + {o.status.replicas || 0} of {o.spec.replicas} pods + +
    +
    + +
    +
    + +
    +
    +); export type ColHeadProps = { applySort?: Function; @@ -427,7 +551,7 @@ export type ListInnerProps = { EmptyMsg?: React.ComponentType<{}>; expand?: boolean; fieldSelector?: string; - filters?: {[name: string]: any}; + filters?: { [name: string]: any }; Header: React.ComponentType; label?: string; listId?: string; diff --git a/frontend/public/components/factory/modal.tsx b/frontend/public/components/factory/modal.tsx index 368ebb27e02..e085fa0a1c7 100644 --- a/frontend/public/components/factory/modal.tsx +++ b/frontend/public/components/factory/modal.tsx @@ -11,9 +11,9 @@ import store from '../../redux'; import { ButtonBar } from '../utils/button-bar'; import { history } from '../utils/router'; -export const createModal: CreateModal = getModalContainer => { +export const createModal: CreateModal = (getModalContainer) => { const modalContainer = document.getElementById('modal-container'); - const result = new Promise(resolve => { + const result = new Promise((resolve) => { const closeModal = (e?: React.SyntheticEvent) => { if (e && e.stopPropagation) { e.stopPropagation(); @@ -24,7 +24,7 @@ export const createModal: CreateModal = getModalContainer => { Modal.setAppElement(modalContainer); ReactDOM.render(getModalContainer(closeModal), modalContainer); }); - return {result}; + return { result }; }; export const createModalLauncher: CreateModalLauncher = (Component) => (props) => { @@ -40,15 +40,20 @@ export const createModalLauncher: CreateModalLauncher = (Component) => (props) = return ( - + - + shouldCloseOnOverlayClick={!props.blocking} + > + @@ -57,9 +62,16 @@ export const createModalLauncher: CreateModalLauncher = (Component) => (props) = return createModal(getModalContainer); }; -export const ModalTitle: React.SFC = ({children, className = 'modal-header'}) =>

    {children}

    ; +export const ModalTitle: React.SFC = ({ + children, + className = 'modal-header', +}) => ( +
    +

    {children}

    +
    +); -export const ModalBody: React.SFC = ({children}) => ( +export const ModalBody: React.SFC = ({ children }) => (
    {children}
    @@ -67,32 +79,67 @@ export const ModalBody: React.SFC = ({children}) => (
    ); - -export const ModalFooter: React.SFC = ({message, errorMessage, inProgress, children}) => { - return - {children} - ; +export const ModalFooter: React.SFC = ({ + message, + errorMessage, + inProgress, + children, +}) => { + return ( + + {children} + + ); }; -export const ModalSubmitFooter: React.SFC = ({message, errorMessage, inProgress, cancel, submitText, cancelText, submitDisabled, submitDanger}) => { - const onCancelClick = e => { +export const ModalSubmitFooter: React.SFC = ({ + message, + errorMessage, + inProgress, + cancel, + submitText, + cancelText, + submitDisabled, + submitDanger, +}) => { + const onCancelClick = (e) => { e.stopPropagation(); cancel(e); }; - return - - - {submitDanger - ? - : } - - ; + return ( + + + + {submitDanger ? ( + + ) : ( + + )} + + + ); }; export type GetModalContainer = (onClose: (e?: React.SyntheticEvent) => void) => React.ReactElement; -type CreateModal = (getModalContainer: GetModalContainer) => {result: Promise}; +type CreateModal = (getModalContainer: GetModalContainer) => { result: Promise }; export type CreateModalLauncherProps = { blocking?: boolean; @@ -129,5 +176,6 @@ export type ModalSubmitFooterProps = { submitDanger?: boolean; }; -export type CreateModalLauncher =

    (C: React.ComponentType

    ) => - (props: P & CreateModalLauncherProps) => {result: Promise<{}>}; +export type CreateModalLauncher =

    ( + C: React.ComponentType

    , +) => (props: P & CreateModalLauncherProps) => { result: Promise<{}> }; diff --git a/frontend/public/components/factory/table-filters.ts b/frontend/public/components/factory/table-filters.ts index af9c26db058..3fbbaa8fc7e 100644 --- a/frontend/public/components/factory/table-filters.ts +++ b/frontend/public/components/factory/table-filters.ts @@ -16,16 +16,14 @@ import { getTemplateInstanceStatus, } from '../../module/k8s'; -import { - alertState, - silenceState, -} from '../../reducers/monitoring'; +import { alertState, silenceState } from '../../reducers/monitoring'; -export const fuzzyCaseInsensitive = (a: string, b: string): boolean => fuzzy(_.toLower(a), _.toLower(b)); +export const fuzzyCaseInsensitive = (a: string, b: string): boolean => + fuzzy(_.toLower(a), _.toLower(b)); // TODO: Table filters are undocumented, stringly-typed, and non-obvious. We can change that. export const tableFilters: TableFilterMap = { - 'name': (filter, obj) => fuzzyCaseInsensitive(filter, obj.metadata.name), + name: (filter, obj) => fuzzyCaseInsensitive(filter, obj.metadata.name), 'alert-name': (filter, alert) => fuzzyCaseInsensitive(filter, _.get(alert, 'labels.alertname')), @@ -42,15 +40,15 @@ export const tableFilters: TableFilterMap = { '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); + '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) => { + selector: (selector, obj) => { if (!selector || !selector.values || !selector.values.size) { return true; } @@ -82,9 +80,14 @@ export const tableFilters: TableFilterMap = { return filters.selected.has(resource.kind); }, - 'packagemanifest-name': (filter, pkg) => fuzzyCaseInsensitive(filter, (pkg.status.defaultChannel - ? pkg.status.channels.find(ch => ch.name === pkg.status.defaultChannel) - : pkg.status.channels[0]).currentCSVDesc.displayName), + 'packagemanifest-name': (filter, pkg) => + fuzzyCaseInsensitive( + filter, + (pkg.status.defaultChannel + ? pkg.status.channels.find((ch) => ch.name === pkg.status.defaultChannel) + : pkg.status.channels[0] + ).currentCSVDesc.displayName, + ), 'build-status': (phases, build) => { if (!phases || !phases.selected || !phases.selected.size) { @@ -168,9 +171,11 @@ export const tableFilters: TableFilterMap = { return statuses.selected.has(status) || !_.includes(statuses.all, status); }, - 'machine': (str: string, machine: MachineKind): boolean => { + machine: (str: string, machine: MachineKind): boolean => { const node: string = _.get(machine, 'status.nodeRef.name'); - return fuzzyCaseInsensitive(str, machine.metadata.name) || (node && fuzzyCaseInsensitive(str, node)); + return ( + fuzzyCaseInsensitive(str, machine.metadata.name) || (node && fuzzyCaseInsensitive(str, node)) + ); }, }; @@ -186,4 +191,4 @@ export type TextFilter = (text: string, obj: any) => boolean; type TableFilterMap = { [key: string]: TableFilter | TextFilter; -} +}; diff --git a/frontend/public/components/factory/table.tsx b/frontend/public/components/factory/table.tsx index 95635ec7dc0..be9c188dc6c 100644 --- a/frontend/public/components/factory/table.tsx +++ b/frontend/public/components/factory/table.tsx @@ -31,7 +31,7 @@ import { SortByDirection, } from '@patternfly/react-table'; -import { CellMeasurerCache, CellMeasurer} from 'react-virtualized'; +import { CellMeasurerCache, CellMeasurer } from 'react-virtualized'; import { AutoSizer, @@ -43,7 +43,7 @@ import { tableFilters } from './table-filters'; const rowFiltersToFilterFuncs = (rowFilters) => { return (rowFilters || []) - .filter(f => f.type && _.isFunction(f.filter)) + .filter((f) => f.type && _.isFunction(f.filter)) .reduce((acc, f) => ({ ...acc, [f.type]: f.filter }), {}); }; @@ -62,7 +62,7 @@ const getFilteredRows = (_filters, rowFilters, objects) => { _.each(_filters, (value, name) => { const filter = allTableFilters[name]; if (_.isFunction(filter)) { - filteredObjects = _.filter(filteredObjects, o => filter(value, o)); + filteredObjects = _.filter(filteredObjects, (o) => filter(value, o)); } }); @@ -79,31 +79,34 @@ const filterPropType = (props, propName, componentName) => { if (key in allTableFilters || key === 'loadTest') { continue; } - return new Error(`Invalid prop '${propName}' in '${componentName}'. '${key}' is not a valid filter type!`); + 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')) + _.size(_.get(resource, 'binaryData')), + daemonsetNumScheduled: (daemonset) => + _.toInteger(_.get(daemonset, 'status.currentNumberScheduled')), + dataSize: (resource) => _.size(_.get(resource, 'data')) + _.size(_.get(resource, 'binaryData')), ingressValidHosts, serviceCatalogStatus, - jobCompletions: job => getJobTypeAndCompletions(job).completions, - jobType: job => getJobTypeAndCompletions(job).type, - nodeReadiness: node => { + jobCompletions: (job) => getJobTypeAndCompletions(job).completions, + jobType: (job) => getJobTypeAndCompletions(job).type, + nodeReadiness: (node) => { let readiness = _.get(node, 'status.conditions'); - readiness = _.find(readiness, {type: 'Ready'}); + readiness = _.find(readiness, { type: 'Ready' }); return _.get(readiness, 'status'); }, - numReplicas: resource => _.toInteger(_.get(resource, 'status.replicas')), + numReplicas: (resource) => _.toInteger(_.get(resource, 'status.replicas')), planExternalName, podPhase, podReadiness, serviceClassDisplayName, silenceStateOrder, - string: val => JSON.stringify(val), - number: val => _.toNumber(val), + string: (val) => JSON.stringify(val), + number: (val) => _.toNumber(val), getClusterOperatorStatus, getClusterOperatorVersion, getTemplateInstanceStatus, @@ -113,23 +116,30 @@ const sorts = { }, }; -const stateToProps = ({UI}, { - data = [], - defaultSortField = 'metadata.name', - defaultSortFunc = undefined, - defaultSortAsNumber = false, - filters = {}, - loaded = false, - reduxID = null, - reduxIDs = null, - staticFilters = [{}], - rowFilters = []}) => { +const stateToProps = ( + { UI }, + { + data = [], + defaultSortField = 'metadata.name', + defaultSortFunc = undefined, + defaultSortAsNumber = false, + filters = {}, + loaded = false, + reduxID = null, + reduxIDs = null, + staticFilters = [{}], + rowFilters = [], + }, +) => { const allFilters = staticFilters ? Object.assign({}, filters, ...staticFilters) : filters; let newData = getFilteredRows(allFilters, rowFilters, 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 currentSortField = UI.getIn( + ['listSorts', listId, 'field'], + defaultSortFunc ? undefined : defaultSortField, + ); const currentSortFunc = UI.getIn(['listSorts', listId, 'func'], defaultSortFunc); const currentSortAsNumber = UI.getIn(['listSorts', listId, 'sortAsNumber'], defaultSortAsNumber); const currentSortOrder = UI.getIn(['listSorts', listId, 'orderBy'], SortByDirection.asc); @@ -138,9 +148,9 @@ const stateToProps = ({UI}, { let sortBy: string | Function = 'metadata.name'; if (currentSortField) { if (currentSortAsNumber) { - sortBy = resource => sorts.number(_.get(resource, currentSortField, '')); + sortBy = (resource) => sorts.number(_.get(resource, currentSortField, '')); } else { - sortBy = resource => sorts.string(_.get(resource, currentSortField, '')); + sortBy = (resource) => sorts.string(_.get(resource, currentSortField, '')); } } else if (currentSortFunc && sorts[currentSortFunc]) { // Sort resources by a function in the 'sorts' object @@ -148,7 +158,11 @@ const stateToProps = ({UI}, { } // Always set the secondary sort criteria to ascending by name - newData = _.orderBy(newData, [sortBy, 'metadata.name'], [currentSortOrder, SortByDirection.asc]); + newData = _.orderBy( + newData, + [sortBy, 'metadata.name'], + [currentSortOrder, SortByDirection.asc], + ); } return { @@ -162,9 +176,25 @@ const stateToProps = ({UI}, { }; // Common table row/columns helper SFCs for implementing accessible data grid -export const TableRow: React.SFC = ({id, index, trKey, style, className, ...props}) => { +export const TableRow: React.SFC = ({ + id, + index, + trKey, + style, + className, + ...props +}) => { return ( -

    + ); }; TableRow.displayName = 'TableRow'; @@ -174,20 +204,23 @@ export type TableRowProps = { trKey: string; style: object; className?: string; -} +}; -export const TableData: React.SFC = ({className, ...props}) => { - return ( -
    - ); +export const TableData: React.SFC = ({ className, ...props }) => { + return ; }; TableData.displayName = 'TableData'; export type TableDataProps = { id?: string; className?: string; -} +}; -const TableWrapper: React.SFC = ({virtualize, ariaLabel, ariaRowCount, ...props}) => { +const TableWrapper: React.SFC = ({ + virtualize, + ariaLabel, + ariaRowCount, + ...props +}) => { return virtualize ? (
    ) : ( @@ -198,31 +231,54 @@ export type TableWrapperProps = { virtualize: boolean; ariaLabel: string; ariaRowCount: number | undefined; -} +}; const VirtualBody: React.SFC = (props) => { - const { customData, Row, height, isScrolling, onChildScroll, data, columns, scrollTop, width } = props; + const { + customData, + Row, + height, + isScrolling, + onChildScroll, + data, + columns, + scrollTop, + width, + } = props; const cellMeasurementCache = new CellMeasurerCache({ fixedWidth: true, minHeight: 44, - keyMapper: rowIndex => _.get(props.data[rowIndex], 'metadata.uid', rowIndex), + keyMapper: (rowIndex) => _.get(props.data[rowIndex], 'metadata.uid', rowIndex), }); - const rowRenderer = ({index, isScrolling: scrolling, isVisible, key, style, parent}) => { - const rowArgs = {obj: data[index], index, columns, isScrolling: scrolling, key, style, customData}; + const rowRenderer = ({ index, isScrolling: scrolling, isVisible, key, style, parent }) => { + const rowArgs = { + obj: data[index], + index, + columns, + isScrolling: scrolling, + key, + style, + customData, + }; const row = (Row as RowFunction)(rowArgs as RowFunctionArgs); // do not render non visible elements (this excludes overscan) - if (!isVisible){ + if (!isVisible) { return null; } - return {row}; + return ( + + {row} + + ); }; return ( @@ -245,7 +301,15 @@ const VirtualBody: React.SFC = (props) => { ); }; -export type RowFunctionArgs = {obj: object, index: number, columns: [], isScrolling: boolean, key: string, style: object, customData?: any}; +export type RowFunctionArgs = { + obj: object; + index: number; + columns: []; + isScrolling: boolean; + key: string; + style: object; + customData?: any; +}; export type RowFunction = (args: RowFunctionArgs) => JSX.Element; export type VirtualBodyProps = { @@ -259,18 +323,18 @@ export type VirtualBodyProps = { scrollTop: number; width: number; expand: boolean; -} +}; export type TableProps = { customData?: any; data?: any[]; defaultSortFunc?: string; defaultSortField?: string; - filters?: {[key: string]: any}; + filters?: { [key: string]: any }; Header: (...args) => any[]; loadError?: string | Object; Row?: RowFunction | React.ComponentClass | React.ComponentType; - Rows?: (...args)=> any[]; + Rows?: (...args) => any[]; 'aria-label': string; virtualize?: boolean; AllItemsFilteredMsg?: React.ComponentType<{}>; @@ -278,7 +342,7 @@ export type TableProps = { loaded?: boolean; reduxID?: string; reduxIDs?: string[]; -} +}; type TablePropsFromState = {}; @@ -286,13 +350,20 @@ type TablePropsFromDispatch = {}; type TableOptionProps = { UI: any; -} +}; -export const Table = connect( +export const Table = connect< + TablePropsFromState, + TablePropsFromDispatch, + TableProps, + TableOptionProps +>( stateToProps, - {sortList: UIActions.sortList}, + { sortList: UIActions.sortList }, null, - {areStatesEqual: ({UI: next}, {UI: prev}) => next.get('listSorts') === prev.get('listSorts')} + { + areStatesEqual: ({ UI: next }, { UI: prev }) => next.get('listSorts') === prev.get('listSorts'), + }, )( class TableInner extends React.Component { static propTypes = { @@ -329,11 +400,17 @@ export const Table = connect -1){ + if (columnIndex > -1) { sortBy = { index: columnIndex + this._columnShift, direction: currentSortOrder }; } } else if (currentSortFunc && currentSortOrder) { const columnIndex = _.findIndex(columns, { sortFunc: currentSortFunc }); - if (columnIndex > -1){ + if (columnIndex > -1) { sortBy = { index: columnIndex + this._columnShift, direction: currentSortOrder }; } } this.state = { columns, sortBy }; } - componentDidMount(){ - const {columns} = this.state; + componentDidMount() { + const { columns } = this.state; const sp = new URLSearchParams(window.location.search); - const columnIndex = _.findIndex(columns, {title: sp.get('sortBy')}); + const columnIndex = _.findIndex(columns, { title: sp.get('sortBy') }); - if (columnIndex > -1){ + if (columnIndex > -1) { const sortOrder = sp.get('orderBy') || SortByDirection.asc; const column = columns[columnIndex]; - this._applySort(column.sortField, column.sortFunc, column.sortAsNumber, sortOrder, column.title); + this._applySort( + column.sortField, + column.sortFunc, + column.sortAsNumber, + sortOrder, + column.title, + ); this.setState({ sortBy: { index: columnIndex + this._columnShift, @@ -376,7 +459,7 @@ export const Table = connect ( - {({height, isScrolling, registerChild, onChildScroll, scrollTop}) => ( + {({ height, isScrolling, registerChild, onChildScroll, scrollTop }) => ( - {({width}) => ( + {({ width }) => (
    ); - const children = mock ? : ( + const children = mock ? ( + + ) : ( - {!virtualize && ( - - )} + {!virtualize && } {virtualize && (scrollNode ? ( @@ -458,14 +566,20 @@ export const Table = connect ); - return
    - { mock - ? children - : } {...this.props}>{children} } -
    ; + return ( +
    + {mock ? ( + children + ) : ( + } {...this.props}> + {children} + + )} +
    + ); } - }); - + }, +); export type TableInnerProps = { 'aria-label': string; @@ -481,7 +595,7 @@ export type TableInnerProps = { EmptyMsg?: React.ComponentType<{}>; expand?: boolean; fieldSelector?: string; - filters?: {[name: string]: any}; + filters?: { [name: string]: any }; Header: (...args) => any[]; label?: string; listId?: string; @@ -492,11 +606,24 @@ export type TableInnerProps = { reduxID?: string; reduxIDs?: string[]; Row?: RowFunction | React.ComponentClass | React.ComponentType; - Rows?: (...args)=> any[]; + Rows?: (...args) => any[]; selector?: Object; - sortList?: (listId: string, field: string, func: any, sortAsNumber: boolean, orderBy: string, column: string) => any; + sortList?: ( + listId: string, + field: string, + func: any, + sortAsNumber: boolean, + orderBy: string, + column: string, + ) => any; selectedResourcesForKind?: string[]; - onSelect?: (event: React.MouseEvent, isSelected: boolean, rowIndex: number, rowData: IRowData, extraData: IExtraData) => void; + onSelect?: ( + event: React.MouseEvent, + isSelected: boolean, + rowIndex: number, + rowData: IRowData, + extraData: IExtraData, + ) => void; staticFilters?: any[]; rowFilters?: any[]; virtualize?: boolean; diff --git a/frontend/public/components/global-notifications.jsx b/frontend/public/components/global-notifications.jsx index a6ff9a46400..a8db724ba5a 100644 --- a/frontend/public/components/global-notifications.jsx +++ b/frontend/public/components/global-notifications.jsx @@ -3,7 +3,9 @@ import * as React from 'react'; import { ImpersonateNotifier } from './impersonate-notifier'; import { KubeAdminNotifier } from './kube-admin-notifier'; -export const GlobalNotifications = () =>
    - - -
    ; +export const GlobalNotifications = () => ( +
    + + +
    +); diff --git a/frontend/public/components/graphs/area.tsx b/frontend/public/components/graphs/area.tsx index 03912b5f4c4..32ff8f14201 100644 --- a/frontend/public/components/graphs/area.tsx +++ b/frontend/public/components/graphs/area.tsx @@ -40,7 +40,7 @@ export const AreaChart: React.FC = ({ yAxis = true, }) => { const [containerRef, width] = useRefWidth(); - const getLabel = ({ datum: { x, y }}) => `${humanize(y).string} at ${formatDate(x)}`; + const getLabel = ({ datum: { x, y } }) => `${humanize(y).string} at ${formatDate(x)}`; const container = ; return ( @@ -48,15 +48,21 @@ export const AreaChart: React.FC = ({ {xAxis && } - {yAxis && humanize(tick).string} />} + {yAxis && ( + humanize(tick).string} + /> + )} @@ -75,7 +81,7 @@ export const Area: React.FC = ({ timespan = DEFAULT_TIMESPAN, ...rest }) => { - const [response,, loading] = usePrometheusPoll({ + const [response, , loading] = usePrometheusPoll({ endpoint: PrometheusEndpoint.QUERY_RANGE, namespace, query, @@ -91,7 +97,7 @@ type AreaChartProps = { className?: string; formatDate?: (date: Date) => string; humanize?: Humanize; - height?: number, + height?: number; loading?: boolean; query?: string; theme?: any; // TODO figure out the best way to import VictoryThemeDefinition @@ -101,7 +107,7 @@ type AreaChartProps = { xAxis?: boolean; yAxis?: boolean; padding?: object; -} +}; type AreaProps = AreaChartProps & { namespace?: string; @@ -109,4 +115,4 @@ type AreaProps = AreaChartProps & { samples?: number; timeout?: string; timespan?: number; -} +}; diff --git a/frontend/public/components/graphs/bar.tsx b/frontend/public/components/graphs/bar.tsx index b28410592bb..ad79fbe94e5 100644 --- a/frontend/public/components/graphs/bar.tsx +++ b/frontend/public/components/graphs/bar.tsx @@ -43,35 +43,37 @@ export const BarChart: React.FC = ({ }; return ( - - { - data.length ? ( - - {data.map((datum, index) => ( - -
    - {LabelComponent ? : datum.x} -
    -
    - } - theme={theme} - height={barWidth + padding.bottom} - width={width} - domain={{y: [0, data[0].y]}} - padding={padding} - /> -
    -
    - ))} -
    - ) : ( - - ) - } + + {data.length ? ( + + {data.map((datum, index) => ( + +
    + {LabelComponent ? ( + + ) : ( + datum.x + )} +
    +
    + } + theme={theme} + height={barWidth + padding.bottom} + width={width} + domain={{ y: [0, data[0].y] }} + padding={padding} + /> +
    +
    + ))} +
    + ) : ( + + )}
    ); }; @@ -85,7 +87,11 @@ export const Bar: React.FC = ({ query, title, }) => { - const [response,, loading] = usePrometheusPoll({ endpoint: PrometheusEndpoint.QUERY, namespace, query }); + const [response, , loading] = usePrometheusPoll({ + endpoint: PrometheusEndpoint.QUERY, + namespace, + query, + }); const data = getInstantVectorStats(response, metric, humanize); return ( @@ -102,8 +108,8 @@ export const Bar: React.FC = ({ type LabelComponentProps = { title: Date | string | number; - metric?: {[key: string]: string}; -} + metric?: { [key: string]: string }; +}; type BarChartProps = { LabelComponent?: React.ComponentType; @@ -114,7 +120,7 @@ type BarChartProps = { data?: DataPoint[]; titleClassName?: string; loading?: boolean; -} +}; type BarProps = { humanize?: Humanize; @@ -125,4 +131,4 @@ type BarProps = { theme?: any; // TODO figure out the best way to import VictoryThemeDefinition title?: string; titleClassName: string; -} +}; diff --git a/frontend/public/components/graphs/gauge.tsx b/frontend/public/components/graphs/gauge.tsx index 9c06b1a0e5e..6f3acc02fcd 100644 --- a/frontend/public/components/graphs/gauge.tsx +++ b/frontend/public/components/graphs/gauge.tsx @@ -1,5 +1,9 @@ import * as React from 'react'; -import { ChartDonutThreshold, ChartDonutUtilization, ChartThemeColor } from '@patternfly/react-charts'; +import { + ChartDonutThreshold, + ChartDonutUtilization, + ChartThemeColor, +} from '@patternfly/react-charts'; import classNames from 'classnames'; import { PrometheusGraph, PrometheusGraphLink } from './prometheus-graph'; @@ -31,7 +35,7 @@ export const GaugeChart: React.FC = ({ const [ref, width] = useRefWidth(); const ready = !error && !loading; const status = loading ? 'Loading' : error; - const labels = ({ datum: { x, y }}) => x ? `${x} ${usedLabel}` : `${y} ${remainderLabel}`; + const labels = ({ datum: { x, y } }) => (x ? `${x} ${usedLabel}` : `${y} ${remainderLabel}`); return ( = ({ query, }); - const [data] = response ? ( - getInstantVectorStats(response, null, humanize).map(({ label, y }) => ({ x: label, y })) - ) : ( - [{ x: humanize(percent).string, y: percent }] + const [data] = response + ? getInstantVectorStats(response, null, humanize).map(({ label, y }) => ({ x: label, y })) + : [{ x: humanize(percent).string, y: percent }]; + return ( + ); - return ; }; type GaugeChartProps = { @@ -119,20 +123,20 @@ type GaugeChartProps = { title?: string; usedLabel?: string; className?: string; -} +}; type GaugeProps = { humanize?: Humanize; invert?: boolean; namespace?: string; percent?: number; - query?: string, - remainderLabel?: string, - secondaryTitle?: string, + query?: string; + remainderLabel?: string; + secondaryTitle?: string; thresholds?: { value: number; color?: string; }[]; - title?: string, - usedLabel?: string, -} + title?: string; + usedLabel?: string; +}; diff --git a/frontend/public/components/graphs/graph-empty.tsx b/frontend/public/components/graphs/graph-empty.tsx index 4eb31b1d732..b1ed46d51c0 100644 --- a/frontend/public/components/graphs/graph-empty.tsx +++ b/frontend/public/components/graphs/graph-empty.tsx @@ -3,19 +3,28 @@ import { EmptyState, EmptyStateVariant, Title } from '@patternfly/react-core'; import { LoadingBox } from '../utils'; export const GraphEmpty: React.FC = ({ height = 180, loading = false }) => ( -
    +
    {loading ? ( ) : ( - - No datapoints found. + + + No datapoints found. + - ) - } + )}
    ); type GraphEmptyProps = { height?: number | string; loading?: boolean; -} +}; diff --git a/frontend/public/components/graphs/health.jsx b/frontend/public/components/graphs/health.jsx index d501365c87e..d622e31bdd1 100644 --- a/frontend/public/components/graphs/health.jsx +++ b/frontend/public/components/graphs/health.jsx @@ -10,59 +10,78 @@ import { k8sBasePath } from '../../module/k8s'; // Use the shorter 'OpenShift Console' instead of 'OpenShift Container Platform Console' since the title appears in the chart. const consoleName = window.SERVER_FLAGS.branding === 'okd' ? 'OKD Console' : 'OpenShift Console'; -const fetchHealth = () => coFetch(`${k8sBasePath}/healthz`) - .then(response => response.text()) - .then(body => { - if (body === 'ok') { - return {short: 'UP', long: 'All good', status: 'OK'}; - } - return {short: 'ERROR', long: body, status: 'ERROR'}; - }) - .catch(errorStatus); +const fetchHealth = () => + coFetch(`${k8sBasePath}/healthz`) + .then((response) => response.text()) + .then((body) => { + if (body === 'ok') { + return { short: 'UP', long: 'All good', status: 'OK' }; + } + return { short: 'ERROR', long: body, status: 'ERROR' }; + }) + .catch(errorStatus); -const fetchConsoleHealth = () => coFetchJSON('health') - .then(() => ({short: 'UP', long: 'All good', status: 'OK'})) - .catch(() => ({short: 'ERROR', long: 'The console service cannot be reached', status: 'ERROR'})); +const fetchConsoleHealth = () => + coFetchJSON('health') + .then(() => ({ short: 'UP', long: 'All good', status: 'OK' })) + .catch(() => ({ + short: 'ERROR', + long: 'The console service cannot be reached', + status: 'ERROR', + })); export const KubernetesHealth = () => ; export const ConsoleHealth = () => ; -const alertsFiringStateToProps = (state) => ({canAccessMonitoring: !!state[featureReducerName].get(FLAGS.CAN_GET_NS)}); +const alertsFiringStateToProps = (state) => ({ + canAccessMonitoring: !!state[featureReducerName].get(FLAGS.CAN_GET_NS), +}); -const AlertsFiring_ = ({canAccessMonitoring, namespace}) => { - const toProp = canAccessMonitoring && !!window.SERVER_FLAGS.prometheusBaseURL ? {to: '/monitoring'} : {}; - return ; +const AlertsFiring_ = ({ canAccessMonitoring, namespace }) => { + const toProp = + canAccessMonitoring && !!window.SERVER_FLAGS.prometheusBaseURL ? { to: '/monitoring' } : {}; + return ( + + ); }; const AlertsFiring = connect(alertsFiringStateToProps)(AlertsFiring_); -const CrashloopingPods = ({namespace}) => ( +const CrashloopingPods = ({ namespace }) => ( 5 )`} - to={`/k8s/${namespace ? `ns/${namespace}` : 'all-namespaces'}/pods?rowFilter-pod-status=CrashLoopBackOff`} + query={`count(increase(kube_pod_container_status_restarts_total${ + namespace ? `{namespace="${namespace}"}` : '' + }[1h]) > 5 )`} + to={`/k8s/${ + namespace ? `ns/${namespace}` : 'all-namespaces' + }/pods?rowFilter-pod-status=CrashLoopBackOff`} /> ); -export const Health = ({namespace}) =>
    -
    - -
    -
    - -
    -
    - +export const Health = ({ namespace }) => ( +
    +
    + +
    +
    + +
    +
    + +
    +
    + +
    -
    - -
    -
    ; +); diff --git a/frontend/public/components/graphs/helpers.ts b/frontend/public/components/graphs/helpers.ts index afe7fa3fcc7..d0ab2926413 100644 --- a/frontend/public/components/graphs/helpers.ts +++ b/frontend/public/components/graphs/helpers.ts @@ -1,6 +1,6 @@ import * as _ from 'lodash-es'; -import {PROMETHEUS_BASE_PATH, PROMETHEUS_TENANCY_BASE_PATH} from './index'; +import { PROMETHEUS_BASE_PATH, PROMETHEUS_TENANCY_BASE_PATH } from './index'; export enum PrometheusEndpoint { LABEL = 'api/v1/label', @@ -9,7 +9,11 @@ export enum PrometheusEndpoint { } // Range vector queries require end, start, and step search params -const getRangeVectorSearchParams = (timespan: number, endTime: number = Date.now(), samples: number = 60): URLSearchParams => { +const getRangeVectorSearchParams = ( + timespan: number, + endTime: number = Date.now(), + samples: number = 60, +): URLSearchParams => { const params = new URLSearchParams(); if (timespan > 0) { params.append('start', `${(endTime - timespan) / 1000}`); @@ -19,15 +23,25 @@ const getRangeVectorSearchParams = (timespan: number, endTime: number = Date.now return params; }; -const getSearchParams = ({endpoint, endTime, timespan, samples, ...params}: PrometheusURLProps): URLSearchParams => { - const searchParams = endpoint === PrometheusEndpoint.QUERY_RANGE - ? getRangeVectorSearchParams(timespan, endTime, samples) - : new URLSearchParams(); +const getSearchParams = ({ + endpoint, + endTime, + timespan, + samples, + ...params +}: PrometheusURLProps): URLSearchParams => { + const searchParams = + endpoint === PrometheusEndpoint.QUERY_RANGE + ? getRangeVectorSearchParams(timespan, endTime, samples) + : new URLSearchParams(); _.each(params, (value, key) => value && searchParams.append(key, value.toString())); return searchParams; }; -export const getPrometheusURL = (props: PrometheusURLProps, basePath: string = props.namespace ? PROMETHEUS_TENANCY_BASE_PATH : PROMETHEUS_BASE_PATH): string => { +export const getPrometheusURL = ( + props: PrometheusURLProps, + basePath: string = props.namespace ? PROMETHEUS_TENANCY_BASE_PATH : PROMETHEUS_BASE_PATH, +): string => { if (!props.query) { return ''; } diff --git a/frontend/public/components/graphs/index.tsx b/frontend/public/components/graphs/index.tsx index a43d1bac199..73fcf840741 100644 --- a/frontend/public/components/graphs/index.tsx +++ b/frontend/public/components/graphs/index.tsx @@ -9,35 +9,43 @@ export const ALERT_MANAGER_BASE_PATH = window.SERVER_FLAGS.alertManagerBaseURL; // Components export * from './require-prometheus'; export { errorStatus, Status } from './status'; -export const Area = props => import('./graph-loader').then(c => c.Area)} {...props} />; -export const Bar = props => import('./graph-loader').then(c => c.Bar)} {...props} />; -export const Gauge = props => import('./graph-loader').then(c => c.Gauge)} {...props} />; +export const Area = (props) => ( + import('./graph-loader').then((c) => c.Area)} {...props} /> +); +export const Bar = (props) => ( + import('./graph-loader').then((c) => c.Bar)} {...props} /> +); +export const Gauge = (props) => ( + import('./graph-loader').then((c) => c.Gauge)} {...props} /> +); // Types export type DataPoint = { x?: Date | string | number; y?: number; label?: string; - metric?: {[key: string]: string}; -} + metric?: { [key: string]: string }; +}; // Only covers range and instant vector responses for now. export type PrometheusResponse = { status: string; data: { - resultType: 'matrix' | 'vector' | 'scalar' | 'string', + resultType: 'matrix' | 'vector' | 'scalar' | 'string'; result: { - metric: {[key: string]: any} + metric: { [key: string]: any }; values?: [number, string | number][]; value?: [number, string | number]; - }[] - }, + }[]; + }; errorType?: string; error?: string; warnings?: string[]; -} +}; -export type DomainPadding = number | { - x?: number | [number, number]; - y?: number | [number, number]; -} +export type DomainPadding = + | number + | { + x?: number | [number, number]; + y?: number | [number, number]; + }; diff --git a/frontend/public/components/graphs/prometheus-graph.tsx b/frontend/public/components/graphs/prometheus-graph.tsx index 8b4a59ddc41..6444a5b6ebd 100644 --- a/frontend/public/components/graphs/prometheus-graph.tsx +++ b/frontend/public/components/graphs/prometheus-graph.tsx @@ -19,21 +19,30 @@ export const getPrometheusExpressionBrowserURL = (urls, queries): string => { }; export const PrometheusGraphLink = connectToURLs(MonitoringRoutes.Prometheus)( - ({children, query, urls}: React.PropsWithChildren) => { + ({ children, query, urls }: React.PropsWithChildren) => { const url = getPrometheusExpressionBrowserURL(urls, [query]); - return query - ? {children} - : {children}; - } + return query ? ( + + {children} + + ) : ( + {children} + ); + }, ); export const PrometheusGraph: React.FC = React.forwardRef( - ({children, className, title}, ref: React.RefObject) => ( + ({ children, className, title }, ref: React.RefObject) => (
    {title &&
    {title}
    } {children}
    - ) + ), ); type PrometheusGraphLinkProps = { @@ -45,4 +54,4 @@ type PrometheusGraphProps = { className?: string; ref?: React.Ref; title?: string; -} +}; diff --git a/frontend/public/components/graphs/prometheus-poll-hook.ts b/frontend/public/components/graphs/prometheus-poll-hook.ts index a14129c1c2b..21e98b3e0ff 100644 --- a/frontend/public/components/graphs/prometheus-poll-hook.ts +++ b/frontend/public/components/graphs/prometheus-poll-hook.ts @@ -27,11 +27,11 @@ export const usePrometheusPoll = ({ const tick = useCallback(() => { if (url) { safeFetch(url) - .then(data => { + .then((data) => { setResponse(data); setError(undefined); }) - .catch(err => { + .catch((err) => { setError(err); // eslint-disable-next-line no-console console.error(`Error polling Prometheus: ${err}`); @@ -56,4 +56,4 @@ type PrometheusPollProps = { samples?: number; timeout?: string; timespan?: number; -} +}; diff --git a/frontend/public/components/graphs/require-prometheus.tsx b/frontend/public/components/graphs/require-prometheus.tsx index be4e8fa9051..f2d29ea100a 100644 --- a/frontend/public/components/graphs/require-prometheus.tsx +++ b/frontend/public/components/graphs/require-prometheus.tsx @@ -4,17 +4,17 @@ import { FLAGS } from '../../const'; import { connectToFlags } from '../../reducers/features'; import { PROMETHEUS_BASE_PATH, PROMETHEUS_TENANCY_BASE_PATH } from '.'; -const canAccessPrometheus = (prometheusFlag) => prometheusFlag && !!PROMETHEUS_BASE_PATH && !!PROMETHEUS_TENANCY_BASE_PATH; +const canAccessPrometheus = (prometheusFlag) => + prometheusFlag && !!PROMETHEUS_BASE_PATH && !!PROMETHEUS_TENANCY_BASE_PATH; // HOC that will hide WrappedComponent when Prometheus isn't configured or the user doesn't have permission to query Prometheus. // TODO Figure out better typing here -export const requirePrometheus = (WrappedComponent) => connectToFlags(FLAGS.PROMETHEUS)( - ({ flags, ...rest }) => { +export const requirePrometheus = (WrappedComponent) => + connectToFlags(FLAGS.PROMETHEUS)(({ flags, ...rest }) => { const prometheusFlag = flags[FLAGS.PROMETHEUS]; if (!canAccessPrometheus(prometheusFlag)) { return null; } return ; - } -); + }); diff --git a/frontend/public/components/graphs/status.jsx b/frontend/public/components/graphs/status.jsx index 06a60c35573..360b95d3a59 100644 --- a/frontend/public/components/graphs/status.jsx +++ b/frontend/public/components/graphs/status.jsx @@ -6,7 +6,7 @@ import { Link } from 'react-router-dom'; import { coFetchJSON } from '../../co-fetch'; import { PROMETHEUS_BASE_PATH, PROMETHEUS_TENANCY_BASE_PATH } from '.'; -export const errorStatus = err => { +export const errorStatus = (err) => { if (_.get(err.response, 'ok') === false) { return { short: '?', @@ -26,7 +26,7 @@ const fetchQuery = (q, long, namespace) => { const nsParam = namespace ? `&namespace=${encodeURIComponent(namespace)}` : ''; const basePath = namespace ? PROMETHEUS_TENANCY_BASE_PATH : PROMETHEUS_BASE_PATH; return coFetchJSON(`${basePath}/api/v1/query?query=${encodeURIComponent(q)}${nsParam}`) - .then(res => { + .then((res) => { const short = parseInt(_.get(res, 'data.result[0].value[1]'), 10) || 0; return { short, @@ -48,24 +48,31 @@ export class Status extends React.Component { this.clock = 0; } - fetch(props=this.props) { + fetch(props = this.props) { const clock = this.clock; - const promise = props.query ? fetchQuery(props.query, props.name, props.namespace) : props.fetch(); + const promise = props.query + ? fetchQuery(props.query, props.name, props.namespace) + : props.fetch(); - const ignorePromise = cb => (...args) => { + const ignorePromise = (cb) => (...args) => { if (clock !== this.clock) { return; } cb(...args); }; promise - .then(ignorePromise(({short, long, status}) => this.setState({short, long, status}))) - .catch(ignorePromise(() => this.setState({short: 'BAD', long: 'Error', status: 'ERROR'}))) - .then(ignorePromise(() => this.interval = setTimeout(() => { - if (this.isMounted_) { - this.fetch(); - } - }, 30000))); + .then(ignorePromise(({ short, long, status }) => this.setState({ short, long, status }))) + .catch(ignorePromise(() => this.setState({ short: 'BAD', long: 'Error', status: 'ERROR' }))) + .then( + ignorePromise( + () => + (this.interval = setTimeout(() => { + if (this.isMounted_) { + this.fetch(); + } + }, 30000)), + ), + ); } componentWillReceiveProps(nextProps) { @@ -75,9 +82,9 @@ export class Status extends React.Component { this.clock += 1; // Don't show stale data if we changed the query. this.setState({ - 'status': '...', - 'short': undefined, - 'long': undefined, + status: '...', + short: undefined, + long: undefined, }); this.fetch(nextProps); } @@ -100,17 +107,23 @@ export class Status extends React.Component { 'graph-status__short--error': status === 'ERROR', }); - const statusElem =
    - { title &&
    {title}
    } -
    -

    {short}

    -
    {long}
    + const statusElem = ( +
    + {title &&
    {title}
    } +
    +

    {short}

    +
    {long}
    +
    -
    ; + ); const linkProps = _.pick(this.props, ['rel', 'target', 'to']); if (_.isEmpty(linkProps)) { return statusElem; } - return {statusElem}; + return ( + + {statusElem} + + ); } } diff --git a/frontend/public/components/graphs/themes.ts b/frontend/public/components/graphs/themes.ts index 071a1d0301a..3fe41394222 100644 --- a/frontend/public/components/graphs/themes.ts +++ b/frontend/public/components/graphs/themes.ts @@ -2,7 +2,7 @@ export const areaTheme = { area: { style: { data: { - fillOpacity: .15, + fillOpacity: 0.15, }, }, }, @@ -16,7 +16,7 @@ export const areaTheme = { }, dependentAxis: { style: { - grid: {stroke: '#EDEDED'}, + grid: { stroke: '#EDEDED' }, }, }, }; diff --git a/frontend/public/components/graphs/utils.ts b/frontend/public/components/graphs/utils.ts index 95d7c7bbddb..65525445936 100644 --- a/frontend/public/components/graphs/utils.ts +++ b/frontend/public/components/graphs/utils.ts @@ -3,9 +3,9 @@ import * as _ from 'lodash-es'; import { PrometheusResponse, DataPoint } from '.'; import { Humanize } from '../utils'; -export const getRangeVectorStats: GetStats = response => { +export const getRangeVectorStats: GetStats = (response) => { const values = _.get(response, 'data.result[0].values'); - return _.map(values, value => ({ + return _.map(values, (value) => ({ x: new Date(value[0] * 1000), y: parseFloat(value[1]), })); @@ -13,7 +13,7 @@ export const getRangeVectorStats: GetStats = response => { export const getInstantVectorStats: GetStats = (response, metric, humanize) => { const results = _.get(response, 'data.result', []); - return results.map(r => { + return results.map((r) => { const y = parseFloat(_.get(r, 'value[1]')); return { label: humanize ? humanize(y).string : null, @@ -26,4 +26,4 @@ export const getInstantVectorStats: GetStats = (response, metric, humanize) => { export type GetStats = { (response: PrometheusResponse, metric?: string, humanize?: Humanize): DataPoint[]; -} +}; diff --git a/frontend/public/components/hpa.tsx b/frontend/public/components/hpa.tsx index 0cf4094a98c..9f28bd739c8 100644 --- a/frontend/public/components/hpa.tsx +++ b/frontend/public/components/hpa.tsx @@ -6,7 +6,16 @@ import { K8sResourceKind, K8sResourceKindReference } from '../module/k8s'; import { HorizontalPodAutoscalerModel } from '../models'; import { Conditions } from './conditions'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; -import { Kebab, SectionHeading, LabelList, navFactory, ResourceKebab, ResourceLink, ResourceSummary, Timestamp } from './utils'; +import { + Kebab, + SectionHeading, + LabelList, + navFactory, + ResourceKebab, + ResourceLink, + ResourceSummary, + Timestamp, +} from './utils'; import { ResourceEventStream } from './events'; const HorizontalPodAutoscalersReference: K8sResourceKindReference = 'HorizontalPodAutoscaler'; @@ -14,17 +23,13 @@ const HorizontalPodAutoscalersReference: K8sResourceKindReference = 'HorizontalP const { common } = Kebab.factory; const menuActions = [...Kebab.getExtensionsActionsForKind(HorizontalPodAutoscalerModel), ...common]; -const MetricsRow: React.SFC = ({type, current, target}) =>
    -
    - {type} +const MetricsRow: React.SFC = ({ type, current, target }) => ( +
    +
    {type}
    +
    {current || '-'}
    +
    {target || '-'}
    -
    - {current || '-'} -
    -
    - {target || '-'} -
    -
    ; +); const externalRow = (metric, current, key) => { const { external } = metric; @@ -42,10 +47,17 @@ const externalRow = (metric, current, key) => { const objectRow = (metric, current, ns, key) => { const { object } = metric; - const type = - {object.metricName} on - - ; + const type = ( + + {object.metricName} on + + + ); const currentValue = _.get(current, 'object.currentValue'); const targetValue = object.targetValue; @@ -61,7 +73,7 @@ const podRow = (metric, current, key) => { return ; }; -const getResourceUtilization = currentMetric => { +const getResourceUtilization = (currentMetric) => { const currentUtilization = _.get(currentMetric, 'resource.currentAverageUtilization'); // Use _.isFinite so that 0 evaluates to true, but null / undefined / NaN don't @@ -79,9 +91,13 @@ const getResourceUtilization = currentMetric => { const resourceRow = (metric, current, key) => { const targetUtilization = metric.resource.targetAverageUtilization; const resourceLabel = `resource ${metric.resource.name}`; - const type = targetUtilization - ? {resourceLabel} (as a percentage of request) - : resourceLabel; + const type = targetUtilization ? ( + + {resourceLabel} (as a percentage of request) + + ) : ( + resourceLabel + ); const currentValue = targetUtilization ? getResourceUtilization(current) : _.get(current, 'resource.currentAverageValue'); @@ -92,84 +108,107 @@ const resourceRow = (metric, current, key) => { return ; }; -const MetricsTable: React.SFC = ({obj: hpa}) => { - return - -
    -
    -
    Type
    -
    Current
    -
    Target
    -
    -
    - {hpa.spec.metrics.map((metric, i) => { - // https://github.com/kubernetes/api/blob/master/autoscaling/v2beta1/types.go - const current = _.get(hpa, ['status', 'currentMetrics', i]); - switch (metric.type) { - case 'External': - return externalRow(metric, current, i); - case 'Object': - return objectRow(metric, current, hpa.metadata.namespace, i); - case 'Pods': - return podRow(metric, current, i); - case 'Resource': - return resourceRow(metric, current, i); - default: - return
    -
    - {metric.type} (unrecognized type) -
    -
    ; - } - })} +const MetricsTable: React.SFC = ({ obj: hpa }) => { + return ( + + +
    +
    +
    Type
    +
    Current
    +
    Target
    +
    +
    + {hpa.spec.metrics.map((metric, i) => { + // https://github.com/kubernetes/api/blob/master/autoscaling/v2beta1/types.go + const current = _.get(hpa, ['status', 'currentMetrics', i]); + switch (metric.type) { + case 'External': + return externalRow(metric, current, i); + case 'Object': + return objectRow(metric, current, hpa.metadata.namespace, i); + case 'Pods': + return podRow(metric, current, i); + case 'Resource': + return resourceRow(metric, current, i); + default: + return ( +
    +
    + {metric.type} (unrecognized type) +
    +
    + ); + } + })} +
    -
    - ; + + ); }; -export const HorizontalPodAutoscalersDetails: React.SFC = ({obj: hpa}) => -
    - -
    -
    - -
    -
    -
    -
    Scale Target
    -
    - -
    -
    Min Pods
    -
    {hpa.spec.minReplicas}
    -
    Max Pods
    -
    {hpa.spec.maxReplicas}
    -
    Last Scale Time
    -
    -
    Current Pods
    -
    {hpa.status.currentReplicas}
    -
    Desired Pods
    -
    {hpa.status.desiredReplicas}
    -
    +export const HorizontalPodAutoscalersDetails: React.SFC = ({ + obj: hpa, +}) => ( + +
    + +
    +
    + +
    +
    +
    +
    Scale Target
    +
    + +
    +
    Min Pods
    +
    {hpa.spec.minReplicas}
    +
    Max Pods
    +
    {hpa.spec.maxReplicas}
    +
    Last Scale Time
    +
    + +
    +
    Current Pods
    +
    {hpa.status.currentReplicas}
    +
    Desired Pods
    +
    {hpa.status.desiredReplicas}
    +
    +
    -
    -
    - -
    -
    - - -
    -; +
    + +
    +
    + + +
    + +); -const pages = [navFactory.details(HorizontalPodAutoscalersDetails), navFactory.editYaml(), navFactory.events(ResourceEventStream)]; -export const HorizontalPodAutoscalersDetailsPage: React.SFC = props => +const pages = [ + navFactory.details(HorizontalPodAutoscalersDetails), + navFactory.editYaml(), + navFactory.events(ResourceEventStream), +]; +export const HorizontalPodAutoscalersDetailsPage: React.SFC< + HorizontalPodAutoscalersDetailsPageProps +> = (props) => ( ; + pages={pages} + /> +); HorizontalPodAutoscalersDetailsPage.displayName = 'HorizontalPodAutoscalersDetailsPage'; const tableColumnClasses = [ @@ -187,59 +226,91 @@ const kind = 'HorizontalPodAutoscaler'; const HorizontalPodAutoscalersTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Scale Target', sortField: 'spec.scaleTargetRef.name', transforms: [sortable], + title: 'Scale Target', + sortField: 'spec.scaleTargetRef.name', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Min Pods', sortField: 'spec.minReplicas', transforms: [sortable], + title: 'Min Pods', + sortField: 'spec.minReplicas', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, { - title: 'Max Pods', sortField: 'spec.maxReplicas', transforms: [sortable], + title: 'Max Pods', + sortField: 'spec.maxReplicas', + transforms: [sortable], props: { className: tableColumnClasses[5] }, }, { - title: '', props: { className: tableColumnClasses[6] }, + title: '', + props: { className: tableColumnClasses[6] }, }, ]; }; HorizontalPodAutoscalersTableHeader.displayName = 'HorizontalPodAutoscalersTableHeader'; -const HorizontalPodAutoscalersTableRow: React.FC = ({obj, index, key, style}) => { +const HorizontalPodAutoscalersTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + - + - - - - {obj.spec.minReplicas} - - - {obj.spec.maxReplicas} + + {obj.spec.minReplicas} + {obj.spec.maxReplicas} - + ); @@ -252,38 +323,49 @@ type HorizontalPodAutoscalersTableRowProps = { style: object; }; -const HorizontalPodAutoscalersList: React.SFC = props => ; +const HorizontalPodAutoscalersList: React.SFC = (props) => ( +
    +); HorizontalPodAutoscalersList.displayName = 'HorizontalPodAutoscalersList'; -export const HorizontalPodAutoscalersPage: React.SFC = props => +export const HorizontalPodAutoscalersPage: React.SFC = ( + props, +) => ( ; + /> +); HorizontalPodAutoscalersPage.displayName = 'HorizontalPodAutoscalersListPage'; export type HorizontalPodAutoscalersDetailsProps = { - obj: any, + obj: any; }; export type HorizontalPodAutoscalersPageProps = { - showTitle?: boolean, - namespace?: string, - selector?: any, + showTitle?: boolean; + namespace?: string; + selector?: any; }; export type HorizontalPodAutoscalersDetailsPageProps = { - match: any, + match: any; }; type MetricsTableProps = { - obj: any, + obj: any; }; type MetricsRowProps = { - type: any, - current: any, - target: any, + type: any; + current: any; + target: any; }; diff --git a/frontend/public/components/image-stream-tag.tsx b/frontend/public/components/image-stream-tag.tsx index 9c0f0af7dca..ac9d999c1e0 100644 --- a/frontend/public/components/image-stream-tag.tsx +++ b/frontend/public/components/image-stream-tag.tsx @@ -2,7 +2,7 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import { K8sResourceKind, K8sResourceKindReference } from '../module/k8s'; -import {ImageStreamTagModel} from '../models'; +import { ImageStreamTagModel } from '../models'; import { DetailsPage } from './factory'; import { Kebab, SectionHeading, navFactory, ResourceSummary } from './utils'; import { humanizeBinaryBytes } from './utils/units'; @@ -32,7 +32,10 @@ const splitEnv = (nameValue: string) => { }; }; -export const ImageStreamTagsDetails: React.SFC = ({obj: imageStreamTag, imageStream}) => { +export const ImageStreamTagsDetails: React.SFC = ({ + obj: imageStreamTag, + imageStream, +}) => { const config = _.get(imageStreamTag, 'image.dockerImageMetadata.Config', {}); const labels = config.Labels || {}; // Convert to an array of objects with name and value properties, then sort the array for display. @@ -46,86 +49,96 @@ export const ImageStreamTagsDetails: React.SFC = ({ const architecture = _.get(imageStreamTag, 'image.dockerImageMetadata.Architecture'); const tagName = _.get(imageStreamTag, 'tag.name'); - return
    -
    -
    -
    - - - {labels.name &&
    Image Name
    } - {labels.name &&
    {labels.name}
    } - {labels.summary &&
    Summary
    } - {labels.summary &&
    {labels.summary}
    } - {humanizedSize &&
    Size
    } - {humanizedSize &&
    {humanizedSize}
    } -
    - -
    -
    - -
    - {entrypoint &&
    Entrypoint
    } - {entrypoint &&
    {entrypoint}
    } - {cmd &&
    Command
    } - {cmd &&
    {cmd}
    } - {config.WorkingDir &&
    Working Dir
    } - {config.WorkingDir &&
    {config.WorkingDir}
    } - {exposedPorts &&
    Exposed Ports
    } - {exposedPorts &&
    {exposedPorts}
    } - {config.User &&
    User
    } - {config.User &&
    {config.User}
    } - {architecture &&
    Architecture
    } - {architecture &&
    {architecture}
    } -
    + return ( +
    +
    +
    +
    + + + {labels.name &&
    Image Name
    } + {labels.name &&
    {labels.name}
    } + {labels.summary &&
    Summary
    } + {labels.summary &&
    {labels.summary}
    } + {humanizedSize &&
    Size
    } + {humanizedSize &&
    {humanizedSize}
    } +
    + +
    +
    + +
    + {entrypoint &&
    Entrypoint
    } + {entrypoint &&
    {entrypoint}
    } + {cmd &&
    Command
    } + {cmd &&
    {cmd}
    } + {config.WorkingDir &&
    Working Dir
    } + {config.WorkingDir &&
    {config.WorkingDir}
    } + {exposedPorts &&
    Exposed Ports
    } + {exposedPorts &&
    {exposedPorts}
    } + {config.User &&
    User
    } + {config.User &&
    {config.User}
    } + {architecture &&
    Architecture
    } + {architecture &&
    {architecture}
    } +
    +
    +
    + + {_.isEmpty(sortedLabels) ? ( + No labels + ) : ( +
    +
    + + + + + + + + {_.map(sortedLabels, ({ name, value }) => ( + + + + + ))} + +
    NameValue
    {name}{value}
    +
    + )} +
    +
    + + {_.isEmpty(config.Env) ? ( + No environment variables + ) : ( +
    + + + + + + + + + {_.map(config.Env, (nameValueStr, i) => { + const pair = splitEnv(nameValueStr); + return ( + + + + + ); + })} + +
    NameValue
    {pair.name}{pair.value}
    +
    + )} +
    -
    - - {_.isEmpty(sortedLabels) - ? No labels - :
    - - - - - - - - - {_.map(sortedLabels, ({name, value}) => - - - )} - -
    NameValue
    {name}{value}
    -
    } -
    -
    - - {_.isEmpty(config.Env) - ? No environment variables - :
    - - - - - - - - - {_.map(config.Env, (nameValueStr, i) => { - const pair = splitEnv(nameValueStr); - return - - - ; - })} - -
    NameValue
    {pair.name}{pair.value}
    -
    } -
    -
    ; + ); }; const parseName = (nameAndTag: string): string => { @@ -134,44 +147,68 @@ const parseName = (nameAndTag: string): string => { const getImageStreamNameAndTag = (imageStreamTag: K8sResourceKind) => { const imageStreamTagName: string = _.get(imageStreamTag, 'metadata.name') || ''; - const [ imageStreamName, tag ] = imageStreamTagName.split(':'); + const [imageStreamName, tag] = imageStreamTagName.split(':'); return { imageStreamName, tag }; }; -const ImageStreamTagHistory: React.FC = ({ obj: imageStreamTag, imageStream }) => { +const ImageStreamTagHistory: React.FC = ({ + obj: imageStreamTag, + imageStream, +}) => { const { tag } = getImageStreamNameAndTag(imageStreamTag); - const imageStreamStatusTags = _.filter(_.get(imageStream, 'status.tags'), i => i.tag === tag); - return ; + const imageStreamStatusTags = _.filter(_.get(imageStream, 'status.tags'), (i) => i.tag === tag); + return ( + + ); }; ImageStreamTagHistory.displayName = 'ImageStreamTagHistory'; -const pages = [navFactory.details(ImageStreamTagsDetails), navFactory.editYaml(), navFactory.history(ImageStreamTagHistory)]; -export const ImageStreamTagsDetailsPage: React.SFC = props => +const pages = [ + navFactory.details(ImageStreamTagsDetails), + navFactory.editYaml(), + navFactory.history(ImageStreamTagHistory), +]; +export const ImageStreamTagsDetailsPage: React.SFC = (props) => ( { + breadcrumbsFor={(obj) => { const { imageStreamName } = getImageStreamNameAndTag(obj); - return [{name: 'Image Streams', path: `/k8s/ns/${props.match.params.ns}/imagestreams`, - }, { - name: imageStreamName, - path: `/k8s/ns/${props.match.params.ns}/imagestreams/${imageStreamName}`, - }, { - name: 'Image Stream Tag Details', - path: props.match.url, - }]; + return [ + { name: 'Image Streams', path: `/k8s/ns/${props.match.params.ns}/imagestreams` }, + { + name: imageStreamName, + path: `/k8s/ns/${props.match.params.ns}/imagestreams/${imageStreamName}`, + }, + { + name: 'Image Stream Tag Details', + path: props.match.url, + }, + ]; }} kind={ImageStreamTagsReference} menuActions={menuActions} resources={[ - {kind: ImageStreamsReference, name: parseName(props.name), namespace: props.namespace, isList: false, prop: 'imageStream'}, + { + kind: ImageStreamsReference, + name: parseName(props.name), + namespace: props.namespace, + isList: false, + prop: 'imageStream', + }, ]} - pages={pages} />; + pages={pages} + /> +); ImageStreamTagsDetailsPage.displayName = 'ImageStreamTagsDetailsPage'; type ImageStreamTagHistoryProps = { imageStream: K8sResourceKind; obj: K8sResourceKind; -} +}; export type ImageStreamTagsDetailsProps = { obj: K8sResourceKind; diff --git a/frontend/public/components/image-stream-timeline.tsx b/frontend/public/components/image-stream-timeline.tsx index 61f2b482598..563dbaf0fbf 100644 --- a/frontend/public/components/image-stream-timeline.tsx +++ b/frontend/public/components/image-stream-timeline.tsx @@ -8,52 +8,98 @@ import { getImageStreamTagName } from './image-stream'; const ImageStreamTagsReference: K8sResourceKindReference = 'ImageStreamTag'; -const ImageStreamTimelineItem: React.FC = ({tag, imageStreamName, imageStreamNamespace, linkToTag}) => { +const ImageStreamTimelineItem: React.FC = ({ + tag, + imageStreamName, + imageStreamNamespace, + linkToTag, +}) => { const referenceAndSHA = _.split(tag.dockerImageReference, '@'); - return -
  • -
    - -
    -
    + return ( + +
  • +
    + + + +
    + +
    +
    -
    - -
    - -
    from {referenceAndSHA[0]}
    -
    {referenceAndSHA[1]}
    +
    + +
    + +
    from {referenceAndSHA[0]}
    +
    {referenceAndSHA[1]}
    +
    -
    -
  • -
    ; + + + ); }; // check is the compared tag version, is the latest version in a sorted array of all tag versions -const isTagVersionLatest = (comparedTag: string, comparedTagPosition: number, orderedTagArray: TagMeta[]) => { - return comparedTagPosition === _.findIndex(orderedTagArray, (orderedTag: TagMeta) => orderedTag.tag === comparedTag); +const isTagVersionLatest = ( + comparedTag: string, + comparedTagPosition: number, + orderedTagArray: TagMeta[], +) => { + return ( + comparedTagPosition === + _.findIndex(orderedTagArray, (orderedTag: TagMeta) => orderedTag.tag === comparedTag) + ); }; -export const ImageStreamTimeline: React.FC = ({ imageStreamTags, imageStreamName, imageStreamNamespace }) => { +export const ImageStreamTimeline: React.FC = ({ + imageStreamTags, + imageStreamName, + imageStreamNamespace, +}) => { if (!_.some(imageStreamTags, 'items')) { return ; } - const tagsArray: TagMeta[] = _.flatten(_.map(imageStreamTags, ({tag, items}) => { - return _.map(items, ({created, dockerImageReference}) => ({tag, created, dockerImageReference})); - })); + const tagsArray: TagMeta[] = _.flatten( + _.map(imageStreamTags, ({ tag, items }) => { + return _.map(items, ({ created, dockerImageReference }) => ({ + tag, + created, + dockerImageReference, + })); + }), + ); const orderedTagArray = _.orderBy(tagsArray, ['created'], ['desc']); - const timeline = _.map((orderedTagArray), (tag: TagMeta, i: number) => { - return ; + const timeline = _.map(orderedTagArray, (tag: TagMeta, i: number) => { + return ( + + ); }); - return -
      - {timeline} -
      - -
      -
    -
    ; + return ( + +
      + {timeline} +
      + + + +
      +
    +
    + ); }; type ImageStreamTimelineItemProps = { @@ -61,16 +107,16 @@ type ImageStreamTimelineItemProps = { imageStreamName: string; imageStreamNamespace: string; linkToTag: boolean; -} +}; type TagMeta = { created: string; tag: string; dockerImageReference: string; -} +}; type ImageStreamTimelineProps = { imageStreamTags: any[]; imageStreamName: string; imageStreamNamespace: string; -} +}; diff --git a/frontend/public/components/image-stream.tsx b/frontend/public/components/image-stream.tsx index 2c8bf536cf2..0a6649059a1 100644 --- a/frontend/public/components/image-stream.tsx +++ b/frontend/public/components/image-stream.tsx @@ -7,9 +7,21 @@ import { AlertVariant, Popover } from '@patternfly/react-core'; import { QuestionCircleIcon } from '@patternfly/react-icons'; import { K8sResourceKind, K8sResourceKindReference } from '../module/k8s'; -import {ImageStreamModel} from '../models'; +import { ImageStreamModel } from '../models'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; -import { CopyToClipboard, ExpandableAlert, ExternalLink, Kebab, SectionHeading, LabelList, navFactory, ResourceKebab, ResourceLink, ResourceSummary, Timestamp } from './utils'; +import { + CopyToClipboard, + ExpandableAlert, + ExternalLink, + Kebab, + SectionHeading, + LabelList, + navFactory, + ResourceKebab, + ResourceLink, + ResourceSummary, + Timestamp, +} from './utils'; import { ImageStreamTimeline } from './image-stream-timeline'; import { fromNow } from './utils/datetime'; import { YellowExclamationTriangleIcon } from '@console/shared'; @@ -17,9 +29,11 @@ import { YellowExclamationTriangleIcon } from '@console/shared'; const ImageStreamsReference: K8sResourceKindReference = 'ImageStream'; const ImageStreamTagsReference: K8sResourceKindReference = 'ImageStreamTag'; -export const getImageStreamTagName = (imageStreamName: string, tag: string): string => `${imageStreamName}:${tag}`; +export const getImageStreamTagName = (imageStreamName: string, tag: string): string => + `${imageStreamName}:${tag}`; -export const getAnnotationTags = (specTag: any) => _.get(specTag, 'annotations.tags', '').split(/\s*,\s*/); +export const getAnnotationTags = (specTag: any) => + _.get(specTag, 'annotations.tags', '').split(/\s*,\s*/); const isBuilderTag = (specTag: any) => { // A spec tag has annotations tags, which is a comma-delimited string (e.g., 'builder,httpd'). @@ -34,12 +48,12 @@ const getStatusTags = (imageStream: K8sResourceKind): any => { export const getBuilderTags = (imageStream: K8sResourceKind): any[] => { const statusTags = getStatusTags(imageStream); - return _.filter(imageStream.spec.tags, tag => isBuilderTag(tag) && statusTags[tag.name]); + return _.filter(imageStream.spec.tags, (tag) => isBuilderTag(tag) && statusTags[tag.name]); }; // Sort tags in reverse order by semver, falling back to a string comparison if not a valid version. export const getBuilderTagsSortedByVersion = (imageStream: K8sResourceKind): any[] => { - return getBuilderTags(imageStream).sort(({name: a}, {name: b}) => { + return getBuilderTags(imageStream).sort(({ name: a }, { name: b }) => { const v1 = semver.coerce(a); const v2 = semver.coerce(b); if (!v1 && !v2) { @@ -68,39 +82,79 @@ export const isBuilder = (imageStream: K8sResourceKind) => !_.isEmpty(getBuilder const { common } = Kebab.factory; const menuActions = [...Kebab.getExtensionsActionsForKind(ImageStreamModel), ...common]; -const ImageStreamTagsRow: React.SFC = ({imageStream, specTag, statusTag}) => { +const ImageStreamTagsRow: React.SFC = ({ + imageStream, + specTag, + statusTag, +}) => { const imageStreamStatus = _.get(imageStream, 'status'); const latest = _.get(statusTag, ['items', 0]); const from = _.get(specTag, 'from'); const referencesTag = _.get(specTag, 'from.kind') === 'ImageStreamTag'; const image = _.get(latest, 'image'); const created = _.get(latest, 'created'); - const dockerRepositoryCheck = _.has(imageStream, ['metadata', 'annotations', 'openshift.io/image.dockerRepositoryCheck']); - return
    -
    - -
    - - {from && referencesTag && } - {from && !referencesTag && {from.name}} - {!from && pushed image} - - - {!imageStreamStatus && dockerRepositoryCheck &&  Unable to resolve} - {!imageStreamStatus && !dockerRepositoryCheck && !from && Not synced yet} - {/* We have no idea why in this case */} - {!imageStreamStatus && !dockerRepositoryCheck && from && Unresolved} - {imageStreamStatus && image && {image}} - {imageStreamStatus && !image &&  There is no image associated with this tag} - -
    - {created && } - {!created && '-'} + const dockerRepositoryCheck = _.has(imageStream, [ + 'metadata', + 'annotations', + 'openshift.io/image.dockerRepositoryCheck', + ]); + return ( +
    +
    + +
    + + {from && referencesTag && ( + + )} + {from && !referencesTag && {from.name}} + {!from && pushed image} + + + {!imageStreamStatus && dockerRepositoryCheck && ( + + +  Unable to resolve + + )} + {!imageStreamStatus && !dockerRepositoryCheck && !from && ( + Not synced yet + )} + {/* We have no idea why in this case */} + {!imageStreamStatus && !dockerRepositoryCheck && from && ( + Unresolved + )} + {imageStreamStatus && image && {image}} + {imageStreamStatus && !image && ( + + +  There is no image associated with this tag + + )} + +
    + {created && } + {!created && '-'} +
    -
    ; + ); }; -export const ExampleDockerCommandPopover: React.FC = ({imageStream, tag}) => { +export const ExampleDockerCommandPopover: React.FC = ({ + imageStream, + tag, +}) => { const publicImageRepository = _.get(imageStream, 'status.publicDockerImageRepository'); if (!publicImageRepository) { return null; @@ -109,96 +163,139 @@ export const ExampleDockerCommandPopover: React.FC'}`; const pullCommand = `docker pull ${publicImageRepository}:${tag || ''}`; - return Image registry commands} - className="co-example-docker-command__popover" - bodyContent={ -
    -

    Create a new Image Stream Tag by pushing an image to this Image Stream with the desired tag.

    -
    -

    Authenticate to the internal registry

    - -
    -

    Push an image to this Image Stream

    - -
    -

    Pull an image from this Image Stream

    - -
    -

    Red Hat Enterprise Linux users may use the equivalent podman commands.

    -
    - }> - -
    ; + return ( + Image registry commands} + className="co-example-docker-command__popover" + bodyContent={ +
    +

    + Create a new Image Stream Tag by pushing an image to this Image Stream with the desired + tag. +

    +
    +

    Authenticate to the internal registry

    + +
    +

    Push an image to this Image Stream

    + +
    +

    Pull an image from this Image Stream

    + +
    +

    + Red Hat Enterprise Linux users may use the equivalent podman commands.{' '} + +

    +
    + } + > + +
    + ); }; const getImportErrors = (imageStream: K8sResourceKind): string[] => { return _.transform(imageStream.status.tags, (acc, tag: any) => { - const importErrorCondition = _.find(tag.conditions, condition => condition.type === 'ImportSuccess' && condition.status === 'False'); - importErrorCondition && acc.push(`Unable to sync image for tag ${imageStream.metadata.name}:${tag.tag}. ${importErrorCondition.message}`); + const importErrorCondition = _.find( + tag.conditions, + (condition) => condition.type === 'ImportSuccess' && condition.status === 'False', + ); + importErrorCondition && + acc.push( + `Unable to sync image for tag ${imageStream.metadata.name}:${tag.tag}. ${ + importErrorCondition.message + }`, + ); }); }; -export const ImageStreamsDetails: React.SFC = ({obj: imageStream}) => { +export const ImageStreamsDetails: React.SFC = ({ obj: imageStream }) => { const imageRepository = _.get(imageStream, 'status.dockerImageRepository'); const publicImageRepository = _.get(imageStream, 'status.publicDockerImageRepository'); const imageCount = _.get(imageStream, 'status.tags.length'); const specTagByName = _.keyBy(imageStream.spec.tags, 'name'); const importErrors = getImportErrors(imageStream); - return
    -
    - {!_.isEmpty(importErrors) && {error})} />} - - - {imageRepository &&
    Image Repository
    } - {imageRepository &&
    {imageRepository}
    } - {publicImageRepository &&
    Public Image Repository
    } - {publicImageRepository &&
    {publicImageRepository}
    } -
    Image Count
    -
    {imageCount ? imageCount : 0}
    -
    - -
    -
    - - {_.isEmpty(imageStream.status.tags) - ? No tags - :
    -
    -
    -
    Name
    -
    From
    -
    Identifier
    -
    Last Updated
    -
    -
    - {_.map(imageStream.status.tags, (statusTag) => - )} + return ( +
    +
    + {!_.isEmpty(importErrors) && ( + ( + {error} + ))} + /> + )} + + + {imageRepository &&
    Image Repository
    } + {imageRepository &&
    {imageRepository}
    } + {publicImageRepository &&
    Public Image Repository
    } + {publicImageRepository &&
    {publicImageRepository}
    } +
    Image Count
    +
    {imageCount ? imageCount : 0}
    +
    + +
    +
    + + {_.isEmpty(imageStream.status.tags) ? ( + No tags + ) : ( +
    +
    +
    +
    Name
    +
    From
    +
    Identifier
    +
    Last Updated
    +
    +
    + {_.map(imageStream.status.tags, (statusTag) => ( + + ))} +
    -
    } + )} +
    -
    ; + ); }; const ImageStreamHistory: React.FC = ({ obj: imageStream }) => { const imageStreamStatusTags = _.get(imageStream, 'status.tags'); - return ; + return ( + + ); }; ImageStreamHistory.displayName = 'ImageStreamHistory'; -const pages = [navFactory.details(ImageStreamsDetails), navFactory.editYaml(), navFactory.history(ImageStreamHistory)]; -export const ImageStreamsDetailsPage: React.SFC = props => - ; +const pages = [ + navFactory.details(ImageStreamsDetails), + navFactory.editYaml(), + navFactory.history(ImageStreamHistory), +]; +export const ImageStreamsDetailsPage: React.SFC = (props) => ( + +); ImageStreamsDetailsPage.displayName = 'ImageStreamsDetailsPage'; const tableColumnClasses = [ @@ -212,33 +309,47 @@ const tableColumnClasses = [ const ImageStreamsTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: '', props: { className: tableColumnClasses[4] }, + title: '', + props: { className: tableColumnClasses[4] }, }, ]; }; ImageStreamsTableHeader.displayName = 'ImageStreamsTableHeader'; -const ImageStreamsTableRow: React.FC = ({obj, index, key, style}) => { +const ImageStreamsTableRow: React.FC = ({ obj, index, key, style }) => { return ( - + @@ -263,19 +374,28 @@ type ImageStreamsTableRowProps = { style: object; }; -export const ImageStreamsList: React.SFC = props => ; +export const ImageStreamsList: React.SFC = (props) => ( +
    +); ImageStreamsList.displayName = 'ImageStreamsList'; -export const buildPhase = build => build.status.phase; +export const buildPhase = (build) => build.status.phase; -export const ImageStreamsPage: React.SFC = props => +export const ImageStreamsPage: React.SFC = (props) => ( ; + /> +); ImageStreamsPage.displayName = 'ImageStreamsListPage'; type ImageStreamTagsRowProps = { @@ -286,11 +406,11 @@ type ImageStreamTagsRowProps = { type ImageStreamHistoryProps = { obj: K8sResourceKind; -} +}; export type ImageStreamManipulationHelpProps = { imageStream: K8sResourceKind; - tag?: string + tag?: string; }; export type ImageStreamsDetailsProps = { diff --git a/frontend/public/components/impersonate-notifier.jsx b/frontend/public/components/impersonate-notifier.jsx index 8344390df2e..133a4a1d3bb 100644 --- a/frontend/public/components/impersonate-notifier.jsx +++ b/frontend/public/components/impersonate-notifier.jsx @@ -5,17 +5,25 @@ import { connect } from 'react-redux'; import * as UIActions from '../actions/ui'; export const ImpersonateNotifier = connect( - ({UI}) => ({impersonate: UI.get('impersonate')}), - {stopImpersonate: UIActions.stopImpersonate} -)(({stopImpersonate, impersonate}) => { + ({ UI }) => ({ impersonate: UI.get('impersonate') }), + { stopImpersonate: UIActions.stopImpersonate }, +)(({ stopImpersonate, impersonate }) => { if (!impersonate) { return null; } - return
    -
    -

    - {`Impersonating ${impersonate.kind}`} You are impersonating {impersonate.name}. You are viewing all resources and roles this {_.toLower(impersonate.kind)} can access. Stop Impersonation -

    + return ( +
    +
    +

    + {`Impersonating ${ + impersonate.kind + }`}{' '} + You are impersonating{' '} + {impersonate.name}. You + are viewing all resources and roles this {_.toLower(impersonate.kind)} can access.{' '} + Stop Impersonation +

    +
    -
    ; + ); }); diff --git a/frontend/public/components/import-yaml.tsx b/frontend/public/components/import-yaml.tsx index 387e6bac07c..e92bd677f44 100644 --- a/frontend/public/components/import-yaml.tsx +++ b/frontend/public/components/import-yaml.tsx @@ -5,10 +5,17 @@ import { AsyncComponent } from './utils'; export const ImportYamlPage = () => { const title = 'Import YAML'; - return - - {title} - - import('./droppable-edit-yaml').then(c => c.DroppableEditYAML)} create={true} download={false} header={title} /> - ; + return ( + + + {title} + + import('./droppable-edit-yaml').then((c) => c.DroppableEditYAML)} + create={true} + download={false} + header={title} + /> + + ); }; diff --git a/frontend/public/components/ingress.jsx b/frontend/public/components/ingress.jsx index 1765969e168..b0b318bb133 100644 --- a/frontend/public/components/ingress.jsx +++ b/frontend/public/components/ingress.jsx @@ -3,18 +3,34 @@ import * as React from 'react'; import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; -import { Kebab, SectionHeading, LabelList, ResourceKebab, ResourceIcon, detailsPage, EmptyBox, navFactory, ResourceLink, ResourceSummary } from './utils'; +import { + Kebab, + SectionHeading, + LabelList, + ResourceKebab, + ResourceIcon, + detailsPage, + EmptyBox, + navFactory, + ResourceLink, + ResourceSummary, +} from './utils'; const menuActions = Kebab.factory.common; -export const ingressValidHosts = ingress => _.map(_.get(ingress, 'spec.rules', []), 'host').filter(_.isString); +export const ingressValidHosts = (ingress) => + _.map(_.get(ingress, 'spec.rules', []), 'host').filter(_.isString); const getHosts = (ingress) => { const hosts = ingressValidHosts(ingress); if (hosts.length) { const hostsStr = hosts.join(', '); - return
    {hostsStr}
    ; + return ( +
    + {hostsStr} +
    + ); } return
    No hosts
    ; @@ -22,15 +38,21 @@ const getHosts = (ingress) => { const getTLSCert = (ingress) => { if (!_.has(ingress.spec, 'tls')) { - return
    Not configured
    ; + return ( +
    + Not configured +
    + ); } const certs = _.map(ingress.spec.tls, 'secretName'); - return
    - - {certs.join(', ')} -
    ; + return ( +
    + + {certs.join(', ')} +
    + ); }; const tableColumnClasses = [ @@ -46,44 +68,59 @@ const kind = 'Ingress'; const IngressTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Hosts', sortFunc: 'ingressValidHosts', transforms: [sortable], + title: 'Hosts', + sortFunc: 'ingressValidHosts', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: '', props: { className: tableColumnClasses[4] }, + title: '', + props: { className: tableColumnClasses[4] }, }, ]; }; IngressTableHeader.displayName = 'IngressTableHeader'; -const IngressTableRow = ({obj: ingress, index, key, style}) => { +const IngressTableRow = ({ obj: ingress, index, key, style }) => { return ( - + - + - - {getHosts(ingress)} - + {getHosts(ingress)} @@ -92,36 +129,43 @@ const IngressTableRow = ({obj: ingress, index, key, style}) => { }; IngressTableRow.displayName = 'IngressTableRow'; -const RulesHeader = () =>
    -
    Host
    -
    Path
    -
    Service
    -
    Service Port
    -
    ; - -const RulesRow = ({rule, namespace}) => { +const RulesHeader = () => ( +
    +
    Host
    +
    Path
    +
    Service
    +
    Service Port
    +
    +); - return
    -
    -
    {rule.host}
    -
    -
    -
    {rule.path}
    -
    -
    - {rule.serviceName ? : '-'} -
    -
    -
    {rule.servicePort || '-'}
    +const RulesRow = ({ rule, namespace }) => { + return ( +
    +
    +
    {rule.host}
    +
    +
    +
    {rule.path}
    +
    +
    + {rule.serviceName ? ( + + ) : ( + '-' + )} +
    +
    +
    {rule.servicePort || '-'}
    +
    -
    ; + ); }; const RulesRows = (props) => { const rules = []; if (_.has(props.spec, 'rules')) { - _.forEach(props.spec.rules, rule => { + _.forEach(props.spec.rules, (rule) => { const paths = _.get(rule, 'http.paths'); if (_.isEmpty(paths)) { rules.push({ @@ -131,7 +175,7 @@ const RulesRows = (props) => { servicePort: _.get(props.spec, 'backend.servicePort'), }); } else { - _.forEach(paths, path => { + _.forEach(paths, (path) => { rules.push({ host: rule.host || '*', path: path.path || '*', @@ -142,7 +186,7 @@ const RulesRows = (props) => { } }); - const rows = _.map(rules, rule => { + const rows = _.map(rules, (rule) => { return ; }); @@ -152,31 +196,49 @@ const RulesRows = (props) => { return ; }; -const Details = ({obj: ingress}) => -
    - - -
    TLS Certificate
    -
    {getTLSCert(ingress)}
    -
    -
    -
    - -

    These rules are handled by a routing layer (Ingress Controller) which is updated as the rules are modified. The Ingress controller implementation defines how headers and other metadata are forwarded or manipulated.

    -
    - - +const Details = ({ obj: ingress }) => ( + +
    + + +
    TLS Certificate
    +
    {getTLSCert(ingress)}
    +
    -
    -; - -const IngressesDetailsPage = props => ; -const IngressesList = props =>
    ; - -const IngressesPage = props => ; - -export {IngressesList, IngressesPage, IngressesDetailsPage}; +
    + +

    + These rules are handled by a routing layer (Ingress Controller) which is updated as the + rules are modified. The Ingress controller implementation defines how headers and other + metadata are forwarded or manipulated. +

    +
    + + +
    +
    + +); + +const IngressesDetailsPage = (props) => ( + +); +const IngressesList = (props) => ( +
    +); + +const IngressesPage = (props) => ( + +); + +export { IngressesList, IngressesPage, IngressesDetailsPage }; diff --git a/frontend/public/components/instantiate-template.tsx b/frontend/public/components/instantiate-template.tsx index 51a8353fee3..6a13ceaf8c6 100644 --- a/frontend/public/components/instantiate-template.tsx +++ b/frontend/public/components/instantiate-template.tsx @@ -6,8 +6,21 @@ import * as classNames from 'classnames'; import { ActionGroup, Button } from '@patternfly/react-core'; import { ANNOTATIONS } from '../const'; -import { getImageForIconClass, getTemplateIcon, normalizeIconClass } from './catalog/catalog-item-icon'; -import { ButtonBar, ExternalLink, Firehose, history, LoadingBox, LoadError, NsDropdown, resourcePathFromModel } from './utils'; +import { + getImageForIconClass, + getTemplateIcon, + normalizeIconClass, +} from './catalog/catalog-item-icon'; +import { + ButtonBar, + ExternalLink, + Firehose, + history, + LoadingBox, + LoadError, + NsDropdown, + resourcePathFromModel, +} from './utils'; import { SecretModel, TemplateInstanceModel } from '../models'; import { k8sCreate, @@ -17,7 +30,7 @@ import { TemplateParameter, } from '../module/k8s'; -const TemplateResourceDetails: React.FC = ({template}) => { +const TemplateResourceDetails: React.FC = ({ template }) => { const resources = _.uniq(_.compact(_.map(template.objects, 'kind'))).sort(); if (_.isEmpty(resources)) { return null; @@ -26,18 +39,18 @@ const TemplateResourceDetails: React.FC = ({templa return (
    -

    - The following resources will be created: -

    +

    The following resources will be created:

      - {resources.map((kind: string) =>
    • {kind}
    • )} + {resources.map((kind: string) => ( +
    • {kind}
    • + ))}
    ); }; TemplateResourceDetails.displayName = 'TemplateResourceDetails'; -const TemplateInfo: React.FC = ({template}) => { +const TemplateInfo: React.FC = ({ template }) => { const annotations = template.metadata.annotations || {}; const { description } = annotations; const displayName = annotations[ANNOTATIONS.displayName] || template.metadata.name; @@ -47,33 +60,59 @@ const TemplateInfo: React.FC = ({template}) => { const documentationURL = annotations[ANNOTATIONS.documentationURL]; const supportURL = annotations[ANNOTATIONS.supportURL]; - return
    -
    - - { imgURL - ? - : } - -
    -

    {displayName}

    - {!_.isEmpty(tags) &&

    {_.map(tags, (tag, i) => {tag})}

    } - {(documentationURL || supportURL) &&
      - {documentationURL &&
    • - -
    • } - {supportURL &&
    • - -
    • } -
    } + return ( +
    +
    + + {imgURL ? ( + + ) : ( + + )} + +
    +

    {displayName}

    + {!_.isEmpty(tags) && ( +

    + {_.map(tags, (tag, i) => ( + + {tag} + + ))} +

    + )} + {(documentationURL || supportURL) && ( +
      + {documentationURL && ( +
    • + +
    • + )} + {supportURL && ( +
    • + +
    • + )} +
    + )} +
    + {description &&

    {description}

    } +
    - {description &&

    {description}

    } - -
    ; + ); }; TemplateInfo.displayName = 'TemplateInfo'; -const stateToProps = ({k8s}) => ({ +const stateToProps = ({ k8s }) => ({ models: k8s.getIn(['RESOURCES', 'models']), }); @@ -81,7 +120,7 @@ class TemplateForm_ extends React.Component { const templateParameters: TemplateParameter[] = props.obj.data.parameters || []; - return templateParameters.reduce((acc, {name, value}: TemplateParameter) => { + return templateParameters.reduce((acc, { name, value }: TemplateParameter) => { acc[name] = value; return acc; }, {}); }; onNamespaceChange = (namespace: string) => { - this.setState({namespace}); - } + this.setState({ namespace }); + }; - onParameterChanged: React.ReactEventHandler = event => { + onParameterChanged: React.ReactEventHandler = (event) => { const { name, value } = event.currentTarget; - this.setState(({parameters}) => ({ + this.setState(({ parameters }) => ({ parameters: { ...parameters, [name]: value, @@ -160,23 +199,37 @@ class TemplateForm_ extends React.Component { - return this.createTemplateInstance(secret).then((instance: TemplateInstanceKind) => { - this.setState({inProgress: false}); - history.push(resourcePathFromModel(TemplateInstanceModel, instance.metadata.name, instance.metadata.namespace)); - }); - }).catch(err => this.setState({ inProgress: false, error: err.message })); + this.setState({ error: '', inProgress: true }); + this.createTemplateSecret() + .then((secret: K8sResourceKind) => { + return this.createTemplateInstance(secret).then((instance: TemplateInstanceKind) => { + this.setState({ inProgress: false }); + history.push( + resourcePathFromModel( + TemplateInstanceModel, + instance.metadata.name, + instance.metadata.namespace, + ), + ); + }); + }) + .catch((err) => this.setState({ inProgress: false, error: err.message })); }; render() { const { obj } = this.props; if (obj.loadError) { - return ; + return ( + + ); } if (!obj.loaded) { @@ -186,48 +239,82 @@ class TemplateForm_ extends React.Component -
    - -
    -
    -
    -
    - - -
    - {parameters.map(({name, displayName, description, required: requiredParam, generate}: TemplateParameter) => { - const value = this.state.parameters[name] || ''; - const helpID = description ? `${name}-help` : ''; - const placeholder = generate ? '(generated if empty)' : ''; - // Only set required for parameters not generated. - const requiredInput = requiredParam && !generate; - return
    -
    -
    ; + ); } } const TemplateForm = connect(stateToProps)(TemplateForm_); @@ -239,20 +326,28 @@ export const InstantiateTemplatePage: React.FC<{}> = (props) => { const templateNamespace = searchParams.get('template-ns'); const preselectedNamespace = searchParams.get('preselected-ns'); const resources = [ - {kind: 'Template', name: templateName, namespace: templateNamespace, isList: false, prop: 'obj'}, + { + kind: 'Template', + name: templateName, + namespace: templateNamespace, + isList: false, + prop: 'obj', + }, ]; - return - - {title} - -
    -

    {title}

    - - - -
    -
    ; + return ( + + + {title} + +
    +

    {title}

    + + + +
    +
    + ); }; type TemplateResourceDetailsProps = { diff --git a/frontend/public/components/job.jsx b/frontend/public/components/job.jsx index e8ad60d3e3a..f5451db2e94 100644 --- a/frontend/public/components/job.jsx +++ b/frontend/public/components/job.jsx @@ -7,16 +7,27 @@ import { Status } from '@console/shared'; import { getJobTypeAndCompletions } from '../module/k8s'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { configureJobParallelismModal } from './modals'; -import { Kebab, ContainerTable, SectionHeading, LabelList, ResourceKebab, ResourceLink, ResourceSummary, Timestamp, navFactory } from './utils'; +import { + Kebab, + ContainerTable, + SectionHeading, + LabelList, + ResourceKebab, + ResourceLink, + ResourceSummary, + Timestamp, + navFactory, +} from './utils'; import { ResourceEventStream } from './events'; -import {JobModel} from '../models'; +import { JobModel } from '../models'; const ModifyJobParallelism = (kind, obj) => ({ label: 'Edit Parallelism', - callback: () => configureJobParallelismModal({ - resourceKind: kind, - resource: obj, - }), + callback: () => + configureJobParallelismModal({ + resourceKind: kind, + resource: obj, + }), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -25,7 +36,11 @@ const ModifyJobParallelism = (kind, obj) => ({ verb: 'patch', }, }); -const menuActions = [ModifyJobParallelism, ...Kebab.getExtensionsActionsForKind(JobModel), ...Kebab.factory.common]; +const menuActions = [ + ModifyJobParallelism, + ...Kebab.getExtensionsActionsForKind(JobModel), + ...Kebab.factory.common, +]; const kind = 'Job'; @@ -41,41 +56,61 @@ const tableColumnClasses = [ const JobTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Labels', sortField: 'metadata.labels', transforms: [sortable], + title: 'Labels', + sortField: 'metadata.labels', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Completions', sortFunc: 'jobCompletions', transforms: [sortable], + title: 'Completions', + sortFunc: 'jobCompletions', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Type', sortFunc: 'jobType', transforms: [sortable], + title: 'Type', + sortFunc: 'jobType', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; JobTableHeader.displayName = 'JobTableHeader'; -const JobTableRow = ({obj: job, index, key, style}) => { - const {type, completions} = getJobTypeAndCompletions(job); +const JobTableRow = ({ obj: job, index, key, style }) => { + const { type, completions } = getJobTypeAndCompletions(job); return ( - + - + @@ -94,52 +129,70 @@ const JobTableRow = ({obj: job, index, key, style}) => { }; JobTableRow.displayName = 'JobTableRow'; -const Details = ({obj: job}) => -
    -
    -
    - - -
    Desired Completions
    -
    {job.spec.completions || '-'}
    -
    Parallelism
    -
    {job.spec.parallelism || '-'}
    -
    Deadline
    -
    {job.spec.activeDeadlineSeconds ? `${job.spec.activeDeadlineSeconds} seconds` : '-'}
    -
    -
    -
    - -
    -
    Status
    -
    {job.status.conditions ? : }
    -
    Start Time
    -
    -
    Completion Time
    -
    -
    Succeeded Pods
    -
    {job.status.succeeded || 0}
    -
    Active Pods
    -
    {job.status.active || 0}
    -
    Failed Pods
    -
    {job.status.failed || 0}
    -
    +const Details = ({ obj: job }) => ( + +
    +
    +
    + + +
    Desired Completions
    +
    {job.spec.completions || '-'}
    +
    Parallelism
    +
    {job.spec.parallelism || '-'}
    +
    Deadline
    +
    + {job.spec.activeDeadlineSeconds ? `${job.spec.activeDeadlineSeconds} seconds` : '-'} +
    +
    +
    +
    + +
    +
    Status
    +
    + {job.status.conditions ? ( + + ) : ( + + )} +
    +
    Start Time
    +
    + +
    +
    Completion Time
    +
    + +
    +
    Succeeded Pods
    +
    {job.status.succeeded || 0}
    +
    Active Pods
    +
    {job.status.active || 0}
    +
    Failed Pods
    +
    {job.status.failed || 0}
    +
    +
    -
    -
    - - -
    -; +
    + + +
    + +); -const {details, pods, editYaml, events} = navFactory; -const JobsDetailsPage = props => ; -const JobsList = props =>
    ; +const { details, pods, editYaml, events } = navFactory; +const JobsDetailsPage = (props) => ( + +); +const JobsList = (props) => ( +
    +); -const JobsPage = props => ; -export {JobsList, JobsPage, JobsDetailsPage}; +const JobsPage = (props) => ; +export { JobsList, JobsPage, JobsDetailsPage }; diff --git a/frontend/public/components/kube-admin-notifier.jsx b/frontend/public/components/kube-admin-notifier.jsx index 387083513a6..dc09dee58db 100644 --- a/frontend/public/components/kube-admin-notifier.jsx +++ b/frontend/public/components/kube-admin-notifier.jsx @@ -10,16 +10,16 @@ import { resourcePathFromModel } from './utils/resource-link'; const oAuthResourcePath = resourcePathFromModel(OAuthModel, 'cluster'); -export const KubeAdminNotifier = connect(userStateToProps)(({user}) => { +export const KubeAdminNotifier = connect(userStateToProps)(({ user }) => { const username = _.get(user, 'metadata.name'); - return username === KUBE_ADMIN_USERNAME - ?
    + return username === KUBE_ADMIN_USERNAME ? ( +

    - You are logged in as a temporary administrative user. - Update the cluster OAuth configuration to allow others to log in. + You are logged in as a temporary administrative user. Update the{' '} + cluster OAuth configuration to allow others to log in.

    - : null; + ) : null; }); diff --git a/frontend/public/components/limit-range.tsx b/frontend/public/components/limit-range.tsx index 9b74e4e902f..d46c5da7d9e 100644 --- a/frontend/public/components/limit-range.tsx +++ b/frontend/public/components/limit-range.tsx @@ -2,10 +2,18 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import * as classNames from 'classnames'; import { sortable } from '@patternfly/react-table'; -import {K8sResourceKindReference, K8sResourceKind} from '../module/k8s'; -import {LimitRangeModel} from '../models'; -import {DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; -import {Kebab, navFactory, SectionHeading, ResourceKebab, ResourceLink, ResourceSummary, Timestamp} from './utils'; +import { K8sResourceKindReference, K8sResourceKind } from '../module/k8s'; +import { LimitRangeModel } from '../models'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; +import { + Kebab, + navFactory, + SectionHeading, + ResourceKebab, + ResourceLink, + ResourceSummary, + Timestamp, +} from './utils'; const { common } = Kebab.factory; const menuActions = [...Kebab.getExtensionsActionsForKind(LimitRangeModel), ...common]; @@ -19,14 +27,27 @@ const tableColumnClasses = [ Kebab.columnClass, ]; -export const LimitRangeTableRow: React.FC = ({obj, index, key, style}) => { +export const LimitRangeTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + - + @@ -48,111 +69,150 @@ type LimitRangeTableRowProps = { export const LimitRangeTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Created', sortField: 'metadata.creationTimestamp', transforms: [sortable], + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: '', props: { className: tableColumnClasses[3] }, + title: '', + props: { className: tableColumnClasses[3] }, }, ]; }; LimitRangeTableHeader.displayName = 'LimitRangeTableHeader'; -export const LimitRangeList: React.SFC = props =>
    ; +export const LimitRangeList: React.SFC = (props) => ( +
    +); -export const LimitRangeListPage: React.SFC = props => +export const LimitRangeListPage: React.SFC = (props) => ( ; + /> +); -export const LimitRangeDetailsRow: React.SFC = ({limitType, resource, limit}) => { - return - - - - - - - - ; +export const LimitRangeDetailsRow: React.SFC = ({ + limitType, + resource, + limit, +}) => { + return ( + + + + + + + + + + ); }; -const LimitRangeDetailsRows: React.SFC = ({limit}) => { +const LimitRangeDetailsRows: React.SFC = ({ limit }) => { const properties = ['max', 'min', 'default', 'defaultRequest', 'maxLimitRequestRatio']; const resources = {}; - _.each(properties, property => { + _.each(properties, (property) => { _.each(limit[property], (value, resource) => _.set(resources, [resource, property], value)); }); - return - {_.map(resources, (resourceLimit, resource) => )} - ; + return ( + + {_.map(resources, (resourceLimit, resource) => ( + + ))} + + ); }; export const LimitRangeDetailsList = (resource) => { - return
    - -
    -
    {limitType}{resource}{limit.min || '-'}{limit.max || '-'}{limit.defaultRequest || '-'}{limit.default || '-'}{limit.maxLimitRequestRatio || '-'}
    {limitType}{resource}{limit.min || '-'}{limit.max || '-'}{limit.defaultRequest || '-'}{limit.default || '-'}{limit.maxLimitRequestRatio || '-'}
    - - - - - - - - - - - - - {_.map(resource.resource.spec.limits, (limit, index) => )} - -
    TypeResourceMinMaxDefault RequestDefault LimitMax Limit/Request Ratio
    + return ( +
    + +
    + + + + + + + + + + + + + + {_.map(resource.resource.spec.limits, (limit, index) => ( + + ))} + +
    TypeResourceMinMaxDefault RequestDefault LimitMax Limit/Request Ratio
    +
    -
    ; + ); }; -const Details = ({obj: rq}) => -
    - - -
    - -
    ; +const Details = ({ obj: rq }) => ( + +
    + + +
    + +
    +); -export const LimitRangeDetailsPage = props => ; +export const LimitRangeDetailsPage = (props) => ( + +); export type LimitRangeProps = { - obj: any, + obj: any; }; export type LimitRangeListPageProps = { - filterLabel: string, + filterLabel: string; }; export type LimitRangeDetailsRowsProps = { - limit: any, + limit: any; }; export type LimitRangeDetailsRowProps = { - limitType: string, - resource: string, - limit: any, + limitType: string; + resource: string; + limit: any; }; export type LimitRangeHeaderProps = { - obj: any, + obj: any; }; diff --git a/frontend/public/components/machine-autoscaler.tsx b/frontend/public/components/machine-autoscaler.tsx index 0c195952bb1..99624b17c36 100644 --- a/frontend/public/components/machine-autoscaler.tsx +++ b/frontend/public/components/machine-autoscaler.tsx @@ -3,7 +3,12 @@ import * as _ from 'lodash-es'; import { sortable } from '@patternfly/react-table'; import * as classNames from 'classnames'; import { MachineAutoscalerModel } from '../models'; -import { groupVersionFor, K8sResourceKind, referenceForGroupVersionKind, referenceForModel } from '../module/k8s'; +import { + groupVersionFor, + K8sResourceKind, + referenceForGroupVersionKind, + referenceForModel, +} from '../module/k8s'; import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, @@ -18,7 +23,7 @@ const { common } = Kebab.factory; const menuActions = [...Kebab.getExtensionsActionsForKind(MachineAutoscalerModel), ...common]; const machineAutoscalerReference = referenceForModel(MachineAutoscalerModel); -const MachineAutoscalerTargetLink: React.FC = ({obj}) => { +const MachineAutoscalerTargetLink: React.FC = ({ obj }) => { const targetAPIVersion: string = _.get(obj, 'spec.scaleTargetRef.apiVersion'); const targetKind: string = _.get(obj, 'spec.scaleTargetRef.kind'); const targetName: string = _.get(obj, 'spec.scaleTargetRef.name'); @@ -27,7 +32,9 @@ const MachineAutoscalerTargetLink: React.FC = } const groupVersion = groupVersionFor(targetAPIVersion); - const reference = referenceForGroupVersionKind(groupVersion.group)(groupVersion.version)(targetKind); + const reference = referenceForGroupVersionKind(groupVersion.group)(groupVersion.version)( + targetKind, + ); return ; }; @@ -43,37 +50,57 @@ const tableColumnClasses = [ const MachineAutoscalerTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Scale Target', sortField: 'spec.scaleTargetRef.name', transforms: [sortable], + title: 'Scale Target', + sortField: 'spec.scaleTargetRef.name', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Min', sortField: 'spec.minReplicas', transforms: [sortable], + title: 'Min', + sortField: 'spec.minReplicas', + transforms: [sortable], props: { className: tableColumnClasses[3] }, }, { - title: 'Max', sortField: 'spec.maxReplicas', transforms: [sortable], + title: 'Max', + sortField: 'spec.maxReplicas', + transforms: [sortable], props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; MachineAutoscalerTableHeader.displayName = 'MachineAutoscalerTableHeader'; -const MachineAutoscalerTableRow: React.FC = ({obj, index, key, style}) => { +const MachineAutoscalerTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + @@ -101,50 +128,55 @@ type MachineAutoscalerTableRowProps = { style: object; }; -const MachineAutoscalerList: React.FC = props => ; +const MachineAutoscalerList: React.FC = (props) => ( +
    +); -const MachineAutoscalerDetails: React.FC = ({obj}) => { - return -
    - - -
    Scale Target
    -
    -
    Min Replicas
    -
    - {_.get(obj, 'spec.minReplicas') || '-'} -
    -
    Max Replicas
    -
    - {_.get(obj, 'spec.maxReplicas') || '-'} -
    -
    -
    -
    ; +const MachineAutoscalerDetails: React.FC = ({ obj }) => { + return ( + +
    + + +
    Scale Target
    +
    + +
    +
    Min Replicas
    +
    {_.get(obj, 'spec.minReplicas') || '-'}
    +
    Max Replicas
    +
    {_.get(obj, 'spec.maxReplicas') || '-'}
    +
    +
    +
    + ); }; -export const MachineAutoscalerPage: React.FC = props => +export const MachineAutoscalerPage: React.FC = (props) => ( ; + /> +); -export const MachineAutoscalerDetailsPage: React.FC = props => ; +export const MachineAutoscalerDetailsPage: React.FC = ( + props, +) => ( + +); type MachineAutoscalerPageProps = { showTitle?: boolean; diff --git a/frontend/public/components/machine-config-pool.tsx b/frontend/public/components/machine-config-pool.tsx index 0b8c7607907..41a85b10093 100644 --- a/frontend/public/components/machine-config-pool.tsx +++ b/frontend/public/components/machine-config-pool.tsx @@ -14,13 +14,7 @@ import { MachineConfigPoolKind, referenceForModel, } from '../module/k8s'; -import { - DetailsPage, - ListPage, - Table, - TableRow, - TableData, -} from './factory'; +import { DetailsPage, ListPage, Table, TableRow, TableData } from './factory'; import { Kebab, KebabAction, @@ -38,7 +32,7 @@ import { ResourceEventStream } from './events'; const pauseAction: KebabAction = (kind, obj) => ({ label: obj.spec.paused ? 'Resume Updates' : 'Pause Updates', - callback: () => togglePaused(kind, obj).catch((err) => errorModal({error: err.message})), + callback: () => togglePaused(kind, obj).catch((err) => errorModal({ error: err.message })), accessReview: { group: kind.apiGroup, resource: kind.plural, @@ -54,139 +48,158 @@ const machineConfigPoolMenuActions = [ ...Kebab.getExtensionsActionsForKind(MachineConfigPoolModel), ...Kebab.factory.common, ]; -const getConditionStatus = (mcp: MachineConfigPoolKind, type: MachineConfigPoolConditionType): K8sResourceConditionStatus => { - const {conditions} = mcp.status; - const condition = _.find(conditions, {type}); +const getConditionStatus = ( + mcp: MachineConfigPoolKind, + type: MachineConfigPoolConditionType, +): K8sResourceConditionStatus => { + const { conditions } = mcp.status; + const condition = _.find(conditions, { type }); return condition ? condition.status : K8sResourceConditionStatus.Unknown; }; -const MachineConfigPoolCharacteristics: React.SFC = ({obj}) => { +const MachineConfigPoolCharacteristics: React.SFC = ({ + obj, +}) => { const configuration = _.get(obj, 'status.configuration'); const maxUnavailable = _.get(obj, 'spec.maxUnavailable', 1); - return
    -
    Max Unavailable Machines
    -
    {maxUnavailable}
    - { configuration && - -
    Current Configuration
    -
    - { - configuration.name - ? - : '-' - } -
    -
    Current Configuration Source
    -
    - { - configuration.source - ? _.map(configuration.source, ({apiVersion, kind, name}) => - - ) - : '-' - } -
    -
    - } -
    ; + return ( +
    +
    Max Unavailable Machines
    +
    {maxUnavailable}
    + {configuration && ( + +
    Current Configuration
    +
    + {configuration.name ? ( + + ) : ( + '-' + )} +
    +
    Current Configuration Source
    +
    + {configuration.source + ? _.map(configuration.source, ({ apiVersion, kind, name }) => ( + + )) + : '-'} +
    +
    + )} +
    + ); }; -const MachineConfigPoolCounts: React.SFC = ({obj}) => { - - return
    -
    -
    -
    -
    -
    Total Machine Count
    -
    - - {pluralize(_.get(obj, 'status.machineCount', 0), 'machine')} - -
    -
    -
    -
    -
    -
    Ready Machines
    -
    - - {pluralize(_.get(obj, 'status.readyMachineCount', 0), 'machine')} - -
    -
    -
    -
    -
    -
    Updated Count
    -
    - - {pluralize(_.get(obj, 'status.updatedMachineCount', 0), 'machine')} - -
    -
    -
    -
    -
    -
    Unavailable Count
    -
    - - {pluralize(_.get(obj, 'status.unavailableMachineCount', 0), 'machine')} - -
    -
    +const MachineConfigPoolCounts: React.SFC = ({ obj }) => { + return ( +
    +
    +
    +
    +
    +
    Total Machine Count
    +
    + + {pluralize(_.get(obj, 'status.machineCount', 0), 'machine')} + +
    +
    +
    +
    +
    +
    Ready Machines
    +
    + + {pluralize(_.get(obj, 'status.readyMachineCount', 0), 'machine')} + +
    +
    +
    +
    +
    +
    Updated Count
    +
    + + {pluralize(_.get(obj, 'status.updatedMachineCount', 0), 'machine')} + +
    +
    +
    +
    +
    +
    Unavailable Count
    +
    + + + {pluralize(_.get(obj, 'status.unavailableMachineCount', 0), 'machine')} + + +
    +
    +
    -
    ; + ); }; -const MachineConfigPoolSummary: React.SFC = ({obj}) => { +const MachineConfigPoolSummary: React.SFC = ({ obj }) => { const machineConfigSelector = _.get(obj, 'spec.machineConfigSelector'); const machineSelector = _.get(obj, 'spec.machineSelector'); - return -
    Machine Config Selector
    -
    - -
    -
    Machine Selector
    -
    - -
    -
    ; + return ( + +
    Machine Config Selector
    +
    + +
    +
    Machine Selector
    +
    + +
    +
    + ); }; -const MachineConfigList: React.SFC = ({obj}) => ( - +const MachineConfigList: React.SFC = ({ obj }) => ( + ); -const MachineConfigPoolDetails: React.SFC = ({obj}) => { +const MachineConfigPoolDetails: React.SFC = ({ obj }) => { const paused = _.get(obj, 'spec.paused'); - return -
    - - {paused && } - -
    -
    - -
    -
    - + return ( + +
    + + {paused && } + +
    +
    + +
    +
    + +
    -
    -
    - - -
    - ; +
    + + +
    + + ); }; const pages = [ @@ -196,7 +209,7 @@ const pages = [ navFactory.events(ResourceEventStream), ]; -export const MachineConfigPoolDetailsPage: React.SFC = props => ( +export const MachineConfigPoolDetailsPage: React.SFC = (props) => ( { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Configuration', sortField: 'status.configuration.name', transforms: [sortable], + title: 'Configuration', + sortField: 'status.configuration.name', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Updated', props: { className: tableColumnClasses[2] }, + title: 'Updated', + props: { className: tableColumnClasses[2] }, }, { - title: 'Updating', props: { className: tableColumnClasses[3] }, + title: 'Updating', + props: { className: tableColumnClasses[3] }, }, { - title: 'Degraded', props: { className: tableColumnClasses[4] }, + title: 'Degraded', + props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; MachineConfigPoolTableHeader.displayName = 'MachineConfigPoolTableHeader'; -const MachineConfigPoolTableRow: React.FC = ({obj, index, key, style}) => { +const MachineConfigPoolTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + - {_.get(obj, 'status.configuration.name') ? : '-'} + {_.get(obj, 'status.configuration.name') ? ( + + ) : ( + '-' + )} {getConditionStatus(obj, MachineConfigPoolConditionType.Updated)} @@ -259,7 +297,11 @@ const MachineConfigPoolTableRow: React.FC = ({ob {getConditionStatus(obj, MachineConfigPoolConditionType.Degraded)} - + ); @@ -272,14 +314,17 @@ type MachineConfigPoolTableRowProps = { style: object; }; -const MachineConfigPoolList: React.SFC = props =>
    ; +const MachineConfigPoolList: React.SFC = (props) => ( +
    +); -export const MachineConfigPoolPage: React.SFC = props => ( +export const MachineConfigPoolPage: React.SFC = (props) => ( = ({obj}) => ( +const MachineConfigSummary: React.SFC = ({ obj }) => (
    OS Image URL
    {obj.spec.osImageURL || '-'}
    ); -const MachineConfigDetails: React.SFC = ({obj}) => ( +const MachineConfigDetails: React.SFC = ({ obj }) => (
    @@ -50,8 +46,15 @@ const pages = [ navFactory.events(ResourceEventStream), ]; -export const MachineConfigDetailsPage: React.SFC = props => { - return ; +export const MachineConfigDetailsPage: React.SFC = (props) => { + return ( + + ); }; const tableColumnClasses = [ @@ -66,41 +69,69 @@ const tableColumnClasses = [ const MachineConfigTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { title: 'Generated By Controller', - sortField: 'metadata.annotations[\'machineconfiguration.openshift.io/generated-by-controller-version\']', - transforms: [sortable], props: { className: tableColumnClasses[1] }, + sortField: + "metadata.annotations['machineconfiguration.openshift.io/generated-by-controller-version']", + transforms: [sortable], + props: { className: tableColumnClasses[1] }, }, { - title: 'Ignition Version', sortField: 'spec.config.ignition.version', transforms: [sortable], + title: 'Ignition Version', + sortField: 'spec.config.ignition.version', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'OS Image URL', sortField: 'spec.osImageURL', - transforms: [sortable], props: { className: tableColumnClasses[3] }, + title: 'OS Image URL', + sortField: 'spec.osImageURL', + transforms: [sortable], + props: { className: tableColumnClasses[3] }, }, { - title: 'Created', sortField: 'metadata.creationTimestamp', - transforms: [sortable], props: { className: tableColumnClasses[4] }, + title: 'Created', + sortField: 'metadata.creationTimestamp', + transforms: [sortable], + props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; MachineConfigTableHeader.displayName = 'MachineConfigTableHeader'; -const MachineConfigTableRow: React.FC = ({obj, index, key, style}) => { +const MachineConfigTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + - { _.get(obj, ['metadata', 'annotations', 'machineconfiguration.openshift.io/generated-by-controller-version'], '-')} + {_.get( + obj, + [ + 'metadata', + 'annotations', + 'machineconfiguration.openshift.io/generated-by-controller-version', + ], + '-', + )} {_.get(obj, 'spec.config.ignition.version') || '-'} @@ -112,7 +143,11 @@ const MachineConfigTableRow: React.FC = ({obj, index {fromNow(obj.metadata.creationTimestamp)} - + ); @@ -125,14 +160,17 @@ type MachineConfigTableRowProps = { style: object; }; -const MachineConfigList: React.SFC = props =>
    ; +const MachineConfigList: React.SFC = (props) => ( +
    +); -export const MachineConfigPage: React.SFC = ({canCreate = true, ...rest}) => ( +export const MachineConfigPage: React.SFC = ({ canCreate = true, ...rest }) => ( { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Machines', sortField: 'status.replicas', transforms: [sortable], + title: 'Machines', + sortField: 'status.replicas', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: '', props: { className: tableColumnClasses[3] }, + title: '', + props: { className: tableColumnClasses[3] }, }, ]; }; MachineDeploymentTableHeader.displayName = 'MachineDeploymentTableHeader'; -const MachineDeploymentTableRow: React.FC = ({obj, index, key, style}) => { +const MachineDeploymentTableRow: React.FC = ({ + obj, + index, + key, + style, +}) => { return ( - + - + {getReadyReplicas(obj)} of {getDesiredReplicas(obj)} machines @@ -89,82 +116,116 @@ type MachineDeploymentTableRowProps = { style: object; }; -const MachineDeploymentDetails: React.SFC = ({obj}) => { +const MachineDeploymentDetails: React.SFC = ({ obj }) => { const machineRole = getMachineRole(obj); const { availabilityZone, region } = getAWSPlacement(obj); const { minReadySeconds, progressDeadlineSeconds } = obj.spec; const rollingUpdateStrategy = _.get(obj, 'spec.strategy.rollingUpdate'); - return -
    - - -
    -
    - -
    Selector
    -
    - -
    - {machineRole && -
    Machine Role
    -
    {machineRole}
    -
    } - {region && -
    Region
    -
    {region}
    -
    } - {availabilityZone && -
    Availability Zone
    -
    {availabilityZone}
    -
    } -
    -
    -
    -
    -
    Strategy
    -
    {_.get(obj, 'spec.strategy.type') || '-'}
    - {rollingUpdateStrategy && -
    Max Unavailable
    -
    {rollingUpdateStrategy.maxUnavailable || 0} of {pluralize(obj.spec.replicas, 'machine')}
    -
    Max Surge
    -
    {rollingUpdateStrategy.maxSurge || 1} greater than {pluralize(obj.spec.replicas, 'machine')}
    -
    } -
    Min Ready Seconds
    -
    {minReadySeconds ? pluralize(minReadySeconds, 'second') : 'Not Configured'}
    - {progressDeadlineSeconds &&
    Progress Deadline
    } - {progressDeadlineSeconds &&
    {/* Convert to ms for formatDuration */ formatDuration(progressDeadlineSeconds * 1000)}
    } -
    + return ( + +
    + + +
    +
    + +
    Selector
    +
    + +
    + {machineRole && ( + +
    Machine Role
    +
    {machineRole}
    +
    + )} + {region && ( + +
    Region
    +
    {region}
    +
    + )} + {availabilityZone && ( + +
    Availability Zone
    +
    {availabilityZone}
    +
    + )} +
    +
    +
    +
    +
    Strategy
    +
    {_.get(obj, 'spec.strategy.type') || '-'}
    + {rollingUpdateStrategy && ( + +
    Max Unavailable
    +
    + {rollingUpdateStrategy.maxUnavailable || 0} of{' '} + {pluralize(obj.spec.replicas, 'machine')} +
    +
    Max Surge
    +
    + {rollingUpdateStrategy.maxSurge || 1} greater than{' '} + {pluralize(obj.spec.replicas, 'machine')} +
    +
    + )} +
    Min Ready Seconds
    +
    {minReadySeconds ? pluralize(minReadySeconds, 'second') : 'Not Configured'}
    + {progressDeadlineSeconds &&
    Progress Deadline
    } + {progressDeadlineSeconds && ( +
    + {/* Convert to ms for formatDuration */ formatDuration( + progressDeadlineSeconds * 1000, + )} +
    + )} +
    +
    -
    - ; + + ); }; -export const MachineDeploymentList: React.SFC = props =>
    ; +export const MachineDeploymentList: React.SFC = (props) => ( +
    +); -export const MachineDeploymentPage: React.SFC = props => +export const MachineDeploymentPage: React.SFC = (props) => ( ; + /> +); -export const MachineDeploymentDetailsPage: React.SFC = props => ; +export const MachineDeploymentDetailsPage: React.SFC = ( + props, +) => ( + +); export type MachineDeploymentDetailsProps = { obj: MachineDeploymentKind; diff --git a/frontend/public/components/machine-set.tsx b/frontend/public/components/machine-set.tsx index ae17729f04f..8378b0260d4 100644 --- a/frontend/public/components/machine-set.tsx +++ b/frontend/public/components/machine-set.tsx @@ -4,7 +4,7 @@ import { Link } from 'react-router-dom'; import { sortable } from '@patternfly/react-table'; import * as classNames from 'classnames'; import { getMachineRole } from '@console/shared'; -import { Tooltip , Button } from '@patternfly/react-core'; +import { Tooltip, Button } from '@patternfly/react-core'; import { PencilAltIcon } from '@patternfly/react-icons'; @@ -28,13 +28,20 @@ import { } from './utils'; import { ResourceEventStream } from './events'; -const machineReplicasModal = (resourceKind: K8sKind, resource: MachineSetKind | MachineDeploymentKind) => configureReplicaCountModal({ - resourceKind, - resource, - message: `${resourceKind.labelPlural} maintain the proper number of healthy machines.`, -}); +const machineReplicasModal = ( + resourceKind: K8sKind, + resource: MachineSetKind | MachineDeploymentKind, +) => + configureReplicaCountModal({ + resourceKind, + resource, + message: `${resourceKind.labelPlural} maintain the proper number of healthy machines.`, + }); -export const editCountAction: KebabAction = (kind: K8sKind, resource: MachineSetKind | MachineDeploymentKind) => ({ +export const editCountAction: KebabAction = ( + kind: K8sKind, + resource: MachineSetKind | MachineDeploymentKind, +) => ({ label: 'Edit Count', callback: () => machineReplicasModal(kind, resource), accessReview: { @@ -48,7 +55,7 @@ export const editCountAction: KebabAction = (kind: K8sKind, resource: MachineSet const configureMachineAutoscaler: KebabAction = (kind: K8sKind, machineSet: MachineSetKind) => ({ label: 'Create Autoscaler', - callback: () => configureMachineAutoscalerModal({machineSet, cancel: _.noop, close: _.noop}), + callback: () => configureMachineAutoscalerModal({ machineSet, cancel: _.noop, close: _.noop }), accessReview: { group: MachineAutoscalerModel.apiGroup, resource: MachineAutoscalerModel.plural, @@ -66,7 +73,8 @@ const menuActions = [ ]; const machineReference = referenceForModel(MachineModel); const machineSetReference = referenceForModel(MachineSetModel); -export const getAWSPlacement = (machineSet: MachineSetKind | MachineDeploymentKind) => _.get(machineSet, 'spec.template.spec.providerSpec.value.placement') || {}; +export const getAWSPlacement = (machineSet: MachineSetKind | MachineDeploymentKind) => + _.get(machineSet, 'spec.template.spec.providerSpec.value.placement') || {}; // `spec.replicas` defaults to 1 if not specified. Make sure to differentiate between undefined and 0. export const getDesiredReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => { @@ -74,9 +82,12 @@ export const getDesiredReplicas = (machineSet: MachineSetKind | MachineDeploymen return _.isNil(replicas) ? 1 : replicas; }; -const getReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => _.get(machineSet, 'status.replicas', 0); -export const getReadyReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => _.get(machineSet, 'status.readyReplicas', 0); -export const getAvailableReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => _.get(machineSet, 'status.availableReplicas', 0); +const getReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => + _.get(machineSet, 'status.replicas', 0); +export const getReadyReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => + _.get(machineSet, 'status.readyReplicas', 0); +export const getAvailableReplicas = (machineSet: MachineSetKind | MachineDeploymentKind) => + _.get(machineSet, 'status.availableReplicas', 0); const tableColumnClasses = [ classNames('col-sm-4', 'col-xs-6'), @@ -88,35 +99,52 @@ const tableColumnClasses = [ const MachineSetTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Machines', sortField: 'status.replicas', transforms: [sortable], + title: 'Machines', + sortField: 'status.replicas', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: '', props: { className: tableColumnClasses[3] }, + title: '', + props: { className: tableColumnClasses[3] }, }, ]; }; MachineSetTableHeader.displayName = 'MachineSetTableHeader'; -const MachineSetTableRow: React.FC = ({obj, index, key, style}) => { +const MachineSetTableRow: React.FC = ({ obj, index, key, style }) => { return ( - + - + {getReadyReplicas(obj)} of {getDesiredReplicas(obj)} machines @@ -134,7 +162,13 @@ type MachineSetTableRowProps = { style: object; }; -export const MachineCounts: React.SFC = ({resourceKind, resource}: {resourceKind: K8sKind, resource: MachineSetKind | MachineDeploymentKind}) => { +export const MachineCounts: React.SFC = ({ + resourceKind, + resource, +}: { + resourceKind: K8sKind; + resource: MachineSetKind | MachineDeploymentKind; +}) => { const editReplicas = (event) => { event.preventDefault(); machineReplicasModal(resourceKind, resource); @@ -154,114 +188,137 @@ export const MachineCounts: React.SFC = ({resourceKind, reso }); const desiredReplicasText = pluralize(desiredReplicas, 'machine'); - return
    -
    -
    -
    -
    -
    Desired Count
    -
    - {canUpdate - ? - : desiredReplicasText} -
    -
    -
    -
    -
    -
    Current Count
    -
    - - {pluralize(replicas, 'machine')} - -
    -
    -
    -
    -
    -
    Ready Count
    -
    - - {pluralize(readyReplicas, 'machine')} - -
    -
    -
    -
    -
    -
    Available Count
    -
    - - {pluralize(availableReplicas, 'machine')} - -
    -
    + return ( +
    +
    +
    +
    +
    +
    Desired Count
    +
    + {canUpdate ? ( + + ) : ( + desiredReplicasText + )} +
    +
    +
    +
    +
    +
    Current Count
    +
    + + {pluralize(replicas, 'machine')} + +
    +
    +
    +
    +
    +
    Ready Count
    +
    + + {pluralize(readyReplicas, 'machine')} + +
    +
    +
    +
    +
    +
    Available Count
    +
    + + {pluralize(availableReplicas, 'machine')} + +
    +
    +
    -
    ; + ); }; -export const MachineTabPage: React.SFC = ({obj}: {obj: MachineSetKind}) => - ; +export const MachineTabPage: React.SFC = ({ + obj, +}: { + obj: MachineSetKind; +}) => ( + +); -const MachineSetDetails: React.SFC = ({obj}) => { +const MachineSetDetails: React.SFC = ({ obj }) => { const machineRole = getMachineRole(obj); const { availabilityZone, region } = getAWSPlacement(obj); - return -
    - - - -
    Selector
    -
    - -
    - {machineRole && -
    Machine Role
    -
    {machineRole}
    -
    } - {region && -
    Region
    -
    {region}
    -
    } - {availabilityZone && -
    Availability Zone
    -
    {availabilityZone}
    -
    } -
    -
    -
    ; + return ( + +
    + + + +
    Selector
    +
    + +
    + {machineRole && ( + +
    Machine Role
    +
    {machineRole}
    +
    + )} + {region && ( + +
    Region
    +
    {region}
    +
    + )} + {availabilityZone && ( + +
    Availability Zone
    +
    {availabilityZone}
    +
    + )} +
    +
    +
    + ); }; -export const MachineSetList: React.SFC = props =>
    ; +export const MachineSetList: React.SFC = (props) => ( +
    +); -export const MachineSetPage: React.SFC = props => - = (props) => ( + +); + +export const MachineSetDetailsPage: React.SFC = (props) => ( + ; - -export const MachineSetDetailsPage: React.SFC = props => ; + pages={[ + navFactory.details(MachineSetDetails), + navFactory.editYaml(), + navFactory.machines(MachineTabPage), + navFactory.events(ResourceEventStream), + ]} + /> +); export type MachineCountsProps = { resourceKind: K8sKind; diff --git a/frontend/public/components/machine.tsx b/frontend/public/components/machine.tsx index a04f9f803aa..97dd17b16a6 100644 --- a/frontend/public/components/machine.tsx +++ b/frontend/public/components/machine.tsx @@ -2,7 +2,12 @@ import * as React from 'react'; import * as _ from 'lodash-es'; import { sortable } from '@patternfly/react-table'; import * as classNames from 'classnames'; -import { getMachineNodeName, getMachineRegion, getMachineRole, getMachineZone } from '@console/shared'; +import { + getMachineNodeName, + getMachineRegion, + getMachineRole, + getMachineZone, +} from '@console/shared'; import { MachineModel } from '../models'; import { MachineKind, referenceForModel } from '../module/k8s'; import { Conditions } from './conditions'; @@ -35,40 +40,56 @@ const tableColumnClasses = [ const MachineTableHeader = () => { return [ { - title: 'Name', sortField: 'metadata.name', transforms: [sortable], + title: 'Name', + sortField: 'metadata.name', + transforms: [sortable], props: { className: tableColumnClasses[0] }, }, { - title: 'Namespace', sortField: 'metadata.namespace', transforms: [sortable], + title: 'Namespace', + sortField: 'metadata.namespace', + transforms: [sortable], props: { className: tableColumnClasses[1] }, }, { - title: 'Node', sortField: 'status.nodeRef.name', transforms: [sortable], + title: 'Node', + sortField: 'status.nodeRef.name', + transforms: [sortable], props: { className: tableColumnClasses[2] }, }, { - title: 'Region', sortField: 'metadata.labels[\'machine.openshift.io/region\']', - transforms: [sortable], props: { className: tableColumnClasses[3] }, + title: 'Region', + sortField: "metadata.labels['machine.openshift.io/region']", + transforms: [sortable], + props: { className: tableColumnClasses[3] }, }, { - title: 'Availability Zone', sortField: 'metadata.labels[\'machine.openshift.io/zone\']', - transforms: [sortable], props: { className: tableColumnClasses[4] }, + title: 'Availability Zone', + sortField: "metadata.labels['machine.openshift.io/zone']", + transforms: [sortable], + props: { className: tableColumnClasses[4] }, }, { - title: '', props: { className: tableColumnClasses[5] }, + title: '', + props: { className: tableColumnClasses[5] }, }, ]; }; MachineTableHeader.displayName = 'MachineTableHeader'; -const MachineTableRow: React.FC = ({obj, index, key, style}) => { +const MachineTableRow: React.FC = ({ obj, index, key, style }) => { const nodeName = getMachineNodeName(obj); const region = getMachineRegion(obj); const zone = getMachineZone(obj); return ( - + @@ -76,12 +97,8 @@ const MachineTableRow: React.FC = ({obj, index, key, style {nodeName ? : '-'} - - {region || '-'} - - - {zone || '-'} - + {region || '-'} + {zone || '-'} @@ -96,45 +113,67 @@ type MachineTableRowProps = { style: object; }; -const MachineDetails: React.SFC = ({obj}: {obj: MachineKind}) => { +const MachineDetails: React.SFC = ({ obj }: { obj: MachineKind }) => { const nodeName = getMachineNodeName(obj); const machineRole = getMachineRole(obj); const region = getMachineRegion(obj); const zone = getMachineZone(obj); - return -
    - - - {nodeName && -
    Node
    -
    -
    } - {machineRole && -
    Machine Role
    -
    {machineRole}
    -
    } - {region && -
    Region
    -
    {region}
    -
    } - {zone && -
    Availability Zone
    -
    {zone}
    -
    } -
    Machine Addresses
    -
    -
    -
    -
    - - -
    -
    ; + return ( + +
    + + + {nodeName && ( + +
    Node
    +
    + +
    +
    + )} + {machineRole && ( + +
    Machine Role
    +
    {machineRole}
    +
    + )} + {region && ( + +
    Region
    +
    {region}
    +
    + )} + {zone && ( + +
    Availability Zone
    +
    {zone}
    +
    + )} +
    Machine Addresses
    +
    + +
    +
    +
    +
    + + +
    +
    + ); }; -export const MachineList: React.SFC = props =>
    ; +export const MachineList: React.SFC = (props) => ( +
    +); -export const MachinePage: React.SFC = props => +export const MachinePage: React.SFC = (props) => ( = props => textFilter="machine" filterLabel="by machine or node name" canCreate - />; + /> +); -export const MachineDetailsPage: React.SFC = props => +export const MachineDetailsPage: React.SFC = (props) => ( = props => navFactory.editYaml(), navFactory.events(ResourceEventStream), ]} - />; + /> +); export type MachineDetailsProps = { obj: MachineKind; diff --git a/frontend/public/components/markdown-view.tsx b/frontend/public/components/markdown-view.tsx index fbd2e175a77..55514450f74 100644 --- a/frontend/public/components/markdown-view.tsx +++ b/frontend/public/components/markdown-view.tsx @@ -3,7 +3,7 @@ import * as _ from 'lodash-es'; import { Converter } from 'showdown'; import * as sanitizeHtml from 'sanitize-html'; -const tableTags = [ 'table', 'thead', 'tbody', 'tr', 'th', 'td']; +const tableTags = ['table', 'thead', 'tbody', 'tr', 'th', 'td']; const markdownConvert = (markdown) => { const unsafeHtml = new Converter({ @@ -14,18 +14,41 @@ const markdownConvert = (markdown) => { }).makeHtml(markdown); return sanitizeHtml(unsafeHtml, { - allowedTags: ['b', 'i', 'strike', 's', 'del', 'em', 'strong', 'a', 'p', 'h1', 'h2', 'h3', 'h4', 'ul', 'ol', 'li', 'code', 'pre', ...tableTags], + allowedTags: [ + 'b', + 'i', + 'strike', + 's', + 'del', + 'em', + 'strong', + 'a', + 'p', + 'h1', + 'h2', + 'h3', + 'h4', + 'ul', + 'ol', + 'li', + 'code', + 'pre', + ...tableTags, + ], allowedAttributes: { - 'a': ['href', 'target', 'rel'], + a: ['href', 'target', 'rel'], }, allowedSchemes: ['http', 'https', 'mailto'], transformTags: { - 'a': sanitizeHtml.simpleTransform('a', {rel: 'noopener noreferrer'}, true), + a: sanitizeHtml.simpleTransform('a', { rel: 'noopener noreferrer' }, true), }, }); }; -export class SyncMarkdownView extends React.Component<{content: string, styles?: string, exactHeight?: boolean}, {}> { +export class SyncMarkdownView extends React.Component< + { content: string; styles?: string; exactHeight?: boolean }, + {} +> { private frame: any; constructor(props) { @@ -37,8 +60,11 @@ export class SyncMarkdownView extends React.Component<{content: string, styles?: } updateDimensions() { - if (!this.frame || !this.frame.contentWindow.document.body || - !this.frame.contentWindow.document.body.firstChild) { + if ( + !this.frame || + !this.frame.contentWindow.document.body || + !this.frame.contentWindow.document.body.firstChild + ) { return; } this.frame.style.height = `${this.frame.contentWindow.document.body.firstChild.scrollHeight}px`; @@ -46,23 +72,29 @@ export class SyncMarkdownView extends React.Component<{content: string, styles?: // Let the new height take effect, then reset again once we recompute setTimeout(() => { if (this.props.exactHeight) { - this.frame.style.height = `${this.frame.contentWindow.document.body.firstChild.scrollHeight}px`; + this.frame.style.height = `${ + this.frame.contentWindow.document.body.firstChild.scrollHeight + }px`; } else { // Increase by 15px for the case where a horizontal scrollbar might appear - this.frame.style.height = `${this.frame.contentWindow.document.body.firstChild.scrollHeight + 15}px`; + this.frame.style.height = `${this.frame.contentWindow.document.body.firstChild + .scrollHeight + 15}px`; } }); } render() { // Find the app's stylesheets and inject them into the frame to ensure consistent styling. - const filteredLinks = Array.from(document.getElementsByTagName('link')).filter((l) => _.includes(l.href, 'app-bundle')); + const filteredLinks = Array.from(document.getElementsByTagName('link')).filter((l) => + _.includes(l.href, 'app-bundle'), + ); const linkRefs = _.reduce( filteredLinks, (refs, link) => `${refs} `, - ''); + '', + ); const contents = ` ${linkRefs} @@ -88,7 +120,17 @@ export class SyncMarkdownView extends React.Component<{content: string, styles?: } ${this.props.styles ? this.props.styles : ''} -
    ${markdownConvert(this.props.content || 'Not available')}
    `; - return