diff --git a/.changeset/config.json b/.changeset/config.json index c7c4f11e57..44a8523265 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -2,55 +2,7 @@ "$schema": "https://unpkg.com/@changesets/config@1.3.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, - "linked": [ - [ - "example-app", - "@backstage/backend-common", - "example-backend", - "@backstage/catalog-model", - "@backstage/cli-common", - "@backstage/cli", - "@backstage/config-loader", - "@backstage/config", - "@backstage/core-api", - "@backstage/core", - "@backstage/create-app", - "@backstage/dev-utils", - "docgen", - "e2e-test", - "storybook", - "@techdocs/cli", - "@backstage/test-utils-core", - "@backstage/test-utils", - "@backstage/theme", - "@backstage/plugin-api-docs", - "@backstage/plugin-app-backend", - "@backstage/plugin-auth-backend", - "@backstage/plugin-catalog-backend", - "@backstage/plugin-catalog", - "@backstage/plugin-circleci", - "@backstage/plugin-gcp-projects", - "@backstage/plugin-github-actions", - "@backstage/plugin-gitops-profiles", - "@backstage/plugin-graphiql", - "@backstage/plugin-graphql-backend", - "@backstage/plugin-jenkins", - "@backstage/plugin-lighthouse", - "@backstage/plugin-newrelic", - "@backstage/plugin-proxy-backend", - "@backstage/plugin-register-component", - "@backstage/plugin-rollbar-backend", - "@backstage/plugin-rollbar", - "@backstage/plugin-scaffolder-backend", - "@backstage/plugin-scaffolder", - "@backstage/plugin-sentry-backend", - "@backstage/plugin-sentry", - "@backstage/plugin-tech-radar", - "@backstage/plugin-techdocs-backend", - "@backstage/plugin-techdocs", - "@backstage/plugin-welcome" - ] - ], + "linked": [["*"]], "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", diff --git a/.changeset/fuzzy-falcons-happen.md b/.changeset/fuzzy-falcons-happen.md new file mode 100644 index 0000000000..461ac195d9 --- /dev/null +++ b/.changeset/fuzzy-falcons-happen.md @@ -0,0 +1,5 @@ +--- +'e2e-test': minor +--- + +Converted into a CLI, use `yarn e2e-test run` to run diff --git a/.changeset/good-islands-cheer.md b/.changeset/good-islands-cheer.md new file mode 100644 index 0000000000..2e7109b312 --- /dev/null +++ b/.changeset/good-islands-cheer.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': minor +--- + +Enable custom alert types in Cost Insights diff --git a/.changeset/old-eagles-admire.md b/.changeset/old-eagles-admire.md new file mode 100644 index 0000000000..0729b6439d --- /dev/null +++ b/.changeset/old-eagles-admire.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-common': minor +'@backstage/cli': minor +--- + +Use APP_ENV before NODE_ENV for determining what config to load diff --git a/.changeset/proud-eagles-grow.md b/.changeset/proud-eagles-grow.md new file mode 100644 index 0000000000..ef83a4ad26 --- /dev/null +++ b/.changeset/proud-eagles-grow.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Fixed duplicate help output, and print help on invalid command diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 01fc8e70c3..de7ce773bb 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,12 +5,9 @@ #### :heavy_check_mark: Checklist - + -- [ ] All tests are passing `yarn test` +- [ ] A changeset describing the change and affected packages. ([more info](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md#creating-changesets)) +- [ ] Added or updated documentation +- [ ] Tests for new functionality and regression tests for bug fixes - [ ] Screenshots attached (for UI changes) -- [ ] Relevant documentation updated -- [ ] Prettier run on changed files -- [ ] Tests added for new functionality -- [ ] Regression tests added for bug fixes -- [ ] Added a changeset ([more info](https://github.com/spotify/backstage/blob/master/CONTRIBUTING.md#creating-changesets)) diff --git a/.github/codecov.yml b/.github/codecov.yml index 1b8e770b59..d39038615f 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -6,6 +6,7 @@ coverage: default: threshold: 0% # Ref: https://docs.codecov.io/docs/codecovyml-reference#coveragestatus target: auto + informational: true # Don't block PRs # Since Backstage is a mono repo, flags here help in getting the code coverage of individual packages. # Documentation: https://docs.codecov.io/docs/flags diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index c9774ae237..c20a9862b0 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -141,6 +141,7 @@ Raghunandan rankdir readme Readme +Recharts Redash replicasets repo @@ -204,3 +205,5 @@ cloudbuild Grafana Iain Snyk +www +WWW diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 65e092676d..d00f304b58 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,7 @@ jobs: if: ${{ steps.yarn-lock.outcome == 'failure' }} run: | yarn lerna -- run test -- --coverage - bash <(curl -s https://codecov.io/bash) + bash <(curl -s https://codecov.io/bash) -N $(git rev-parse FETCH_HEAD) - name: verify plugin template run: yarn lerna -- run diff -- --check diff --git a/.github/workflows/e2e-win.yml b/.github/workflows/e2e-win.yml index c48d779962..570d857c48 100644 --- a/.github/workflows/e2e-win.yml +++ b/.github/workflows/e2e-win.yml @@ -40,4 +40,4 @@ jobs: - name: yarn build run: yarn build --ignore example-app --ignore example-backend --ignore @techdocs/cli - name: run E2E test - run: yarn workspace e2e-test start + run: yarn e2e-test run diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e280bf59a3..62be0a6cea 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -70,7 +70,7 @@ jobs: - name: run E2E test run: | sudo sysctl fs.inotify.max_user_watches=524288 - yarn workspace e2e-test start + yarn e2e-test run env: POSTGRES_HOST: localhost POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 487b94a39d..0bcce2966f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ If you encounter issues while upgrading to a newer version, don't hesitate to re > Collect changes for the next release below +### @backstage/cli + +- The recommended way to set the configuration environment is now to use `APP_ENV` instead of `NODE_ENV`. + ### Backend (example-backend, or backends created with @backstage/create-app) - A plugin database manager has been created, and plugins can now accept that interface as an argument during initialisation. Notably, the `auth` plugin has a [`createRouter` signature change](./plugins/auth-backend/src/service/router.ts). See [packages/backend/src/index.ts](./packages/backend/src/index.ts) on how to set it up. [#2697](https://github.com/spotify/backstage/pull/2697) diff --git a/README.md b/README.md index e05c1eb16e..70badd5321 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,8 @@ # [Backstage](https://backstage.io) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) -![](https://github.com/spotify/backstage/workflows/Frontend%20CI/badge.svg) +[![CNCF Status](https://img.shields.io/badge/cncf%20status-sandbox-blue.svg)](https://www.cncf.io/projects) +[![Main CI Build](https://github.com/spotify/backstage/workflows/Main%20Master%20Build/badge.svg)](https://github.com/spotify/backstage/actions?query=workflow%3A%22Main+Master+Build%22) [![Discord](https://img.shields.io/discord/687207715902193673)](https://discord.gg/EBHEGzX) ![Code style](https://img.shields.io/badge/code_style-prettier-ff69b4.svg) [![Codecov](https://img.shields.io/codecov/c/github/spotify/backstage)](https://codecov.io/gh/spotify/backstage) @@ -24,9 +25,7 @@ Out of the box, Backstage includes: - [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach - Plus, a growing ecosystem of [open source plugins](https://github.com/spotify/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality -For more information go to [backstage.io](https://backstage.io) or join our [Discord chatroom](https://discord.gg/EBHEGzX). - -🎉 Backstage is a CNCF Sandbox project. Read the announcement [here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox). +Backstage was created by Spotify but is now hosted by the [Cloud Native Computing Foundation (CNCF)](https://www.cncf.io) as a Sandbox level project. Read the announcement [here](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox). ## Project roadmap @@ -58,6 +57,6 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how ## License -Copyright 2020 Spotify AB. +Copyright 2020 © Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0 diff --git a/contrib/chart/backstage/Chart.lock b/contrib/chart/backstage/Chart.lock new file mode 100644 index 0000000000..e390bf21ba --- /dev/null +++ b/contrib/chart/backstage/Chart.lock @@ -0,0 +1,6 @@ +dependencies: +- name: postgresql + repository: https://charts.bitnami.com/bitnami + version: 9.4.1 +digest: sha256:f949ec0fe7d146610ce2ee78fbfb4e52d235883a797968b0a1e61aa88ada2786 +generated: "2020-09-25T08:59:54.255582519+02:00" diff --git a/contrib/chart/backstage/Chart.yaml b/contrib/chart/backstage/Chart.yaml new file mode 100644 index 0000000000..7d2a489199 --- /dev/null +++ b/contrib/chart/backstage/Chart.yaml @@ -0,0 +1,30 @@ +apiVersion: v2 +name: backstage +description: A Helm chart for Spotify Backstage +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. +appVersion: v0.1.1-alpha.23 + +sources: + - https://github.com/spotify/backstage + - https://github.com/spotify/lighthouse-audit-service + +dependencies: + - name: postgresql + condition: postgresql.enabled + version: 9.4.1 + repository: https://charts.bitnami.com/bitnami + +maintainers: + - name: Martina Iglesias Fernández + email: martina@roadie.io + url: https://roadie.io + - name: David Tuite + email: david@roadie.io + url: https://roadie.io diff --git a/contrib/chart/backstage/README.md b/contrib/chart/backstage/README.md new file mode 100644 index 0000000000..4f247252b1 --- /dev/null +++ b/contrib/chart/backstage/README.md @@ -0,0 +1,218 @@ +# Backstage demo helm charts + +This folder contains Helm charts that can easily create a Kubernetes deployment of a demo Backstage app. + +### Pre-requisites + +These charts depend on the `nginx-ingress` controller being present in the cluster. If it's not already installed you +can run: + +``` +helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx +helm install nginx-ingress ingress-nginx/ingress-nginx +``` + +### Installing the charts + +After choosing a DNS name where backstage will be hosted create a yaml file for your custom configuration. + +``` +appConfig: + app: + baseUrl: https://backstage.mydomain.com + title: Backstage + backend: + baseUrl: https://backstage.mydomain.com + cors: + origin: https://backstage.mydomain.com + lighthouse: + baseUrl: https://backstage.mydomain.com/lighthouse-api + techdocs: + storageUrl: https://backstage.mydomain.com/api/techdocs/static/docs + requestUrl: https://backstage.mydomain.com/api/techdocs + +``` + +Then use it to run: + +``` +git clone https://github.com/spotify/backstage.git +cd contrib/chart/backstage +helm dependency update +helm install -f backstage-mydomain.yaml backstage . +``` + +This command will deploy the following pieces: + +- Backstage frontend +- Backstage backend with scaffolder and auth plugins +- (optional) a PostgreSQL instance +- lighthouse plugin +- ingress + +After a few minutes Backstage should be up and running in your cluster under the DNS specified earlier. + +Make sure to create the appropriate DNS entry in your infrastructure. To find the public IP address run: + +```bash +$ kubectl get ingress +NAME HOSTS ADDRESS PORTS AGE +backstage-ingress * 123.1.2.3 80 17m +``` + +> **NOTE**: this is not a production ready deployment. + +## Caveats + +The current implementation does not generate certificates for the ingress which means the browser will alert that the +site is insecure and using self-signed certificates. + +## Customization + +### Custom PostgreSQL instance + +Configuring a connection to an existing PostgreSQL instance is possible through the chart's values. + +First create a yaml file with the configuration you want to override, for example `backstage-prod.yaml`: + +```bash +postgresql: + enabled: false + +appConfig: + app: + baseUrl: https://backstage-demo.mydomain.com + title: Backstage + backend: + baseUrl: https://backstage-demo.mydomain.com + cors: + origin: https://backstage-demo.mydomain.com + database: + client: pg + connection: + database: backstage_plugin_catalog + host: + user: + password: + lighthouse: + baseUrl: https://backstage-demo.mydomain.com/lighthouse-api + +lighthouse: + database: + client: pg + connection: + host: + user: + password: + database: lighthouse_audit_service + +``` + +For the CA, create a `configMap` named `-postgres-ca` with a file called `ca.crt`: + +``` +kubectl create configmap my-backstage --from-file=ca.crt" +``` + +Now install the helm chart: + +``` +cd contrib/chart/backstage +helm install -f backstage-prod.yaml my-backstage . +``` + +### Use your own docker images + +The docker images used for the deployment can be configured through the charts values: + +``` +frontend: + image: + repository: + tag: + +backend: + image: + repository: + tag: + +lighthouse: + image: + repository: +``` + +### Different namespace + +To install the charts a specific namespace use `--namespace `: + +``` +helm install -f my_values.yaml --namespace demos backstage . +``` + +### Disable loading of demo data + +To deploy backstage with the pre-loaded demo data disable `backend.demoData`: + +``` +helm install -f my_values.yaml --set backend.demoData=false backstage . +``` + +### Other options + +For more customization options take a look at the [values.yaml](/contrib/chart/backstage/values.yaml) file. + +## Troubleshooting + +Some resources created by these charts are meant to survive after upgrades and even after uninstalls. When +troubleshooting these charts it can be useful to delete these resources between re-installs. + +Secrets: + +``` +-postgresql-certs -- contains the certificates used by the deployed PostgreSQL +``` + +Persistent volumes: + +``` +data--postgresql-0 -- this is the data volume used by PostgreSQL to store data and configuration +``` + +> **NOTE**: this volume also stores the configuration for PostgreSQL which includes things like the password for the +> `postgres` user. This means that uninstalling and re-installing the charts with `postgres.enabled` set to `true` and +> auto generated passwords will fail. The solution is to delete this volume with +> `kubectl delete pvc data--postgresql-0` + +ConfigMaps: + +``` +-postgres-ca -- contains the generated CA certificate for PostgreSQL when `postgres` is enabled +``` + +#### Unable to verify signature + +``` +Backend failed to start up Error: unable to verify the first certificate + at TLSSocket.onConnectSecure (_tls_wrap.js:1501:34) + at TLSSocket.emit (events.js:315:20) + at TLSSocket._finishInit (_tls_wrap.js:936:8) + at TLSWrap.ssl.onhandshakedone (_tls_wrap.js:710:12) { + code: 'UNABLE_TO_VERIFY_LEAF_SIGNATURE' +``` + +This error happens in the backend when it tries to connect to the configured PostgreSQL database and the specified CA is not correct. The solution is to make sure that the contents of the `configMap` that holds the certificate match the CA for the PostgreSQL instance. A workaround is to set `appConfig.backend.database.connection.ssl.rejectUnauthorized` to `false` in the chart's values. + + + +## Uninstalling Backstage + +To uninstall Backstage simply run: + +``` +RELEASE_NAME= # use `helm list` to find out the name +helm uninstall ${RELEASE_NAME} +kubectl delete pvc data-${RELEASE_NAME}-postgresql-0 +kubectl delete secret ${RELEASE_NAME}-postgresql-certs +kubectl delete configMap ${RELEASE_NAME}-postgres-ca +``` diff --git a/contrib/chart/backstage/files/app-config.development.yaml.tpl b/contrib/chart/backstage/files/app-config.development.yaml.tpl new file mode 100644 index 0000000000..76d6499145 --- /dev/null +++ b/contrib/chart/backstage/files/app-config.development.yaml.tpl @@ -0,0 +1,51 @@ +backend: + lighthouseHostname: {{ include "lighthouse.serviceName" . | quote }} + listen: + port: {{ .Values.appConfig.backend.listen.port | default 7000 }} + database: + client: {{ .Values.appConfig.backend.database.client | quote }} + connection: + host: {{ include "backend.postgresql.host" . | quote }} + port: {{ include "backend.postgresql.port" . | quote }} + user: {{ include "backend.postgresql.user" . | quote }} + database: {{ .Values.appConfig.backend.database.connection.database | quote }} + ssl: + rejectUnauthorized: {{ .Values.appConfig.backend.database.connection.ssl.rejectUnauthorized | quote }} + ca: {{ include "backstage.backend.postgresCaFilename" . | quote }} + +catalog: +{{- if .Values.backend.demoData }} + locations: + # Backstage example components + - type: github + target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/all-components.yaml + # Example component for github-actions + - type: github + target: https://github.com/spotify/backstage/blob/master/plugins/github-actions/examples/sample.yaml + # Example component for techdocs + - type: github + target: https://github.com/spotify/backstage/blob/master/plugins/techdocs-backend/examples/documented-component/documented-component.yaml + # Backstage example APIs + - type: github + target: https://github.com/spotify/backstage/blob/master/packages/catalog-model/examples/all-apis.yaml + # Backstage example templates + - type: github + target: https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/sample-templates/all-templates.yaml +{{- else }} + locations: [] +{{- end }} + +auth: + providers: + microsoft: null + +scaffolder: + azure: null + + +sentry: + organization: {{ .Values.appConfig.sentry.organization | quote }} + +techdocs: + generators: + techdocs: 'local' diff --git a/contrib/chart/backstage/files/create-backend-dbs.sql b/contrib/chart/backstage/files/create-backend-dbs.sql new file mode 100644 index 0000000000..043ff7daf5 --- /dev/null +++ b/contrib/chart/backstage/files/create-backend-dbs.sql @@ -0,0 +1,13 @@ +{{ $backendDb := .Values.appConfig.backend.database.connection.database }} +{{ $lighthouseDb := .Values.lighthouse.database.connection.database }} +{{ $user := .Values.global.postgresql.postgresqlUsername }} + +grant all privileges on database {{ $backendDb }} to {{ $user }}; + +create database backstage_plugin_auth; +grant all privileges on database backstage_plugin_auth to {{ $user }}; + +{{ if not (eq $backendDb $lighthouseDb) }} +create database {{ $lighthouseDb }}; +grant all privileges on database {{ $lighthouseDb }} to {{ $user }}; +{{ end }} diff --git a/contrib/chart/backstage/templates/_helpers.tpl b/contrib/chart/backstage/templates/_helpers.tpl new file mode 100644 index 0000000000..5d49ba6975 --- /dev/null +++ b/contrib/chart/backstage/templates/_helpers.tpl @@ -0,0 +1,286 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "backstage.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "backstage.fullname" -}} +{{- if .Values.fullnameOverride -}} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- $name := default .Chart.Name .Values.nameOverride -}} +{{- if contains $name .Release.Name -}} +{{- .Release.Name | trunc 63 | trimSuffix "-" -}} +{{- else -}} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "backstage.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} +{{- end -}} + +{{/* +Common App labels +*/}} +{{- define "backstage.app.labels" -}} +app.kubernetes.io/name: {{ include "backstage.name" . }}-app +helm.sh/chart: {{ include "backstage.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Common Backend labels +*/}} +{{- define "backstage.backend.labels" -}} +app.kubernetes.io/name: {{ include "backstage.name" . }}-backend +helm.sh/chart: {{ include "backstage.chart" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end -}} + +{{/* +Name for postgresql dependency +See https://github.com/helm/helm/issues/3920#issuecomment-686913512 +*/}} +{{- define "backstage.postgresql.fullname" -}} +{{ printf "%s-%s" .Release.Name .Values.postgresql.nameOverride }} +{{- end -}} + + +{{/* +Create the name of the service account to use for the app +*/}} +{{- define "backstage.app.serviceAccountName" -}} +{{- if .Values.app.serviceAccount.create -}} + {{ default "default" .Values.app.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.app.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Create the name of the service account to use for the backend +*/}} +{{- define "backstage.backend.serviceAccountName" -}} +{{- if .Values.backend.serviceAccount.create -}} + {{ default default .Values.backend.serviceAccount.name }} +{{- else -}} + {{ default "default" .Values.backend.serviceAccount.name }} +{{- end -}} +{{- end -}} + +{{/* +Path to the CA certificate file in the backend +*/}} +{{- define "backstage.backend.postgresCaFilename" -}} +{{ include "backstage.backend.postgresCaDir" . }}/{{- required "The name for the CA certificate file for postgresql is required" .Values.global.postgresql.caFilename }} +{{- end -}} +{{/* + +{{/* +Directory path to the CA certificate file in the backend +*/}} +{{- define "backstage.backend.postgresCaDir" -}} +{{- if .Values.appConfig.backend.database.connection.ssl.ca -}} + {{ .Values.appConfig.backend.database.connection.ssl.ca }} +{{- else -}} +/etc/postgresql +{{- end -}} +{{- end -}} +{{/* + +Path to the CA certificate file in lighthouse +*/}} +{{- define "backstage.lighthouse.postgresCaFilename" -}} +{{ include "backstage.lighthouse.postgresCaDir" . }}/{{- required "The name for the CA certificate file for postgresql is required" .Values.global.postgresql.caFilename }} +{{- end -}} + +{{/* +Directory path to the CA certificate file in lighthouse +*/}} +{{- define "backstage.lighthouse.postgresCaDir" -}} +{{- if .Values.lighthouse.database.pathToDatabaseCa -}} + {{ .Values.lighthouse.database.pathToDatabaseCa }} +{{- else -}} +/etc/postgresql +{{- end -}} +{{- end -}} +{{/* + +{{/* +Generate ca for postgresql +*/}} +{{- define "backstage.postgresql.generateCA" -}} +{{- $ca := .ca | default (genCA (include "backstage.postgresql.fullname" .) 365) -}} +{{- $_ := set . "ca" $ca -}} +{{- $ca.Cert -}} +{{- end -}} + +{{/* +Generate certificates for postgresql +*/}} +{{- define "generateCerts" -}} +{{- $postgresName := (include "backstage.postgresql.fullname" .) }} +{{- $altNames := list $postgresName ( printf "%s.%s" $postgresName .Release.Namespace ) ( printf "%s.%s.svc" ( $postgresName ) .Release.Namespace ) -}} +{{- $ca := .ca | default (genCA (include "backstage.postgresql.fullname" .) 365) -}} +{{- $_ := set . "ca" $ca -}} +{{- $cert := genSignedCert ( $postgresName ) nil $altNames 365 $ca -}} +tls.crt: {{ $cert.Cert | b64enc }} +tls.key: {{ $cert.Key | b64enc }} +{{- end -}} + +{{/* +Generate a password for the postgres user used for the connections from the backend and lighthouse +*/}} +{{- define "postgresql.generateUserPassword" -}} +{{- $pgPassword := .pgPassword | default ( randAlphaNum 12 ) -}} +{{- $_ := set . "pgPassword" $pgPassword -}} +{{ $pgPassword}} +{{- end -}} + +{{/* +Name of the backend service +*/}} +{{- define "backend.serviceName" -}} +{{ include "backstage.fullname" . }}-backend +{{- end -}} + +{{/* +Name of the frontend service +*/}} +{{- define "frontend.serviceName" -}} +{{ include "backstage.fullname" . }}-frontend +{{- end -}} + +{{/* +Name of the lighthouse backend service +*/}} +{{- define "lighthouse.serviceName" -}} +{{ include "backstage.fullname" . }}-lighthouse +{{- end -}} + +{{/* +Name of the postgresql service +*/}} +{{- define "postgresql.serviceName" -}} +{{- include "backstage.postgresql.fullname" . }} +{{- end -}} + +{{/* +Postgres host for lighthouse +*/}} +{{- define "lighthouse.postgresql.host" -}} +{{- if .Values.postgresql.enabled }} +{{- include "postgresql.serviceName" . }} +{{- else -}} +{{- required "A valid .Values.lighthouse.database.connection.host is required when postgresql is not enabled" .Values.lighthouse.database.connection.host -}} +{{- end -}} +{{- end -}} + +{{/* +Postgres host for the backend +*/}} +{{- define "backend.postgresql.host" -}} +{{- if .Values.postgresql.enabled }} +{{- include "postgresql.serviceName" . }} +{{- else -}} +{{- required "A valid .Values.appConfig.backend.database.connection.host is required when postgresql is not enabled" .Values.appConfig.backend.database.connection.host -}} +{{- end -}} +{{- end -}} + +{{/* +Postgres port for the backend +*/}} +{{- define "backend.postgresql.port" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.service.port }} +{{- else if .Values.appConfig.backend.database.connection.port -}} +{{- .Values.appConfig.backend.database.connection.port }} +{{ else }} +5432 +{{- end -}} +{{- end -}} + +{{/* +Postgres port for lighthouse +*/}} +{{- define "lighthouse.postgresql.port" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.postgresql.service.port }} +{{- else if .Values.lighthouse.database.connection.port -}} +{{- .Values.lighthouse.database.connection.port }} +{{- else -}} +5432 +{{- end -}} +{{- end -}} + +{{/* +Postgres user for backend +*/}} +{{- define "backend.postgresql.user" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.global.postgresql.postgresqlUsername }} +{{- else -}} +{{- required "A valid .Values.appConfig.backend.database.connection.user is required when postgresql is not enabled" .Values.appConfig.backend.database.connection.user -}} +{{- end -}} +{{- end -}} + +{{/* +Postgres user for lighthouse +*/}} +{{- define "lighthouse.postgresql.user" -}} +{{- if .Values.postgresql.enabled }} +{{- .Values.global.postgresql.postgresqlUsername }} +{{- else -}} +{{- required "A valid .Values.lighthouse.database.connection.user is required when postgresql is not enabled" .Values.lighthouse.database.connection.user -}} +{{- end -}} +{{- end -}} + +{{/* +Postgres password secret for backend +*/}} +{{- define "backend.postgresql.passwordSecret" -}} +{{- if .Values.postgresql.enabled }} +{{- template "backstage.postgresql.fullname" . }} +{{- else -}} +{{ $secretName := (printf "%s-backend-postgres" (include "backstage.fullname" . )) }} +{{- required "A valid .Values.appConfig.backend.database.connection.password is required when postgresql is not enabled" $secretName -}} +{{- end -}} +{{- end -}} + +{{/* +Postgres password for lighthouse +*/}} +{{- define "lighthouse.postgresql.passwordSecret" -}} +{{- if .Values.postgresql.enabled }} +{{- template "backstage.postgresql.fullname" . }} +{{- else -}} +{{ $secretName := (printf "%s-lighthouse-postgres" (include "backstage.fullname" . )) }} +{{- required "A valid .Values.lighthouse.database.connection.password is required when postgresql is not enabled" $secretName -}} +{{- end -}} +{{- end -}} + +{{/* +app-config file name +*/}} +{{- define "backstage.appConfigFilename" -}} +{{- "app-config.development.yaml" -}} +{{- end -}} diff --git a/contrib/chart/backstage/templates/backend-deployment.yaml b/contrib/chart/backstage/templates/backend-deployment.yaml new file mode 100644 index 0000000000..c57764db8e --- /dev/null +++ b/contrib/chart/backstage/templates/backend-deployment.yaml @@ -0,0 +1,79 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "backstage.fullname" . }}-backend + +spec: + replicas: {{ .Values.backend.replicaCount }} + + selector: + matchLabels: + app: backstage + component: backend + + template: + metadata: + annotations: + ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"backend"}]' + labels: + app: backstage + component: backend + + spec: + containers: + - name: {{ .Chart.Name }}-backend + image: {{ .Values.backend.image.repository }}:{{ .Values.backend.image.tag }} + imagePullPolicy: {{ .Values.backend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.backend.containerPort }} + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} + + envFrom: + - secretRef: + name: {{ include "backstage.fullname" . }}-backend + - configMapRef: + name: {{ include "backstage.fullname" . }}-app-env + - configMapRef: + name: {{ include "backstage.fullname" . }}-auth + env: + - name: NODE_ENV + value: {{ .Values.backend.nodeEnv | default "development" }} + - name: APP_CONFIG_backend_database_connection_password + valueFrom: + secretKeyRef: + name: {{ include "backend.postgresql.passwordSecret" .}} + key: postgresql-password + volumeMounts: + - name: postgres-ca + mountPath: {{ include "backstage.backend.postgresCaDir" . }} + - name: app-config + mountPath: {{ printf "/usr/src/app/%s" (include "backstage.appConfigFilename" .) }} + subPath: {{ include "backstage.appConfigFilename" . }} + + volumes: + - name: postgres-ca + configMap: + name: {{ include "backstage.fullname" . }}-postgres-ca + - name: app-config + configMap: + name: {{ include "backstage.fullname" . }}-app-config + +{{- if .Values.backend.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "backend.serviceName" . }} + +spec: + ports: + - port: 80 + targetPort: {{ .Values.backend.containerPort }} + + selector: + app: backstage + component: backend + + type: ClusterIP +{{- end }} diff --git a/contrib/chart/backstage/templates/backend-secret.yaml b/contrib/chart/backstage/templates/backend-secret.yaml new file mode 100644 index 0000000000..63eddc2742 --- /dev/null +++ b/contrib/chart/backstage/templates/backend-secret.yaml @@ -0,0 +1,21 @@ +{{- if .Values.backend.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "backstage.fullname" . }}-backend +type: Opaque +stringData: + AUTH_GOOGLE_CLIENT_SECRET: {{ .Values.auth.google.clientSecret }} + AUTH_GITHUB_CLIENT_SECRET: {{ .Values.auth.github.clientSecret }} + AUTH_GITLAB_CLIENT_SECRET: {{ .Values.auth.gitlab.clientSecret }} + AUTH_OKTA_CLIENT_SECRET: {{ .Values.auth.okta.clientSecret }} + AUTH_OAUTH2_CLIENT_SECRET: {{ .Values.auth.oauth2.clientSecret }} + AUTH_AUTH0_CLIENT_SECRET: {{ .Values.auth.auth0.clientSecret }} + AUTH_MICROSOFT_CLIENT_SECRET: {{ .Values.auth.microsoft.clientSecret }} + SENTRY_TOKEN: {{ .Values.auth.sentryToken }} + ROLLBAR_ACCOUNT_TOKEN: {{ .Values.auth.rollbarAccountToken }} + CIRCLECI_AUTH_TOKEN: {{ .Values.auth.circleciAuthToken }} + GITHUB_TOKEN: {{ .Values.auth.githubToken }} + GITLAB_TOKEN: {{ .Values.auth.gitlabToken }} + AZURE_TOKEN: {{ .Values.auth.azure.api.token }} +{{- end }} diff --git a/contrib/chart/backstage/templates/backstage-app-config.yaml b/contrib/chart/backstage/templates/backstage-app-config.yaml new file mode 100644 index 0000000000..061fc3285b --- /dev/null +++ b/contrib/chart/backstage/templates/backstage-app-config.yaml @@ -0,0 +1,27 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "backstage.fullname" . }}-app-config +data: +{{ include "backstage.appConfigFilename" . | indent 2 }}: | +{{ tpl (.Files.Get "files/app-config.development.yaml.tpl") . | indent 4 }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "backstage.fullname" . }}-app-env +data: + APP_CONFIG_app_baseUrl: {{ .Values.appConfig.app.baseUrl | quote | quote }} + APP_CONFIG_app_title: {{ .Values.appConfig.app.title | quote | quote }} + APP_CONFIG_backend_baseUrl: {{ .Values.appConfig.backend.baseUrl | quote | quote }} + APP_CONFIG_backend_cors_origin: {{ .Values.appConfig.backend.cors.origin | quote | quote }} + APP_CONFIG_techdocs_storageUrl: {{ .Values.appConfig.techdocs.storageUrl | quote | quote }} + APP_CONFIG_techdocs_requestUrl: {{ .Values.appConfig.techdocs.requestUrl | quote | quote }} + APP_CONFIG_lighthouse_baseUrl: {{ .Values.appConfig.lighthouse.baseUrl | quote | quote }} + APP_CONFIG_backend_database_connection_ssl_rejectUnauthorized: "false" + APP_CONFIG_auth_providers_github_development_appOrigin: {{ .Values.appConfig.auth.providers.github.development.appOrigin | quote | quote }} + APP_CONFIG_auth_providers_google_development_appOrigin: {{ .Values.appConfig.auth.providers.google.development.appOrigin | quote | quote }} + APP_CONFIG_auth_providers_gitlab_development_appOrigin: {{ .Values.appConfig.auth.providers.gitlab.development.appOrigin | quote | quote }} + APP_CONFIG_auth_providers_okta_development_appOrigin: {{ .Values.appConfig.auth.providers.okta.development.appOrigin | quote | quote }} + APP_CONFIG_auth_providers_oauth2_development_appOrigin: {{ .Values.appConfig.auth.providers.oauth2.development.appOrigin | quote | quote }} + diff --git a/contrib/chart/backstage/templates/backstage-auth-config.yaml b/contrib/chart/backstage/templates/backstage-auth-config.yaml new file mode 100644 index 0000000000..7dcf8379aa --- /dev/null +++ b/contrib/chart/backstage/templates/backstage-auth-config.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "backstage.fullname" . }}-auth +data: + AUTH_GOOGLE_CLIENT_ID: {{ .Values.auth.google.clientId }} + AUTH_GITHUB_CLIENT_ID: {{ .Values.auth.github.clientId }} + AUTH_GITLAB_CLIENT_ID: {{ .Values.auth.gitlab.clientId }} + # This should not be prefixed with AUTH_. This could be a typo in the Backstage app config. + # Regardless, it is not decided by me. + GITLAB_BASE_URL: {{ .Values.auth.gitlab.baseUrl }} + AUTH_OKTA_CLIENT_ID: {{ .Values.auth.okta.clientId }} + AUTH_OKTA_AUDIENCE: {{ .Values.auth.okta.audience }} + AUTH_OAUTH2_CLIENT_ID: {{ .Values.auth.oauth2.clientId }} + AUTH_OAUTH2_AUTH_URL: {{ .Values.auth.oauth2.authUrl }} + AUTH_OAUTH2_TOKEN_URL: {{ .Values.auth.oauth2.tokenUrl }} + AUTH_AUTH0_CLIENT_ID: {{ .Values.auth.auth0.clientId }} + AUTH_AUTH0_DOMAIN: {{ .Values.auth.auth0.domain }} + AUTH_MICROSOFT_CLIENT_ID: {{ .Values.auth.microsoft.clientId }} + AUTH_MICROSOFT_TENANT_ID: {{ .Values.auth.microsoft.tenantId }} + diff --git a/contrib/chart/backstage/templates/frontend-deployment.yaml b/contrib/chart/backstage/templates/frontend-deployment.yaml new file mode 100644 index 0000000000..a5d84be2b2 --- /dev/null +++ b/contrib/chart/backstage/templates/frontend-deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "backstage.fullname" . }}-frontend + +spec: + replicas: {{ .Values.frontend.replicaCount }} + + selector: + matchLabels: + app: backstage + component: frontend + + template: + metadata: + annotations: + ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"frontend"}]' + labels: + app: backstage + component: frontend + + spec: + containers: + - name: {{ .Chart.Name }}-frontend + image: {{ .Values.frontend.image.repository }}:{{ .Values.frontend.image.tag }} + imagePullPolicy: {{ .Values.frontend.image.pullPolicy }} + ports: + - containerPort: {{ .Values.frontend.containerPort }} + resources: + {{- toYaml .Values.backend.resources | nindent 12 }} + envFrom: + - configMapRef: + name: {{ include "backstage.fullname" . }}-app-env + volumeMounts: + - name: app-config + mountPath: {{ printf "/usr/share/nginx/html/static/%s" (include "backstage.appConfigFilename" .) }} + subPath: {{ include "backstage.appConfigFilename" . }} + volumes: + - name: app-config + configMap: + name: {{ include "backstage.fullname" . }}-app-config +{{- if .Values.frontend.enabled }} +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "frontend.serviceName" . }} + +spec: + ports: + - port: 80 + targetPort: {{ .Values.frontend.containerPort }} + + selector: + app: backstage + component: frontend + + type: ClusterIP +{{- end }} diff --git a/contrib/chart/backstage/templates/ingress.yaml b/contrib/chart/backstage/templates/ingress.yaml new file mode 100644 index 0000000000..034862ef75 --- /dev/null +++ b/contrib/chart/backstage/templates/ingress.yaml @@ -0,0 +1,78 @@ +{{- $frontendUrl := urlParse .Values.appConfig.app.baseUrl}} +{{- $backendUrl := urlParse .Values.appConfig.backend.baseUrl}} +{{- $lighthouseUrl := urlParse .Values.appConfig.lighthouse.baseUrl}} +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: {{ include "backstage.fullname" . }}-ingress + {{- with .Values.ingress.annotations }} + annotations: + nginx.ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/configuration-snippet: | + if ($scheme = https) { + add_header Strict-Transport-Security "max-age=0;"; + } + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + rules: + - host: {{ $frontendUrl.host }} + http: + paths: + - path: / + backend: + serviceName: {{ include "frontend.serviceName" . }} + servicePort: 80 + {{/* Route the backend inside the same hostname as the frontend when they are the same */}} + {{- if eq $frontendUrl.host $backendUrl.host}} + - path: /api + backend: + serviceName: {{ include "backend.serviceName" . }} + servicePort: 80 + {{/* Route the backend through a different host */}} + {{- else -}} + - host: {{ $backendUrl.host }} + http: + paths: + - path: {{ $backendUrl.path | default "/" }} + backend: + serviceName: {{ include "backend.serviceName" . }} + servicePort: 80 + {{- end }} + +{{/* Route lighthouse through a different host */}} +{{- if not ( eq $frontendUrl.host $lighthouseUrl.host ) }} + - host: {{ $lighthouseUrl.host }} + http: + paths: + - path: {{ $lighthouseUrl.path | default "/" }} + backend: + serviceName: {{ include "lighthouse.serviceName" . }} + servicePort: 80 +{{- else }} +{{/* Route lighthouse by path with re-write rules when it is hosted under the same hostname */}} +--- +apiVersion: networking.k8s.io/v1beta1 +kind: Ingress +metadata: + name: {{ include "backstage.fullname" . }}-ingress-lighthouse + {{- with .Values.ingress.annotations }} + annotations: + nginx.ingress.kubernetes.io/rewrite-target: /$2 + nginx.ingress.kubernetes.io/ssl-redirect: "false" + nginx.ingress.kubernetes.io/configuration-snippet: | + if ($scheme = https) { + add_header Strict-Transport-Security "max-age=0;"; + } + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + rules: + - host: {{ $frontendUrl.host }} + http: + paths: + - path: {{$lighthouseUrl.path}}(/|$)(.*) + backend: + serviceName: {{ include "lighthouse.serviceName" . }} + servicePort: 80 +{{- end }} diff --git a/contrib/chart/backstage/templates/lighthouse-config.yaml b/contrib/chart/backstage/templates/lighthouse-config.yaml new file mode 100644 index 0000000000..fdf4bd8b06 --- /dev/null +++ b/contrib/chart/backstage/templates/lighthouse-config.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "backstage.fullname" . -}}-lighthouse +data: + PGDATABASE: {{ .Values.lighthouse.database.connection.database | quote }} + PGUSER: {{ include "lighthouse.postgresql.user" . | quote }} + PGPORT: {{ include "lighthouse.postgresql.port" . | quote }} + PGHOST: {{ include "lighthouse.postgresql.host" . | quote }} + PGPATH_TO_CA: {{ include "backstage.lighthouse.postgresCaFilename" . | quote }} \ No newline at end of file diff --git a/contrib/chart/backstage/templates/lighthouse-deployment.yaml b/contrib/chart/backstage/templates/lighthouse-deployment.yaml new file mode 100644 index 0000000000..70341a2a22 --- /dev/null +++ b/contrib/chart/backstage/templates/lighthouse-deployment.yaml @@ -0,0 +1,74 @@ +{{- if .Values.lighthouse.enabled }} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "backstage.fullname" . }}-lighthouse + +spec: + replicas: {{ .Values.lighthouse.replicaCount }} + + selector: + matchLabels: + app: backstage + component: lighthouse-audit-service + + template: + metadata: + annotations: + ad.datadoghq.com/backstage.logs: '[{"source":"backstage","service":"lighthouse"}]' + labels: + app: backstage + component: lighthouse-audit-service + + spec: + containers: + - name: lighthouse-audit-service + image: {{ .Values.lighthouse.image.repository }}:{{ .Values.lighthouse.image.tag }} + imagePullPolicy: {{ .Values.lighthouse.image.pullPolicy }} + ports: + - containerPort: {{ .Values.lighthouse.containerPort }} + resources: + {{- toYaml .Values.lighthouse.resources | nindent 12 }} + + envFrom: + - configMapRef: + name: {{ include "backstage.fullname" . -}}-lighthouse + - configMapRef: + name: {{ include "backstage.fullname" . }}-app-env + + env: + - name: LAS_PORT + value: {{ .Values.lighthouse.containerPort | quote }} + - name: LAS_CORS + value: "true" + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "lighthouse.postgresql.passwordSecret" . }} + key: postgresql-password + + volumeMounts: + - name: postgres-ca + mountPath: {{ include "backstage.lighthouse.postgresCaDir" . }} + + volumes: + - name: postgres-ca + configMap: + name: {{ include "backstage.fullname" . }}-postgres-ca +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "lighthouse.serviceName" . }} + +spec: + ports: + - port: 80 + targetPort: {{ .Values.lighthouse.containerPort }} + + selector: + app: backstage + component: lighthouse-audit-service + + type: ClusterIP +{{- end }} diff --git a/contrib/chart/backstage/templates/postgresql-ca-config.yaml b/contrib/chart/backstage/templates/postgresql-ca-config.yaml new file mode 100644 index 0000000000..2631989f4e --- /dev/null +++ b/contrib/chart/backstage/templates/postgresql-ca-config.yaml @@ -0,0 +1,21 @@ +{{- if .Values.postgresql.enabled }} +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "backstage.fullname" . }}-postgres-ca + labels: + app: {{ include "backstage.postgresql.fullname" . }} + release: {{ .Release.Name }} + annotations: + "helm.sh/hook": "pre-install" + "helm.sh/hook-delete-policy": "before-hook-creation" +data: + {{ .Values.global.postgresql.caFilename }}: | +{{ include "backstage.postgresql.generateCA" . | indent 4}} +{{- else }} +{{- $caConfig := printf "%s-postgres-ca" (include "backstage.fullname" .) }} +{{- if not ( lookup "v1" "ConfigMap" .Release.Namespace $caConfig ) }} +{{- fail (printf "\n\nPlease create the '%s' configmap with the CA certificate for your existing postgresql: kubectl create configmap %s --from-file=ca.crt" $caConfig $caConfig) }} +{{- end }} +{{- end }} \ No newline at end of file diff --git a/contrib/chart/backstage/templates/postgresql-certs-secret.yaml b/contrib/chart/backstage/templates/postgresql-certs-secret.yaml new file mode 100644 index 0000000000..c6845caa7e --- /dev/null +++ b/contrib/chart/backstage/templates/postgresql-certs-secret.yaml @@ -0,0 +1,16 @@ +{{- if .Values.postgresql.enabled }} +--- +apiVersion: v1 +kind: Secret +type: kubernetes.io/tls +metadata: + name: {{ required ".Values.postgresql.tls.certificatesSecret is required" .Values.postgresql.tls.certificatesSecret }} + labels: + app: {{ include "backstage.postgresql.fullname" . }} + release: {{ .Release.Name }} + annotations: + "helm.sh/hook": "pre-install" + "helm.sh/hook-delete-policy": "before-hook-creation" +data: +{{ include "generateCerts" . | indent 2 }} +{{- end }} diff --git a/contrib/chart/backstage/templates/postgresql-initdb-secret.yaml b/contrib/chart/backstage/templates/postgresql-initdb-secret.yaml new file mode 100644 index 0000000000..d1c446f79f --- /dev/null +++ b/contrib/chart/backstage/templates/postgresql-initdb-secret.yaml @@ -0,0 +1,14 @@ +{{- if .Values.postgresql.enabled }} +apiVersion: v1 +kind: Secret +metadata: + name: {{ required ".Values.postgresql.initdbScriptsSecret is required" .Values.postgresql.initdbScriptsSecret }} + annotations: + "helm.sh/hook": "pre-install" + "helm.sh/hook-delete-policy": "before-hook-creation" +type: Opaque +data: + create-backend-dbs.sql: | + {{ tpl (.Files.Get "files/create-backend-dbs.sql") . | b64enc }} +{{- end }} + diff --git a/contrib/chart/backstage/templates/postgresql-password-secret.yaml b/contrib/chart/backstage/templates/postgresql-password-secret.yaml new file mode 100644 index 0000000000..9685130a03 --- /dev/null +++ b/contrib/chart/backstage/templates/postgresql-password-secret.yaml @@ -0,0 +1,30 @@ +{{- if not .Values.postgresql.enabled }} +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ include "backend.postgresql.passwordSecret" . }} + labels: + release: {{ .Release.Name }} + annotations: + "helm.sh/hook": "pre-install" + "helm.sh/hook-delete-policy": "before-hook-creation" +data: + postgresql-password: {{ .Values.appConfig.backend.database.connection.password | b64enc }} +{{- end }} +{{- if not .Values.postgresql.enabled }} +--- +apiVersion: v1 +kind: Secret +type: Opaque +metadata: + name: {{ include "lighthouse.postgresql.passwordSecret" . }} + labels: + release: {{ .Release.Name }} + annotations: + "helm.sh/hook": "pre-install" + "helm.sh/hook-delete-policy": "before-hook-creation" +data: + postgresql-password: {{ .Values.lighthouse.database.connection.password | b64enc }} +{{- end }} diff --git a/contrib/chart/backstage/templates/tests/test-app-connection.yaml b/contrib/chart/backstage/templates/tests/test-app-connection.yaml new file mode 100644 index 0000000000..93f51f3f09 --- /dev/null +++ b/contrib/chart/backstage/templates/tests/test-app-connection.yaml @@ -0,0 +1,23 @@ +--- +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "backstage.fullname" . -}}-test-app-connection + annotations: + "helm.sh/hook": test +spec: + containers: + - name: test-app + image: busybox + command: + - /bin/sh + - -ecx + - | + echo -e "===== Testing the connection with the frontend...\n" + wget -q -O - {{ printf "%s.%s" (include "frontend.serviceName" .) .Release.Namespace | quote }} + echo -e "\n\n===== Testing the connection with the backend...\n" + wget -q -O - {{ printf "http://%s.%s/catalog/entities" (include "backend.serviceName" .) .Release.Namespace | quote }} + echo -e "\n\n===== Testing the connection with the lighthouse plugin...\n" + wget -q -O - {{ printf "%s.%s/v1/audits" (include "lighthouse.serviceName" .) .Release.Namespace | quote }} + echo -e "\n" + restartPolicy: Never \ No newline at end of file diff --git a/contrib/chart/backstage/templates/tests/test-postgresql.yaml b/contrib/chart/backstage/templates/tests/test-postgresql.yaml new file mode 100644 index 0000000000..8eec1db452 --- /dev/null +++ b/contrib/chart/backstage/templates/tests/test-postgresql.yaml @@ -0,0 +1,33 @@ +{{- if .Values.postgresql.enabled}} +--- +apiVersion: v1 +kind: Pod +metadata: + name: {{ include "backstage.name" . -}}-test-postgres + annotations: + "helm.sh/hook": test +spec: + containers: + - name: postgresql-client + image: bitnami/postgresql + env: + - name: PG_HOST + value: {{ include "postgresql.serviceName" . | quote }} + - name: PG_PORT + value: {{ .Values.postgresql.service.port | quote }} + - name: PG_USER + value: {{ .Values.global.postgresql.postgresqlUsername | quote }} + - name: PGPASSWORD + valueFrom: + secretKeyRef: + name: {{ include "backend.postgresql.passwordSecret" .}} + key: postgresql-password + - name: PG_DBNAME + value: {{ .Values.appConfig.backend.database.connection.database }} + command: + - /bin/bash + - -ecx + - | + psql --host=$PG_HOST --port=$PG_PORT --username=$PG_USER --dbname=$PG_DBNAME --no-password + restartPolicy: Never +{{- end }} \ No newline at end of file diff --git a/contrib/chart/backstage/values.yaml b/contrib/chart/backstage/values.yaml new file mode 100644 index 0000000000..3ce45c5330 --- /dev/null +++ b/contrib/chart/backstage/values.yaml @@ -0,0 +1,245 @@ +# Default values for backstage. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +frontend: + enabled: true + replicaCount: 1 + image: + repository: martinaif/backstage-k8s-demo-frontend + tag: test1 + pullPolicy: IfNotPresent + containerPort: 80 + resources: + requests: + memory: 128Mi + limits: + memory: 256Mi + +backend: + enabled: true + nodeEnv: development + demoData: true + replicaCount: 1 + image: + repository: martinaif/backstage-k8s-demo-backend + tag: test1 + pullPolicy: IfNotPresent + containerPort: 7000 + resources: + requests: + memory: 512Mi + limits: + memory: 1024Mi + +lighthouse: + enabled: true + replicaCount: 1 + image: + repository: roadiehq/lighthouse-audit-service + tag: latest + pullPolicy: IfNotPresent + containerPort: 3003 + resources: + requests: + memory: 128Mi + limits: + memory: 256Mi + database: + connection: + port: + host: + user: + password: + database: lighthouse_audit_service + pathToDatabaseCa: + +nameOverride: '' +fullnameOverride: '' + +ingress: + annotations: + kubernetes.io/ingress.class: nginx + +global: + postgresql: + postgresqlUsername: backend-user + caFilename: ca.crt + +postgresql: + enabled: true + nameOverride: postgresql + tls: + enabled: true + certificatesSecret: backstage-postgresql-certs + certFilename: tls.crt + certKeyFilename: tls.key + volumePermissions: + enabled: true + initdbScriptsSecret: backstage-postgresql-initdb + +appConfig: + app: + baseUrl: https://demo.example.com + title: Backstage + backend: + baseUrl: https://demo.example.com + listen: + port: 7000 + cors: + origin: https://demo.example.com + database: + client: pg + connection: + database: backstage_plugin_catalog + host: + user: + port: + password: + ssl: + rejectUnauthorized: false + ca: + sentry: + organization: spotify + techdocs: + storageUrl: https://demo.example.com/api/techdocs/static/docs + requestUrl: https://demo.example.com/api/techdocs + lighthouse: + baseUrl: https://demo.example.com/lighthouse-api + rollbar: + organization: roadie + + # Auth config has recently moved into the app config file in upstream Backstage. However, + # most of this config simply mandates that items like the client id and client secret should + # be picked up from the environment variables named below. Those environment variables are + # set in this helm controlled environment by the 'auth' configuration below this section. + # Thus, the only key in this config which directly controls an app config is the + # auth.providers.github.development.appOrigin property. + auth: + providers: + google: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_GOOGLE_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GOOGLE_CLIENT_SECRET + github: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_GITHUB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITHUB_CLIENT_SECRET + enterpriseInstanceUrl: + $secret: + env: AUTH_GITHUB_ENTERPRISE_INSTANCE_URL + gitlab: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_GITLAB_CLIENT_ID + clientSecret: + $secret: + env: AUTH_GITLAB_CLIENT_SECRET + audience: + $secret: + env: GITLAB_BASE_URL + okta: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_OKTA_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OKTA_CLIENT_SECRET + audience: + $secret: + env: AUTH_OKTA_AUDIENCE + oauth2: + development: + appOrigin: 'http://localhost:3000/' + secure: false + clientId: + $secret: + env: AUTH_OAUTH2_CLIENT_ID + clientSecret: + $secret: + env: AUTH_OAUTH2_CLIENT_SECRET + authorizationURL: + $secret: + env: AUTH_OAUTH2_AUTH_URL + tokenURL: + $secret: + env: AUTH_OAUTH2_TOKEN_URL + auth0: + development: + clientId: + $secret: + env: AUTH_AUTH0_CLIENT_ID + clientSecret: + $secret: + env: AUTH_AUTH0_CLIENT_SECRET + domain: + $secret: + env: AUTH_AUTH0_DOMAIN + microsoft: + development: + clientId: + $secret: + env: AUTH_MICROSOFT_CLIENT_ID + clientSecret: + $secret: + env: AUTH_MICROSOFT_CLIENT_SECRET + tenantId: + $secret: + env: AUTH_MICROSOFT_TENANT_ID + +auth: + google: + clientId: a + clientSecret: a + github: + clientId: c + clientSecret: c + gitlab: + clientId: b + clientSecret: b + baseUrl: b + okta: + clientId: b + clientSecret: b + audience: b + oauth2: + clientId: b + clientSecret: b + authUrl: b + tokenUrl: b + auth0: + clientId: b + clientSecret: b + domain: b + microsoft: + clientId: f + clientSecret: f + tenantId: f + azure: + api: + token: h + sentryToken: e + rollbarAccountToken: f + # This is a 'Personal Access Token' + circleciAuthToken: r + # Used by the scaffolder to create GitHub repos. Must have 'repo' scope. + githubToken: g + gitlabToken: g diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index a9922cfb6c..0000000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,12 +0,0 @@ -# Make sure that before you -# run the docker-compose that you have run -# $ yarn docker-build -version: '3' - -services: - backstage: - image: 'example-backend:latest' - ports: - - '7000:7000' - environment: - NODE_ENV: development diff --git a/docs/conf/writing.md b/docs/conf/writing.md index 5564e780bb..bb4d0beb75 100644 --- a/docs/conf/writing.md +++ b/docs/conf/writing.md @@ -65,9 +65,10 @@ All `app-config.yaml` files inside the monorepo root and package root are considered, as are files with additional `local` and environment affixes such as `development`, for example `app-config.local.yaml`, `app-config.production.yaml`, and `app-config.development.local.yaml`. Which -environment config files are loaded is determined by the `NODE_ENV` environment -variable. Local configuration files are always loaded, but are meant for local -development overrides and should typically be `.gitignore`'d. +environment config files are loaded is determined by the `APP_ENV` environment +variable, or `NODE_ENV` if it is not set. Local configuration files are always +loaded, but are meant for local development overrides and should typically be +`.gitignore`'d. All loaded configuration files are merged together using the following rules: diff --git a/docs/getting-started/deployment-helm.md b/docs/getting-started/deployment-helm.md new file mode 100644 index 0000000000..bc239d9306 --- /dev/null +++ b/docs/getting-started/deployment-helm.md @@ -0,0 +1,69 @@ +--- +id: deployment-helm +title: Helm +description: Documentation on Kubernetes and Helm Deployment +--- + +# Helm charts + +An example Backstage app can be deployed in Kubernetes using the [Backstage Helm +charts][backstage-helm-charts] + +First, choose a DNS name where backstage will be hosted create a yaml file for +your custom configuration. + +``` +appConfig: + app: + baseUrl: https://backstage.mydomain.com + title: Backstage + backend: + baseUrl: https://backstage.mydomain.com + cors: + origin: https://backstage.mydomain.com + lighthouse: + baseUrl: https://backstage.mydomain.com/lighthouse-api + techdocs: + storageUrl: https://backstage.mydomain.com/api/techdocs/static/docs + requestUrl: https://backstage.mydomain.com/api/techdocs + +``` + +Then use it to run: + +``` +git clone https://github.com/spotify/backstage.git +cd contrib/chart/backstage +helm dependency update +helm install -f backstage-mydomain.yaml backstage . +``` + +This command will deploy the following pieces: + +- Backstage frontend +- Backstage backend with scaffolder and auth plugins +- (optional) a PostgreSQL instance +- lighthouse plugin +- ingress + +After a few minutes Backstage should be up and running in your cluster under the +DNS specified earlier. + +Make sure to create the appropriate DNS entry in your infrastructure. To find +the public IP address run: + +```bash +$ kubectl get ingress +NAME HOSTS ADDRESS PORTS AGE +backstage-ingress * 123.1.2.3 80 17m +``` + +> **NOTE**: this is not a production ready deployment. + +For more information on how to customize the deployment check the +[README][charts-readme]. + +[backstage-helm-charts]: + (https://github.com/spotify/backstage/tree/master/contrib/chart/backstage) +[charts-readme]: + (https://github.com/spotify/backstage/tree/master/contrib/chart/backstage/README.md) diff --git a/docs/getting-started/deployment-other.md b/docs/getting-started/deployment-other.md index 2cd9708e2a..509bbdc978 100644 --- a/docs/getting-started/deployment-other.md +++ b/docs/getting-started/deployment-other.md @@ -13,18 +13,7 @@ Run the following commands if you have Docker environment ```bash $ yarn install $ yarn docker-build -$ docker run --rm -it -p 7000:7000 -e NODE_ENV=development example-backend:latest +$ docker run --rm -it -p 7000:7000 -e APP_ENV=production -e NODE_ENV=development example-backend:latest ``` Then open http://localhost/ on your browser. - -### Running with `docker-compose` - -There is also a `docker-compose.yaml` that you can use to replace the previous -`docker run` command: - -```bash -$ yarn install -$ yarn docker-build -$ docker-compose up -``` diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 1c90916fb5..59505aa915 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -66,12 +66,6 @@ guidelines to get started. The platform APIs and features are stable and can be depended on for production use. After this plugins will require little to no maintenance. -- **Backstage Design System** - By providing design guidelines for common plugin - layouts together, rich set of reusable UI components - ([Storybook](https://backstage.io/storybook)) and Figma design resources. The - Design System will make it easy to design and build plugins that are - consistent across the platform -- supporting both developers and designers. - - Further improvements to platform documentation ### Plugins @@ -115,6 +109,7 @@ Chances are that someone will jump in and help build it. ### Completed milestones ✅ +- [Backstage Design System 💅](https://backstage.io/blog/2020/09/30/backstage-design-system) - [Cost Insights plugin 💸](https://engineering.atspotify.com/2020/09/29/managing-clouds-from-the-ground-up-cost-engineering-at-spotify/) - [Donate Backstage to the CNCF 🎉](https://backstage.io/blog/2020/09/23/backstage-cncf-sandbox) - [TechDocs v1](https://backstage.io/blog/2020/09/08/announcing-tech-docs) diff --git a/lerna.json b/lerna.json index 7d514abb97..f804c800de 100644 --- a/lerna.json +++ b/lerna.json @@ -2,5 +2,5 @@ "packages": ["packages/*", "plugins/*"], "npmClient": "yarn", "useWorkspaces": true, - "version": "0.1.1-alpha.24" + "version": "0.1.1-alpha.25" } diff --git a/microsite/blog/2020-09-30-plugin-marketplace.md b/microsite/blog/2020-09-30-plugin-marketplace.md index b7a799097f..0b96abbc60 100644 --- a/microsite/blog/2020-09-30-plugin-marketplace.md +++ b/microsite/blog/2020-09-30-plugin-marketplace.md @@ -40,4 +40,4 @@ We are really excited to see all the amazing plugins that have already been buil What plugins would you like to see in the Plugin Marketplace? [Tell us](https://github.com/spotify/backstage/issues/new?labels=plugin&template=plugin_template.md&title=%5BPlugin%5D+THE+PLUGIN+NAME)! -_Special shout-out to community member [Iain Billett](https://github.com/iain-b) from Roadie for helping build and contribute the [Plugin Marketplace page](https://backstage.io/plugins) (as his first PR no less!)._ +_Special shout-out to community member [Iain Billett](https://github.com/iain-b) from [Roadie](https://roadie.io) for helping build and contribute the [Plugin Marketplace page](https://backstage.io/plugins) (as his first PR no less!)._ diff --git a/microsite/core/Footer.js b/microsite/core/Footer.js index 8e3c4c086d..1bebf2f399 100644 --- a/microsite/core/Footer.js +++ b/microsite/core/Footer.js @@ -1,8 +1,17 @@ -/** - * Copyright (c) 2017-present, Facebook, Inc. +/* + * Copyright 2020 Backstage Project Authors. * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ const React = require('react'); @@ -82,15 +91,6 @@ class Footer extends React.Component { )} - - - Made with at {this.props.config.organizationName} -

{this.props.config.copyright}

); diff --git a/microsite/data/plugins/github-insights.yaml b/microsite/data/plugins/github-insights.yaml new file mode 100644 index 0000000000..8ac8321da0 --- /dev/null +++ b/microsite/data/plugins/github-insights.yaml @@ -0,0 +1,9 @@ +--- +title: GitHub Insights +author: roadie.io +authorUrl: https://roadie.io +category: Monitoring +description: View GitHub Insights for your components in Backstage. +documentation: https://roadie.io/backstage/plugins/github-insights +iconUrl: https://roadie.io/static/2ad5123c425908efde0c922d707e737b/06c84/code-icon.png +npmPackageName: '@roadiehq/backstage-plugin-github-insights' diff --git a/microsite/siteConfig.js b/microsite/siteConfig.js index d034321d6c..370d491eb3 100644 --- a/microsite/siteConfig.js +++ b/microsite/siteConfig.js @@ -78,7 +78,7 @@ const siteConfig = { }, // This copyright info is used in /core/Footer.js and blog RSS/Atom feeds. - copyright: `Copyright © ${new Date().getFullYear()} Spotify AB`, + copyright: `Copyright © ${new Date().getFullYear()} Backstage Project Authors. All rights reserved. The Linux Foundation has registered trademarks and uses trademarks. For a list of trademarks of The Linux Foundation, please see our Trademark Usage page: https://www.linuxfoundation.org/trademark-usage`, highlight: { // Highlight.js theme to use for syntax highlighting in code blocks. diff --git a/microsite/static/css/custom.css b/microsite/static/css/custom.css index 738478724f..6599aef6a6 100644 --- a/microsite/static/css/custom.css +++ b/microsite/static/css/custom.css @@ -1084,3 +1084,10 @@ code { margin-bottom: 40px; margin-top: 20px; } + +.nav-footer .copyright { + width: 600px; + color: #fff; + margin: auto; + font-size: 10pt; +} diff --git a/package.json b/package.json index b9c84a4d90..371646074c 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ }, "version": "1.0.0", "devDependencies": { - "@changesets/cli": "2.10.2", + "@changesets/cli": "^2.11.0", "@spotify/eslint-config-oss": "^1.0.1", "@spotify/prettier-config": "^8.0.0", "concurrently": "^5.2.0", diff --git a/packages/app/package.json b/packages/app/package.json index 76e20bc0dc..15b5cbed6e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,41 +1,43 @@ { "name": "example-app", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/core": "^0.1.1-alpha.24", - "@backstage/plugin-api-docs": "^0.1.1-alpha.24", - "@backstage/plugin-catalog": "^0.1.1-alpha.24", - "@backstage/plugin-circleci": "^0.1.1-alpha.24", - "@backstage/plugin-cloudbuild": "^0.1.1-alpha.24", - "@backstage/plugin-cost-insights": "^0.1.1-alpha.24", - "@backstage/plugin-explore": "^0.1.1-alpha.24", - "@backstage/plugin-gcp-projects": "^0.1.1-alpha.24", - "@backstage/plugin-github-actions": "^0.1.1-alpha.24", - "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.24", - "@backstage/plugin-graphiql": "^0.1.1-alpha.24", - "@backstage/plugin-jenkins": "^0.1.1-alpha.24", - "@backstage/plugin-kubernetes": "^0.1.1-alpha.24", - "@backstage/plugin-lighthouse": "^0.1.1-alpha.24", - "@backstage/plugin-newrelic": "^0.1.1-alpha.24", - "@backstage/plugin-register-component": "^0.1.1-alpha.24", - "@backstage/plugin-rollbar": "^0.1.1-alpha.24", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.24", - "@backstage/plugin-sentry": "^0.1.1-alpha.24", - "@backstage/plugin-tech-radar": "^0.1.1-alpha.24", - "@backstage/plugin-techdocs": "^0.1.1-alpha.24", - "@backstage/plugin-welcome": "^0.1.1-alpha.24", - "@backstage/plugin-user-settings": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/core": "^0.1.1-alpha.25", + "@backstage/plugin-api-docs": "^0.1.1-alpha.25", + "@backstage/plugin-catalog": "^0.1.1-alpha.25", + "@backstage/plugin-circleci": "^0.1.1-alpha.25", + "@backstage/plugin-cloudbuild": "^0.1.1-alpha.25", + "@backstage/plugin-cost-insights": "^0.1.1-alpha.25", + "@backstage/plugin-explore": "^0.1.1-alpha.25", + "@backstage/plugin-gcp-projects": "^0.1.1-alpha.25", + "@backstage/plugin-github-actions": "^0.1.1-alpha.25", + "@backstage/plugin-gitops-profiles": "^0.1.1-alpha.25", + "@backstage/plugin-graphiql": "^0.1.1-alpha.25", + "@backstage/plugin-jenkins": "^0.1.1-alpha.25", + "@backstage/plugin-kubernetes": "^0.1.1-alpha.25", + "@backstage/plugin-lighthouse": "^0.1.1-alpha.25", + "@backstage/plugin-newrelic": "^0.1.1-alpha.25", + "@backstage/plugin-register-component": "^0.1.1-alpha.25", + "@backstage/plugin-rollbar": "^0.1.1-alpha.25", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.25", + "@backstage/plugin-sentry": "^0.1.1-alpha.25", + "@backstage/plugin-tech-radar": "^0.1.1-alpha.25", + "@backstage/plugin-techdocs": "^0.1.1-alpha.25", + "@backstage/plugin-user-settings": "^0.1.1-alpha.25", + "@backstage/plugin-welcome": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@octokit/rest": "^18.0.0", + "@roadiehq/backstage-plugin-github-insights": "^0.2.2", "@roadiehq/backstage-plugin-github-pull-requests": "^0.4.3", "@roadiehq/backstage-plugin-travis-ci": "^0.2.3", + "dayjs": "^1.9.1", "history": "^5.0.0", "prop-types": "^15.7.2", "react": "^16.12.0", @@ -44,6 +46,7 @@ "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3", + "regression": "^2.0.1", "zen-observable": "^0.8.15" }, "devDependencies": { @@ -55,6 +58,7 @@ "@types/jquery": "^3.3.34", "@types/node": "^12.0.0", "@types/react-dom": "^16.9.8", + "@types/regression": "^2.0.0", "@types/zen-observable": "^0.8.0", "cross-env": "^7.0.0", "cypress": "^4.2.0", diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 393ffe2631..16c105ca0b 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -40,6 +40,13 @@ import { Router as ApiDocsRouter } from '@backstage/plugin-api-docs'; import { Router as SentryRouter } from '@backstage/plugin-sentry'; import { EmbeddedDocsRouter as DocsRouter } from '@backstage/plugin-techdocs'; import { Router as KubernetesRouter } from '@backstage/plugin-kubernetes'; +import { + Router as GitHubInsightsRouter, + isPluginApplicableToEntity as isGitHubAvailable, + ReadMeCard, + LanguagesCard, + ReleasesCard, +} from '@roadiehq/backstage-plugin-github-insights'; import React, { ReactNode } from 'react'; import { AboutCard, @@ -120,6 +127,17 @@ const OverviewContent = ({ entity }: { entity: Entity }) => ( + {isGitHubAvailable(entity) && ( + <> + + + + + + + + + )} {isLighthouseAvailable(entity) && ( @@ -160,6 +178,11 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( title="Kubernetes" element={} /> + } + /> ); @@ -195,6 +218,11 @@ const WebsiteEntityPage = ({ entity }: { entity: Entity }) => ( title="Kubernetes" element={} /> + } + /> ); const DefaultEntityPage = ({ entity }: { entity: Entity }) => ( diff --git a/packages/app/src/plugins.ts b/packages/app/src/plugins.ts index 57dc8a3443..bdc3851318 100644 --- a/packages/app/src/plugins.ts +++ b/packages/app/src/plugins.ts @@ -36,4 +36,5 @@ export { plugin as GcpProjects } from '@backstage/plugin-gcp-projects'; export { plugin as Kubernetes } from '@backstage/plugin-kubernetes'; export { plugin as Cloudbuild } from '@backstage/plugin-cloudbuild'; export { plugin as CostInsights } from '@backstage/plugin-cost-insights'; +export { plugin as GitHubInsights } from '@roadiehq/backstage-plugin-github-insights'; export { plugin as UserSettings } from '@backstage/plugin-user-settings'; diff --git a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts index c01092dc51..aa795e9eee 100644 --- a/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts +++ b/packages/app/src/plugins/cost-insights/ExampleCostInsightsClient.ts @@ -15,16 +15,84 @@ */ /* eslint-disable no-restricted-imports */ +import dayjs from 'dayjs'; +import regression, { DataPoint } from 'regression'; import { - CostInsightsApi, Alert, + ChangeStatistic, Cost, + CostInsightsApi, + DateAggregation, Duration, - Project, - ProductCost, + exclusiveEndDateOf, Group, + inclusiveStartDateOf, + Maybe, + ProductCost, + Project, + ProjectGrowthAlert, + ProjectGrowthData, + Trendline, + UnlabeledDataflowAlert, + UnlabeledDataflowData, } from '@backstage/plugin-cost-insights'; +function durationOf(intervals: string): Duration { + const match = intervals.match(/\/(?P\d+[DM])\//); + const { duration } = match!.groups!; + return duration as Duration; +} + +function aggregationFor( + duration: Duration, + baseline: number, +): DateAggregation[] { + const days = dayjs(exclusiveEndDateOf(duration)).diff( + inclusiveStartDateOf(duration), + 'day', + ); + + return [...Array(days).keys()].reduce( + (values: DateAggregation[], i: number): DateAggregation[] => { + const last = values.length ? values[values.length - 1].amount : baseline; + values.push({ + date: dayjs(inclusiveStartDateOf(duration)) + .add(i, 'day') + .format('YYYY-MM-DD'), + amount: last + (baseline / 20) * (Math.random() * 2 - 1), + }); + return values; + }, + [], + ); +} + +function trendlineOf(aggregation: DateAggregation[]): Trendline { + const data: ReadonlyArray = aggregation.map(a => [ + Date.parse(a.date) / 1000, + a.amount, + ]); + const result = regression.linear(data, { precision: 5 }); + return { + slope: result.equation[0], + intercept: result.equation[1], + }; +} + +function changeOf(aggregation: DateAggregation[]): ChangeStatistic { + const half = Math.ceil(aggregation.length / 2); + const before = aggregation + .slice(0, half) + .reduce((sum, a) => sum + a.amount, 0); + const after = aggregation + .slice(half, aggregation.length) + .reduce((sum, a) => sum + a.amount, 0); + return { + ratio: (after - before) / before, + amount: after - before, + }; +} + export class ExampleCostInsightsClient implements CostInsightsApi { private request(_: any, res: any): Promise { return new Promise(resolve => setTimeout(resolve, 0, res)); @@ -53,30 +121,17 @@ export class ExampleCostInsightsClient implements CostInsightsApi { metric: string | null, intervals: string, ): Promise { + const aggregation = aggregationFor( + durationOf(intervals), + metric ? 0.3 : 8_000, + ); const groupDailyCost: Cost = await this.request( { group, metric, intervals }, { id: metric, // costs with null ids will appear as "All Projects" in Cost Overview panel - aggregation: [ - { date: '2020-08-01', amount: 75_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-02', amount: 120_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-03', amount: 110_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-04', amount: 90_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-05', amount: 80_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-06', amount: 85_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-07', amount: 82_500 / (metric ? 200_000 : 1) }, - { date: '2020-08-08', amount: 100_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-09', amount: 130_000 / (metric ? 200_000 : 1) }, - { date: '2020-08-10', amount: 140_000 / (metric ? 200_000 : 1) }, - ], - change: { - ratio: 0.86, - amount: 65_000, - }, - trendline: { - slope: 0, - intercept: 90_000, - }, + aggregation: aggregation, + change: changeOf(aggregation), + trendline: trendlineOf(aggregation), }, ); @@ -88,30 +143,17 @@ export class ExampleCostInsightsClient implements CostInsightsApi { metric: string | null, intervals: string, ): Promise { + const aggregation = aggregationFor( + durationOf(intervals), + metric ? 0.1 : 1_500, + ); const projectDailyCost: Cost = await this.request( { project, metric, intervals }, { id: 'project-a', - aggregation: [ - { date: '2020-08-01', amount: 1000 }, - { date: '2020-08-02', amount: 2000 }, - { date: '2020-08-03', amount: 3000 }, - { date: '2020-08-04', amount: 4000 }, - { date: '2020-08-05', amount: 5000 }, - { date: '2020-08-06', amount: 6000 }, - { date: '2020-08-07', amount: 7000 }, - { date: '2020-08-08', amount: 8000 }, - { date: '2020-08-09', amount: 9000 }, - { date: '2020-08-10', amount: 10_000 }, - ], - change: { - ratio: 0.5, - amount: 10000, - }, - trendline: { - slope: 0, - intercept: 0, - }, + aggregation: aggregation, + change: changeOf(aggregation), + trendline: trendlineOf(aggregation), }, ); @@ -122,9 +164,38 @@ export class ExampleCostInsightsClient implements CostInsightsApi { product: string, group: string, duration: Duration, + project: Maybe, ): Promise { + const projectProductInsights = await this.request( + { product, group, duration, project }, + { + aggregation: [80_000, 110_000], + change: { + ratio: 0.375, + amount: 30_000, + }, + entities: [ + { + id: null, // entities with null ids will be appear as "Unlabeled" in product panels + aggregation: [45_000, 50_000], + }, + { + id: 'entity-a', + aggregation: [15_000, 20_000], + }, + { + id: 'entity-b', + aggregation: [20_000, 30_000], + }, + { + id: 'entity-e', + aggregation: [0, 10_000], + }, + ], + }, + ); const productInsights: ProductCost = await this.request( - { product, group, duration }, + { product, group, duration, project }, { aggregation: [200_000, 250_000], change: { @@ -134,7 +205,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { entities: [ { id: null, // entities with null ids will be appear as "Unlabeled" in product panels - aggregation: [45_000, 50_000], + aggregation: [15_000, 30_000], }, { id: 'entity-a', @@ -172,36 +243,48 @@ export class ExampleCostInsightsClient implements CostInsightsApi { }, ); - return productInsights; + return project ? projectProductInsights : productInsights; } async getAlerts(group: string): Promise { - const alerts: Alert[] = await this.request({ group }, [ - { - id: 'projectGrowth', - project: 'example-project', - periodStart: 'Q1 2020', - periodEnd: 'Q2 2020', - aggregation: [60_000, 120_000], - change: { - ratio: 1, - amount: 60000, - }, - products: [ - { - id: 'Compute Engine', - aggregation: [58_000, 118_000], - }, - { - id: 'Cloud Dataflow', - aggregation: [1200, 1500], - }, - { - id: 'Cloud Storage', - aggregation: [800, 500], - }, - ], + const projectGrowthData: ProjectGrowthData = { + project: 'example-project', + periodStart: 'Q2 2020', + periodEnd: 'Q3 2020', + aggregation: [60_000, 120_000], + change: { + ratio: 1, + amount: 60000, }, + products: [ + { id: 'Compute Engine', aggregation: [58_000, 118_000] }, + { id: 'Cloud Dataflow', aggregation: [1200, 1500] }, + { id: 'Cloud Storage', aggregation: [800, 500] }, + ], + }; + + const unlabeledDataflowData: UnlabeledDataflowData = { + periodStart: '2020-09-01', + periodEnd: '2020-09-30', + labeledCost: 6_200, + unlabeledCost: 7_000, + projects: [ + { + id: 'example-project-1', + unlabeledCost: 5_000, + labeledCost: 3_000, + }, + { + id: 'example-project-2', + unlabeledCost: 2_000, + labeledCost: 3_200, + }, + ], + }; + + const alerts: Alert[] = await this.request({ group }, [ + new ProjectGrowthAlert(projectGrowthData), + new UnlabeledDataflowAlert(unlabeledDataflowData), ]); return alerts; diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 7194b076df..cf7e997c97 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli-common": "^0.1.1-alpha.24", - "@backstage/config": "^0.1.1-alpha.24", - "@backstage/config-loader": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", + "@backstage/cli-common": "^0.1.1-alpha.25", + "@backstage/config": "^0.1.1-alpha.25", + "@backstage/config-loader": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", "@types/cors": "^2.8.6", "@types/express": "^4.17.6", "compression": "^1.7.4", @@ -61,7 +61,7 @@ } }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", "@types/compression": "^1.7.0", "@types/http-errors": "^1.6.3", "@types/morgan": "^1.9.0", diff --git a/packages/backend-common/src/config.ts b/packages/backend-common/src/config.ts index 247eb593cc..490070f80a 100644 --- a/packages/backend-common/src/config.ts +++ b/packages/backend-common/src/config.ts @@ -21,9 +21,10 @@ import { loadConfig } from '@backstage/config-loader'; * Load configuration for a Backend */ export async function loadBackendConfig() { + /* eslint-disable-next-line no-restricted-syntax */ const paths = findPaths(__dirname); const configs = await loadConfig({ - env: process.env.NODE_ENV ?? 'development', + env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development', rootPaths: [paths.targetRoot, paths.targetDir], shouldReadSecrets: true, }); diff --git a/packages/backend/package.json b/packages/backend/package.json index c3a912c93e..ef55b007a7 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "dist/index.cjs.js", "types": "src/index.ts", "private": true, @@ -18,24 +18,24 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.24", - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/config": "^0.1.1-alpha.24", - "@backstage/plugin-app-backend": "^0.1.1-alpha.24", - "@backstage/plugin-auth-backend": "^0.1.1-alpha.24", - "@backstage/plugin-catalog-backend": "^0.1.1-alpha.24", - "@backstage/plugin-graphql-backend": "^0.1.1-alpha.24", - "@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.24", - "@backstage/plugin-proxy-backend": "^0.1.1-alpha.24", - "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.24", - "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.24", - "@backstage/plugin-sentry-backend": "^0.1.1-alpha.24", - "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.24", + "@backstage/backend-common": "^0.1.1-alpha.25", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/config": "^0.1.1-alpha.25", + "@backstage/plugin-app-backend": "^0.1.1-alpha.25", + "@backstage/plugin-auth-backend": "^0.1.1-alpha.25", + "@backstage/plugin-catalog-backend": "^0.1.1-alpha.25", + "@backstage/plugin-graphql-backend": "^0.1.1-alpha.25", + "@backstage/plugin-kubernetes-backend": "^0.1.1-alpha.25", + "@backstage/plugin-proxy-backend": "^0.1.1-alpha.25", + "@backstage/plugin-rollbar-backend": "^0.1.1-alpha.25", + "@backstage/plugin-scaffolder-backend": "^0.1.1-alpha.25", + "@backstage/plugin-sentry-backend": "^0.1.1-alpha.25", + "@backstage/plugin-techdocs-backend": "^0.1.1-alpha.25", "@gitbeaker/node": "^23.5.0", "@octokit/rest": "^18.0.0", "azure-devops-node-api": "^10.1.1", "dockerode": "^3.2.0", - "example-app": "^0.1.1-alpha.24", + "example-app": "^0.1.1-alpha.25", "express": "^4.17.1", "express-promise-router": "^3.0.3", "knex": "^0.21.1", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 6a543f7f84..8fc4484d33 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,7 +20,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.24", + "@backstage/config": "^0.1.1-alpha.25", "@types/json-schema": "^7.0.5", "@types/yup": "^0.28.2", "json-schema": "^0.2.5", @@ -29,7 +29,7 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/catalog-model/src/EntityPolicies.ts b/packages/catalog-model/src/EntityPolicies.ts index ca59338c16..acdfb4df2a 100644 --- a/packages/catalog-model/src/EntityPolicies.ts +++ b/packages/catalog-model/src/EntityPolicies.ts @@ -40,10 +40,13 @@ class AllEntityPolicies implements EntityPolicy { async enforce(entity: Entity): Promise { let result = entity; for (const policy of this.policies) { - const output = await policy.enforce(entity); - if (output) { - result = output; + const output = await policy.enforce(result); + if (!output) { + throw new Error( + `Policy ${policy.constructor.name} did not return a result`, + ); } + result = output; } return result; } @@ -54,10 +57,10 @@ class AllEntityPolicies implements EntityPolicy { class AnyEntityPolicy implements EntityPolicy { constructor(private readonly policies: EntityPolicy[]) {} - async enforce(entity: Entity): Promise { + async enforce(entity: Entity): Promise { for (const policy of this.policies) { const output = await policy.enforce(entity); - if (output !== null) { + if (output) { return output; } } diff --git a/packages/cli-common/package.json b/packages/cli-common/package.json index abb4b5f9a1..d9492ef127 100644 --- a/packages/cli-common/package.json +++ b/packages/cli-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli-common", "description": "Common functionality used by cli, backend, and create-app", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "main": "src/index.ts", "types": "src/index.ts", diff --git a/packages/cli-common/src/paths.test.ts b/packages/cli-common/src/paths.test.ts index 087f95af38..2278ee3581 100644 --- a/packages/cli-common/src/paths.test.ts +++ b/packages/cli-common/src/paths.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +/* eslint-disable no-restricted-syntax */ import { resolve as resolvePath } from 'path'; import { findPaths, findRootPath, findOwnDir, findOwnRootDir } from './paths'; diff --git a/packages/cli/bin/backstage-cli b/packages/cli/bin/backstage-cli index a065b8ac5a..5c127af2ec 100755 --- a/packages/cli/bin/backstage-cli +++ b/packages/cli/bin/backstage-cli @@ -18,6 +18,7 @@ const path = require('path'); // Figure out whether we're running inside the backstage repo or as an installed dependency +/* eslint-disable-next-line no-restricted-syntax */ const isLocal = require('fs').existsSync(path.resolve(__dirname, '../src')); if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) { @@ -25,6 +26,7 @@ if (!isLocal || process.env.BACKSTAGE_E2E_CLI_TEST) { } else { require('ts-node').register({ transpileOnly: true, + /* eslint-disable-next-line no-restricted-syntax */ project: path.resolve(__dirname, '../../../tsconfig.json'), compilerOptions: { module: 'CommonJS', diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js index 113ba55818..2c0b05c196 100644 --- a/packages/cli/config/eslint.backend.js +++ b/packages/cli/config/eslint.backend.js @@ -67,6 +67,11 @@ module.exports = { selector: 'ImportDeclaration[source.value="winston"] ImportDefaultSpecifier', }, + { + message: + "`__dirname` doesn't refer to the same dir in production builds, try `resolvePackagePath()` from `@backstage/backend-common` instead.", + selector: 'Identifier[name="__dirname"]', + }, ], }, overrides: [ diff --git a/packages/cli/package.json b/packages/cli/package.json index 9bbbe9f29a..5bd732f1d2 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public" @@ -28,9 +28,9 @@ "backstage-cli": "bin/backstage-cli" }, "dependencies": { - "@backstage/cli-common": "^0.1.1-alpha.24", - "@backstage/config": "^0.1.1-alpha.24", - "@backstage/config-loader": "^0.1.1-alpha.24", + "@backstage/cli-common": "^0.1.1-alpha.25", + "@backstage/config": "^0.1.1-alpha.25", + "@backstage/config-loader": "^0.1.1-alpha.25", "@hot-loader/react-dom": "^16.13.0", "@lerna/package-graph": "^3.18.5", "@lerna/project": "^3.18.0", diff --git a/packages/cli/src/commands/app/build.ts b/packages/cli/src/commands/app/build.ts index 5d499cbc2d..9713acd233 100644 --- a/packages/cli/src/commands/app/build.ts +++ b/packages/cli/src/commands/app/build.ts @@ -22,7 +22,7 @@ import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ - env: process.env.NODE_ENV ?? 'production', + env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production', rootPaths: [paths.targetRoot, paths.targetDir], }); await buildBundle({ diff --git a/packages/cli/src/commands/app/serve.ts b/packages/cli/src/commands/app/serve.ts index c748d12a75..031214c264 100644 --- a/packages/cli/src/commands/app/serve.ts +++ b/packages/cli/src/commands/app/serve.ts @@ -22,7 +22,7 @@ import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ - env: process.env.NODE_ENV ?? 'development', + env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development', rootPaths: [paths.targetRoot, paths.targetDir], }); const waitForExit = await serveBundle({ diff --git a/packages/cli/src/commands/backend/dev.ts b/packages/cli/src/commands/backend/dev.ts index 8b2ff9285a..ca76f19da3 100644 --- a/packages/cli/src/commands/backend/dev.ts +++ b/packages/cli/src/commands/backend/dev.ts @@ -22,7 +22,7 @@ import { serveBackend } from '../../lib/bundler/backend'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ - env: process.env.NODE_ENV ?? 'development', + env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development', rootPaths: [paths.targetRoot, paths.targetDir], }); diff --git a/packages/cli/src/commands/config/print.ts b/packages/cli/src/commands/config/print.ts index bb626f520f..526f4d70b1 100644 --- a/packages/cli/src/commands/config/print.ts +++ b/packages/cli/src/commands/config/print.ts @@ -22,7 +22,8 @@ import { stringify as stringifyYaml } from 'yaml'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ - env: cmd.env ?? process.env.NODE_ENV ?? 'development', + env: + cmd.env ?? process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development', shouldReadSecrets: cmd.withSecrets ?? false, rootPaths: [paths.targetRoot, paths.targetDir], }); diff --git a/packages/cli/src/commands/index.ts b/packages/cli/src/commands/index.ts index fea250cd55..e5017b829a 100644 --- a/packages/cli/src/commands/index.ts +++ b/packages/cli/src/commands/index.ts @@ -133,7 +133,7 @@ export function registerCommands(program: CommanderStatic) { .option('--with-secrets', 'Include secrets in the printed configuration') .option( '--env ', - 'The environment to print configuration for [NODE_ENV or development]', + 'The environment to print configuration for [APP_ENV or NODE_ENV or development]', ) .option( '--format ', diff --git a/packages/cli/src/commands/plugin/export.ts b/packages/cli/src/commands/plugin/export.ts index 8cbbe5b3ee..4fe9cb3c03 100644 --- a/packages/cli/src/commands/plugin/export.ts +++ b/packages/cli/src/commands/plugin/export.ts @@ -22,7 +22,7 @@ import { buildBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ - env: process.env.NODE_ENV ?? 'production', + env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'production', rootPaths: [paths.targetRoot, paths.targetDir], }); await buildBundle({ diff --git a/packages/cli/src/commands/plugin/serve.ts b/packages/cli/src/commands/plugin/serve.ts index 8a04df8837..bae63f2fda 100644 --- a/packages/cli/src/commands/plugin/serve.ts +++ b/packages/cli/src/commands/plugin/serve.ts @@ -22,7 +22,7 @@ import { serveBundle } from '../../lib/bundler'; export default async (cmd: Command) => { const appConfigs = await loadConfig({ - env: process.env.NODE_ENV ?? 'development', + env: process.env.APP_ENV ?? process.env.NODE_ENV ?? 'development', rootPaths: [paths.targetRoot, paths.targetDir], }); const waitForExit = await serveBundle({ diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index e278164f4c..28e46bbd1a 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -27,18 +27,12 @@ const main = (argv: string[]) => { program.on('command:*', () => { console.log(); - console.log( - chalk.red(`Invalid command: ${chalk.cyan(program.args.join(' '))}`), - ); - console.log(chalk.red('See --help for a list of available commands.')); + console.log(chalk.red(`Invalid command: ${program.args.join(' ')}`)); console.log(); + program.outputHelp(); process.exit(1); }); - if (!process.argv.slice(2).length) { - program.outputHelp(chalk.yellow); - } - program.parse(argv); }; diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 118cb1b40c..00a555eebe 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -209,6 +209,7 @@ export async function createBackendConfig( ], target: 'node' as const, node: { + /* eslint-disable-next-line no-restricted-syntax */ __dirname: true, __filename: true, global: true, diff --git a/packages/cli/src/lib/paths.ts b/packages/cli/src/lib/paths.ts index 06c8f2ab38..a17034344d 100644 --- a/packages/cli/src/lib/paths.ts +++ b/packages/cli/src/lib/paths.ts @@ -16,4 +16,5 @@ import { findPaths } from '@backstage/cli-common'; +/* eslint-disable-next-line no-restricted-syntax */ export const paths = findPaths(__dirname); diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index 96ceb9784d..1a73515622 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -23,8 +23,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^{{version}}", - "@backstage/config": "^{{version}}", + "@backstage/backend-common": "^{{backstageVersion}}", + "@backstage/config": "^{{backstageVersion}}", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^{{version}}", + "@backstage/cli": "^{{backstageVersion}}", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", "msw": "^0.21.2" diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 6a17eba76c..0a95dc8c86 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.24", + "@backstage/config": "^0.1.1-alpha.25", "fs-extra": "^9.0.0", "yaml": "^1.9.2", "yup": "^0.29.1" diff --git a/packages/config-loader/src/lib/resolver.ts b/packages/config-loader/src/lib/resolver.ts index 44f0f6ea9e..3856598331 100644 --- a/packages/config-loader/src/lib/resolver.ts +++ b/packages/config-loader/src/lib/resolver.ts @@ -28,7 +28,7 @@ type ResolveOptions = { * Resolves all configuration files that should be loaded in the given environment. * * For each root directory, search for the default app-config.yaml, along with suffixed - * NODE_ENV and local variants, e.g. app-config.production.yaml or app-config.development.local.yaml + * APP_ENV and local variants, e.g. app-config.production.yaml or app-config.development.local.yaml * * The priority order of config loaded through suffixes is `env > local > none`, meaning that * for example app-config.development.yaml has higher priority than `app-config.local.yaml`. diff --git a/packages/config/package.json b/packages/config/package.json index d3695ebf33..589c87c15c 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/package.json b/packages/core-api/package.json index d6dd492906..de6a14bb2b 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/config": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@types/react": "^16.9", @@ -41,8 +41,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/test-utils-core": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/test-utils-core": "^0.1.1-alpha.25", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/packages/core/package.json b/packages/core/package.json index b665006fd2..2b0fa58266 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", @@ -29,9 +29,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/config": "^0.1.1-alpha.24", - "@backstage/core-api": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/config": "^0.1.1-alpha.25", + "@backstage/core-api": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -55,8 +55,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx b/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx index 12f19b17a1..d5a33f0f9b 100644 --- a/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx +++ b/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx @@ -60,7 +60,7 @@ const CHECKBOX_TREE_ITEMS = [ ]; export default { - title: 'CheckboxTree', + title: 'Inputs/CheckboxTree', component: CheckboxTree, }; diff --git a/packages/core/src/components/Select/Select.stories.tsx b/packages/core/src/components/Select/Select.stories.tsx index feb5c1cffb..833f3bc5f4 100644 --- a/packages/core/src/components/Select/Select.stories.tsx +++ b/packages/core/src/components/Select/Select.stories.tsx @@ -18,7 +18,7 @@ import React from 'react'; import { Select } from '.'; export default { - title: 'Select', + title: 'Inputs/Select', component: Select, }; diff --git a/packages/core/src/layout/InfoCard/InfoCard.tsx b/packages/core/src/layout/InfoCard/InfoCard.tsx index 09d31735cd..1a4ceb024b 100644 --- a/packages/core/src/layout/InfoCard/InfoCard.tsx +++ b/packages/core/src/layout/InfoCard/InfoCard.tsx @@ -76,19 +76,6 @@ const VARIANT_STYLES = { height: 'calc(100% - 10px)', // for pages without content header marginBottom: '10px', }, - contentheader: { - height: 'calc(100% - 40px)', // for pages with content header - }, - contentheadertabs: { - height: 'calc(100% - 97px)', // for pages with content header and tabs (Tingle) - }, - noShrink: { - flexShrink: 0, - }, - minheight300: { - minHeight: 300, - overflow: 'initial', - }, }, cardContent: { fullHeight: { @@ -97,10 +84,6 @@ const VARIANT_STYLES = { height100: { flex: 1, }, - contentRow: { - display: 'flex', - flexDirection: 'row', - }, }, }; @@ -120,10 +103,6 @@ const VARIANT_STYLES = { * Display the card full height suitable for DataGrid: * * ... - * - * Variants can be combined in a whitespace delimited list like so: - * - * ... */ type Props = { title?: ReactNode; diff --git a/packages/core/src/layout/ItemCard/ItemCard.stories.tsx b/packages/core/src/layout/ItemCard/ItemCard.stories.tsx index 998856e041..2b48cc4630 100644 --- a/packages/core/src/layout/ItemCard/ItemCard.stories.tsx +++ b/packages/core/src/layout/ItemCard/ItemCard.stories.tsx @@ -24,7 +24,7 @@ export default { export const Default = () => ( - + ( onClick={() => {}} /> - + ( export const Tags = () => ( - + ( label="Button" /> - + => { + /* eslint-disable-next-line no-restricted-syntax */ const paths = findPaths(__dirname); const questions: Question[] = [ diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 05bab2f415..21793bda81 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/core": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/core": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/docgen/bin/backstage-docgen b/packages/docgen/bin/backstage-docgen index a065b8ac5a..467a0cae8f 100755 --- a/packages/docgen/bin/backstage-docgen +++ b/packages/docgen/bin/backstage-docgen @@ -15,6 +15,7 @@ * limitations under the License. */ +/* eslint-disable no-restricted-syntax */ const path = require('path'); // Figure out whether we're running inside the backstage repo or as an installed dependency diff --git a/packages/docgen/package.json b/packages/docgen/package.json index 99591f5869..61efc0bbd4 100644 --- a/packages/docgen/package.json +++ b/packages/docgen/package.json @@ -1,7 +1,7 @@ { "name": "docgen", "description": "Tool for generating API Documentation for itself", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": true, "homepage": "https://backstage.io", "repository": { diff --git a/packages/docgen/src/generate.ts b/packages/docgen/src/generate.ts index 3587e2776b..66ee1a07a6 100644 --- a/packages/docgen/src/generate.ts +++ b/packages/docgen/src/generate.ts @@ -37,6 +37,7 @@ export async function generate( ); } + /* eslint-disable-next-line no-restricted-syntax */ const rootDir = resolvePath(__dirname, '../../..'); const srcDir = resolvePath(rootDir, 'packages', 'core-api', 'src'); const targetDir = resolvePath(targetPath); diff --git a/packages/docgen/src/index.ts b/packages/docgen/src/index.ts index 866299151a..f41629e7fd 100644 --- a/packages/docgen/src/index.ts +++ b/packages/docgen/src/index.ts @@ -21,6 +21,7 @@ import fs from 'fs-extra'; import { generate } from './generate'; const main = (argv: string[]) => { + /* eslint-disable-next-line no-restricted-syntax */ const pkgJson = fs.readJsonSync(resolvePath(__dirname, '../package.json')); program.name('docgen').version(pkgJson.version); diff --git a/packages/e2e-test/README.md b/packages/e2e-test/README.md index 6cf50283c5..e3706b6131 100644 --- a/packages/e2e-test/README.md +++ b/packages/e2e-test/README.md @@ -14,7 +14,7 @@ yarn tsc yarn build ``` -Once those tasks have completed, you can now run the test using `yarn start` inside this package. +Once those tasks have completed, you can now run the test using `yarn e2e-test run`. If you make changes to other packages you will need to rerun `yarn tsc && yarn build`. Changes to this package do not require a rebuild. diff --git a/packages/e2e-test/src/index.js b/packages/e2e-test/bin/e2e-test old mode 100644 new mode 100755 similarity index 79% rename from packages/e2e-test/src/index.js rename to packages/e2e-test/bin/e2e-test index 90f8030a0e..d6d449fe31 --- a/packages/e2e-test/src/index.js +++ b/packages/e2e-test/bin/e2e-test @@ -1,3 +1,4 @@ +#!/usr/bin/env node /* * Copyright 2020 Spotify AB * @@ -14,12 +15,15 @@ * limitations under the License. */ +const path = require('path'); + require('ts-node').register({ transpileOnly: true, - project: require('path').resolve(__dirname, '../../../tsconfig.json'), + /* eslint-disable-next-line no-restricted-syntax */ + project: path.resolve(__dirname, '../../../tsconfig.json'), compilerOptions: { module: 'CommonJS', }, }); -require('./e2e-test'); +require('../src'); diff --git a/packages/e2e-test/package.json b/packages/e2e-test/package.json index 6f8a952547..764e3c415a 100644 --- a/packages/e2e-test/package.json +++ b/packages/e2e-test/package.json @@ -1,7 +1,7 @@ { "name": "e2e-test", "description": "E2E test for verifying Backstage packages", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": true, "homepage": "https://backstage.io", "repository": { @@ -13,17 +13,22 @@ "backstage" ], "license": "Apache-2.0", - "main": "src/index.js", + "main": "src/index.ts", "scripts": { "start": "node .", "lint": "backstage-cli lint", "test": "backstage-cli test", "test:e2e": "yarn start" }, + "bin": { + "e2e-test": "bin/e2e-test" + }, "devDependencies": { - "@backstage/cli-common": "^0.1.1-alpha.24", + "@backstage/cli-common": "^0.1.1-alpha.25", "@types/fs-extra": "^9.0.1", "@types/node": "^13.7.2", + "chalk": "^4.0.0", + "commander": "^6.1.0", "fs-extra": "^9.0.0", "handlebars": "^4.7.3", "cross-fetch": "^3.0.6", diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewFooter.tsx b/packages/e2e-test/src/commands/index.ts similarity index 56% rename from plugins/cost-insights/src/components/CostOverviewCard/CostOverviewFooter.tsx rename to packages/e2e-test/src/commands/index.ts index 03920887b8..78ff3343f1 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewFooter.tsx +++ b/packages/e2e-test/src/commands/index.ts @@ -13,24 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; -import { Box } from '@material-ui/core'; -type CostOverviewFooterProps = { - children?: React.ReactNode; -}; +import { CommanderStatic } from 'commander'; +import { run } from './run'; -const CostOverviewFooter = ({ children }: CostOverviewFooterProps) => ( - - {React.Children.map(children, child => ( - {child} - ))} - -); - -export default CostOverviewFooter; +export function registerCommands(program: CommanderStatic) { + program.command('run').description('Run e2e tests').action(run); +} diff --git a/packages/e2e-test/src/e2e-test.ts b/packages/e2e-test/src/commands/run.ts similarity index 95% rename from packages/e2e-test/src/e2e-test.ts rename to packages/e2e-test/src/commands/run.ts index 104a481d46..3794db96b1 100644 --- a/packages/e2e-test/src/e2e-test.ts +++ b/packages/e2e-test/src/commands/run.ts @@ -24,15 +24,15 @@ import Browser from 'zombie'; import { spawnPiped, runPlain, - handleError, waitForPageWithText, waitFor, waitForExit, print, -} from './helpers'; +} from '../lib/helpers'; import pgtools from 'pgtools'; import { findPaths } from '@backstage/cli-common'; +// eslint-disable-next-line no-restricted-syntax const paths = findPaths(__dirname); const templatePackagePaths = [ @@ -41,7 +41,7 @@ const templatePackagePaths = [ 'packages/create-app/templates/default-app/packages/backend/package.json.hbs', ]; -async function main() { +export async function run() { const rootDir = await fs.mkdtemp(resolvePath(os.tmpdir(), 'backstage-e2e-')); print(`CLI E2E test root: ${rootDir}\n`); @@ -412,16 +412,3 @@ async function testBackendStart(appDir: string, isPostgres: boolean) { print('Backend startup test finished successfully'); } } - -process.on('unhandledRejection', (error: Error) => { - // Try to avoid exiting if the unhandled error is coming from jsdom, i.e. zombie. - // Those are typically errors on the page that should be benign, at least in the - // context of this test. We have other ways of asserting that the page is being - // rendered correctly. - if (error?.stack?.includes('node_modules/jsdom/lib')) { - console.log(`Ignored error inside jsdom, ${error}`); - } else { - handleError(error); - } -}); -main().catch(handleError); diff --git a/packages/e2e-test/src/index.ts b/packages/e2e-test/src/index.ts new file mode 100644 index 0000000000..2266439973 --- /dev/null +++ b/packages/e2e-test/src/index.ts @@ -0,0 +1,58 @@ +/* + * Copyright 2020 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import program from 'commander'; +import chalk from 'chalk'; +import { registerCommands } from './commands'; +import { version } from '../package.json'; +import { exitWithError } from './lib/helpers'; + +async function main(argv: string[]) { + program.name('e2e-test').version(version); + + registerCommands(program); + + program.on('command:*', () => { + console.log(); + console.log(chalk.red(`Invalid command: ${program.args.join(' ')}`)); + console.log(); + program.outputHelp(); + process.exit(1); + }); + + program.parse(argv); +} + +process.on('unhandledRejection', (rejection: unknown) => { + // Try to avoid exiting if the unhandled error is coming from jsdom, i.e. zombie. + // Those are typically errors on the page that should be benign, at least in the + // context of this test. We have other ways of asserting that the page is being + // rendered correctly. + if ( + rejection instanceof Error && + rejection?.stack?.includes('node_modules/jsdom/lib') + ) { + console.log(`Ignored error inside jsdom, ${rejection?.stack ?? rejection}`); + } else { + if (rejection instanceof Error) { + exitWithError(rejection); + } else { + exitWithError(new Error(`Unknown rejection: '${rejection}'`)); + } + } +}); + +main(process.argv).catch(exitWithError); diff --git a/packages/e2e-test/src/helpers.test.ts b/packages/e2e-test/src/lib/helpers.test.ts similarity index 100% rename from packages/e2e-test/src/helpers.test.ts rename to packages/e2e-test/src/lib/helpers.test.ts diff --git a/packages/e2e-test/src/helpers.ts b/packages/e2e-test/src/lib/helpers.ts similarity index 97% rename from packages/e2e-test/src/helpers.ts rename to packages/e2e-test/src/lib/helpers.ts index b59de75860..aded184f5c 100644 --- a/packages/e2e-test/src/helpers.ts +++ b/packages/e2e-test/src/lib/helpers.ts @@ -42,7 +42,7 @@ export function spawnPiped(cmd: string[], options?: SpawnOptions) { shell: true, ...options, }); - child.on('error', handleError); + child.on('error', exitWithError); const logPrefix = cmd.map(s => s.replace(/.+\//, '')).join(' '); child.stdout?.on( @@ -75,7 +75,7 @@ export async function runPlain(cmd: string[], options?: SpawnOptions) { } } -export function handleError(err: Error & { code?: unknown }) { +export function exitWithError(err: Error & { code?: unknown }) { process.stdout.write(`${err.name}: ${err.stack || err.message}\n`); if (typeof err.code === 'number') { diff --git a/packages/storybook/.storybook/main.js b/packages/storybook/.storybook/main.js index 2c58144acc..136b3945da 100644 --- a/packages/storybook/.storybook/main.js +++ b/packages/storybook/.storybook/main.js @@ -14,6 +14,7 @@ module.exports = { 'storybook-dark-mode/register', ], webpackFinal: async config => { + /* eslint-disable-next-line no-restricted-syntax */ const coreSrc = path.resolve(__dirname, '../../core/src'); // Mirror config in packages/cli/src/lib/bundler diff --git a/packages/storybook/.storybook/preview.js b/packages/storybook/.storybook/preview.js index f92e07b045..dd2d2e9cdd 100644 --- a/packages/storybook/.storybook/preview.js +++ b/packages/storybook/.storybook/preview.js @@ -28,18 +28,7 @@ addParameters({ export const parameters = { options: { storySort: { - order: [ - 'Example Plugin', - 'Header', - 'Sidebar', - 'Tabs', - 'Information Card', - 'Tabbed Card', - 'Table', - 'Status', - 'Trendline', - 'Progress Card', - ], + order: ['Plugins', 'Layout', 'Navigation'], }, }, }; diff --git a/packages/storybook/package.json b/packages/storybook/package.json index 4cfc21be59..b1e45a26b6 100644 --- a/packages/storybook/package.json +++ b/packages/storybook/package.json @@ -1,6 +1,6 @@ { "name": "storybook", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "description": "Storybook build for core package", "private": true, "scripts": { @@ -14,7 +14,7 @@ ] }, "dependencies": { - "@backstage/theme": "^0.1.1-alpha.24" + "@backstage/theme": "^0.1.1-alpha.25" }, "devDependencies": { "@storybook/addon-actions": "^6.0.21", diff --git a/packages/techdocs-cli/bin/techdocs-cli b/packages/techdocs-cli/bin/techdocs-cli index ccc9c23beb..9818edf417 100755 --- a/packages/techdocs-cli/bin/techdocs-cli +++ b/packages/techdocs-cli/bin/techdocs-cli @@ -15,6 +15,7 @@ * limitations under the License. */ +/* eslint-disable no-restricted-syntax */ const path = require('path'); // Figure out whether we're running inside the backstage repo or as an installed dependency diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 77940d8299..adce3dd101 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "CLI for running TechDocs locally.", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public" @@ -44,7 +44,7 @@ "ext": "ts" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", "commander": "^6.1.0", "fs-extra": "^9.0.1", "http-proxy": "^1.18.1", diff --git a/packages/techdocs-cli/src/index.ts b/packages/techdocs-cli/src/index.ts index da196d10b8..824bdb84fc 100644 --- a/packages/techdocs-cli/src/index.ts +++ b/packages/techdocs-cli/src/index.ts @@ -101,6 +101,7 @@ const main = (argv: string[]) => { // Local Backstage Preview const techdocsPreviewBundlePath = path.join( + /* eslint-disable-next-line no-restricted-syntax */ __dirname, '..', 'dist', diff --git a/packages/test-utils-core/package.json b/packages/test-utils-core/package.json index ffdcdc02fb..96d153d397 100644 --- a/packages/test-utils-core/package.json +++ b/packages/test-utils-core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils-core", "description": "Utilities to test Backstage core", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index efe5cafaed..1b7d35a197 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", @@ -29,10 +29,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/core-api": "^0.1.1-alpha.24", - "@backstage/test-utils-core": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/core-api": "^0.1.1-alpha.25", + "@backstage/test-utils-core": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", diff --git a/packages/theme/package.json b/packages/theme/package.json index e9f3a346ce..95d0ee150b 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/theme", "description": "material-ui theme for use with Backstage.", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "@material-ui/core": "^4.11.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24" + "@backstage/cli": "^0.1.1-alpha.25" }, "files": [ "dist" diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index d00d4497da..6820027c6e 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/core": "^0.1.1-alpha.24", - "@backstage/plugin-catalog": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/core": "^0.1.1-alpha.25", + "@backstage/plugin-catalog": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@kyma-project/asyncapi-react": "^0.13.1", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", @@ -39,9 +39,9 @@ "swagger-ui-react": "^3.31.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/dev-utils": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/dev-utils": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 98873f88c7..b402d9fa5b 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,8 +20,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.24", - "@backstage/config-loader": "^0.1.1-alpha.24", + "@backstage/backend-common": "^0.1.1-alpha.25", + "@backstage/config-loader": "^0.1.1-alpha.25", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -30,7 +30,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", "@types/supertest": "^2.0.8", "msw": "^0.20.5", "supertest": "^4.0.2" diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index 417fdc43b6..aed4280ca5 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -25,6 +25,7 @@ import { createRouter } from './router'; jest.mock('../lib/config', () => ({ injectEnvConfig: jest.fn() })); global.__non_webpack_require__ = { + /* eslint-disable-next-line no-restricted-syntax */ resolve: () => resolvePath(__dirname, '__fixtures__/app-dir/package.json'), }; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 513fb540f2..bace524383 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.24", - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/config": "^0.1.1-alpha.24", + "@backstage/backend-common": "^0.1.1-alpha.25", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/config": "^0.1.1-alpha.25", "@types/express": "^4.17.6", "compression": "^1.7.4", "cookie-parser": "^1.4.5", @@ -50,7 +50,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/jwt-decode": "2.2.1", diff --git a/plugins/auth-backend/src/providers/saml/provider.ts b/plugins/auth-backend/src/providers/saml/provider.ts index a90f3e3053..59e46a86d7 100644 --- a/plugins/auth-backend/src/providers/saml/provider.ts +++ b/plugins/auth-backend/src/providers/saml/provider.ts @@ -118,15 +118,18 @@ type SAMLProviderOptions = { }; export const createSamlProvider: AuthProviderFactory = ({ + globalConfig, config, tokenIssuer, }) => { + const url = new URL(globalConfig.baseUrl); + const providerId = 'saml'; const entryPoint = config.getString('entryPoint'); const issuer = config.getString('issuer'); const opts = { entryPoint, issuer, - path: '/auth/saml/handler/frame', + path: `${url.pathname}/${providerId}/handler/frame`, tokenIssuer, }; diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index bbd86f9d60..07f47fbb50 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.24", - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/config": "^0.1.1-alpha.24", + "@backstage/backend-common": "^0.1.1-alpha.25", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/config": "^0.1.1-alpha.25", "@octokit/graphql": "^4.5.6", "@types/express": "^4.17.6", "codeowners-utils": "^1.0.2", @@ -45,8 +45,8 @@ "yup": "^0.29.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/ldapjs": "^1.0.9", diff --git a/plugins/catalog-backend/src/database/search.test.ts b/plugins/catalog-backend/src/database/search.test.ts index fad17da733..d108820ca9 100644 --- a/plugins/catalog-backend/src/database/search.test.ts +++ b/plugins/catalog-backend/src/database/search.test.ts @@ -14,51 +14,36 @@ * limitations under the License. */ -import { ENTITY_DEFAULT_NAMESPACE, Entity } from '@backstage/catalog-model'; -import { buildEntitySearch, visitEntityPart } from './search'; -import type { DbEntitiesSearchRow } from './types'; +import { Entity, ENTITY_DEFAULT_NAMESPACE } from '@backstage/catalog-model'; +import { buildEntitySearch, mapToRows, traverse } from './search'; describe('search', () => { - describe('visitEntityPart', () => { + describe('traverse', () => { it('expands lists of strings to several rows', () => { const input = { a: ['b', 'c', 'd'] }; - const output: DbEntitiesSearchRow[] = []; - visitEntityPart('eid', '', input, output); + const output = traverse(input); expect(output).toEqual([ - { entity_id: 'eid', key: 'a', value: 'b' }, - { entity_id: 'eid', key: 'a', value: 'c' }, - { entity_id: 'eid', key: 'a', value: 'd' }, + { key: 'a', value: 'b' }, + { key: 'a', value: 'c' }, + { key: 'a', value: 'd' }, ]); }); it('expands objects', () => { const input = { a: { b: { c: 'd' }, e: 'f' } }; - const output: DbEntitiesSearchRow[] = []; - visitEntityPart('eid', '', input, output); + const output = traverse(input); expect(output).toEqual([ - { entity_id: 'eid', key: 'a.b.c', value: 'd' }, - { entity_id: 'eid', key: 'a.e', value: 'f' }, + { key: 'a.b.c', value: 'd' }, + { key: 'a.e', value: 'f' }, ]); }); - it('converts base types to strings or null', () => { - const input = { - a: true, - b: false, - c: 7, - d: 'string', - e: null, - f: undefined, - }; - const output: DbEntitiesSearchRow[] = []; - visitEntityPart('eid', '', input, output); + it('expands list of objects', () => { + const input = { root: { list: [{ a: 1 }, { a: 2 }] } }; + const output = traverse(input); expect(output).toEqual([ - { entity_id: 'eid', key: 'a', value: 'true' }, - { entity_id: 'eid', key: 'b', value: 'false' }, - { entity_id: 'eid', key: 'c', value: '7' }, - { entity_id: 'eid', key: 'd', value: 'string' }, - { entity_id: 'eid', key: 'e', value: null }, - { entity_id: 'eid', key: 'f', value: null }, + { key: 'root.list.a', value: 1 }, + { key: 'root.list.a', value: 2 }, ]); }); @@ -76,34 +61,47 @@ describe('search', () => { }, d: 'd', }; - const output: DbEntitiesSearchRow[] = []; - visitEntityPart('eid', '', input, output); + const output = traverse(input); expect(output).toEqual([ - { entity_id: 'eid', key: 'a', value: 'a' }, - { entity_id: 'eid', key: 'metadata.b', value: 'b' }, - { entity_id: 'eid', key: 'metadata.c', value: 'c' }, - { entity_id: 'eid', key: 'd', value: 'd' }, + { key: 'a', value: 'a' }, + { key: 'metadata.b', value: 'b' }, + { key: 'metadata.c', value: 'c' }, + { key: 'd', value: 'd' }, ]); }); + }); - it('expands list of objects', () => { - const input = { root: { list: [{ a: 1 }, { a: 2 }] } }; - const output: DbEntitiesSearchRow[] = []; - visitEntityPart('eid', '', input, output); + describe('mapToRows', () => { + it('converts base types to strings or null', () => { + const input = [ + { key: 'a', value: true }, + { key: 'b', value: false }, + { key: 'c', value: 7 }, + { key: 'd', value: 'string' }, + { key: 'e', value: null }, + { key: 'f', value: undefined }, + ]; + const output = mapToRows(input, 'eid'); expect(output).toEqual([ - { entity_id: 'eid', key: 'root.list.a', value: '1' }, - { entity_id: 'eid', key: 'root.list.a', value: '2' }, + { entity_id: 'eid', key: 'a', value: 'true' }, + { entity_id: 'eid', key: 'b', value: 'false' }, + { entity_id: 'eid', key: 'c', value: '7' }, + { entity_id: 'eid', key: 'd', value: 'string' }, + { entity_id: 'eid', key: 'e', value: null }, + { entity_id: 'eid', key: 'f', value: null }, ]); }); it('emits lowercase version of keys and values', () => { - const input = { theRoot: { listItems: [{ a: 'One' }, { a: 2 }] } }; - const output: DbEntitiesSearchRow[] = []; - visitEntityPart('eid', '', input, output); - expect(output).toEqual([ - { entity_id: 'eid', key: 'theroot.listitems.a', value: 'one' }, - { entity_id: 'eid', key: 'theroot.listitems.a', value: '2' }, - ]); + const input = [{ key: 'fOo', value: 'BaR' }]; + const output = mapToRows(input, 'eid'); + expect(output).toEqual([{ entity_id: 'eid', key: 'foo', value: 'bar' }]); + }); + + it('skips very large values', () => { + const input = [{ key: 'foo', value: 'a'.repeat(10000) }]; + const output = mapToRows(input, 'eid'); + expect(output).toEqual([]); }); }); @@ -115,6 +113,8 @@ describe('search', () => { metadata: { name: 'n' }, }; expect(buildEntitySearch('eid', input)).toEqual([ + { entity_id: 'eid', key: 'apiversion', value: 'a' }, + { entity_id: 'eid', key: 'kind', value: 'b' }, { entity_id: 'eid', key: 'metadata.name', value: 'n' }, { entity_id: 'eid', key: 'metadata.namespace', value: null }, { entity_id: 'eid', key: 'metadata.uid', value: null }, @@ -123,8 +123,6 @@ describe('search', () => { key: 'metadata.namespace', value: ENTITY_DEFAULT_NAMESPACE, }, - { entity_id: 'eid', key: 'apiversion', value: 'a' }, - { entity_id: 'eid', key: 'kind', value: 'b' }, ]); }); }); diff --git a/plugins/catalog-backend/src/database/search.ts b/plugins/catalog-backend/src/database/search.ts index bbed2fac47..863b86ee9a 100644 --- a/plugins/catalog-backend/src/database/search.ts +++ b/plugins/catalog-backend/src/database/search.ts @@ -28,16 +28,18 @@ const SPECIAL_KEYS = [ 'metadata.generation', ]; -function toValue(current: any): string | null { - if (current === undefined || current === null) { - return null; - } +// The maximum length allowed for search values. These columns are indexed, and +// database engines do not like to index on massive values. For example, +// postgres will balk after 8191 byte line sizes. +const MAX_VALUE_LENGTH = 200; - return String(current).toLowerCase(); -} +type Kv = { + key: string; + value: any; +}; -// Helper for iterating through a nested structure and outputting a list of -// path->value entries. +// Helper for traversing through a nested structure and outputting a list of +// path->value entries of the leaves. // // For example, this yaml structure // @@ -53,57 +55,77 @@ function toValue(current: any): string | null { // // will result in // -// "a", "1" +// "a", 1 // "b.c", null // "b.e": "f" // "b.e": "g" -// "h.i": "1" +// "h.i": 1 // "h.j": "k" -// "h.i": "2" +// "h.i": 2 // "h.j": "l" -export function visitEntityPart( - entityId: string, - path: string, - current: any, - output: DbEntitiesSearchRow[], -) { - // ignored - if (SPECIAL_KEYS.includes(path)) { - return; - } +export function traverse(root: any): Kv[] { + const output: Kv[] = []; - // empty or scalar - if ( - current === undefined || - current === null || - ['string', 'number', 'boolean'].includes(typeof current) - ) { - output.push({ entity_id: entityId, key: path, value: toValue(current) }); - return; - } - - // unknown - if (typeof current !== 'object') { - return; - } - - // array - if (Array.isArray(current)) { - for (const item of current) { - visitEntityPart(entityId, path, item, output); + function visit(path: string, current: any) { + if (SPECIAL_KEYS.includes(path)) { + return; + } + + // empty or scalar + if ( + current === undefined || + current === null || + ['string', 'number', 'boolean'].includes(typeof current) + ) { + output.push({ key: path, value: current }); + return; + } + + // unknown + if (typeof current !== 'object') { + return; + } + + // array + if (Array.isArray(current)) { + for (const item of current) { + // keep the same path as currently + visit(path, item); + } + return; + } + + // object + for (const [key, value] of Object.entries(current)) { + visit(path ? `${path}.${key}` : key, value); } - return; } - // object - for (const [key, value] of Object.entries(current)) { - visitEntityPart( - entityId, - (path ? `${path}.${key}` : key).toLowerCase(), - value, - output, - ); + visit('', root); + + return output; +} + +// Translates a number of raw data rows to search table rows +export function mapToRows( + input: Kv[], + entityId: string, +): DbEntitiesSearchRow[] { + const result: DbEntitiesSearchRow[] = []; + + for (let { key, value } of input) { + key = key.toLowerCase(); + if (value === undefined || value === null) { + result.push({ entity_id: entityId, key, value: null }); + } else { + value = String(value).toLowerCase(); + if (value.length <= MAX_VALUE_LENGTH) { + result.push({ entity_id: entityId, key, value }); + } + } } + + return result; } /** @@ -117,38 +139,20 @@ export function buildEntitySearch( entityId: string, entity: Entity, ): DbEntitiesSearchRow[] { + // Visit the entire structure recursively + const raw = traverse(entity); + // Start with some special keys that are always present because you want to // be able to easily search for null specifically - const result: DbEntitiesSearchRow[] = [ - { - entity_id: entityId, - key: 'metadata.name', - value: toValue(entity.metadata.name), - }, - { - entity_id: entityId, - key: 'metadata.namespace', - value: toValue(entity.metadata.namespace), - }, - { - entity_id: entityId, - key: 'metadata.uid', - value: toValue(entity.metadata.uid), - }, - ]; + raw.push({ key: 'metadata.name', value: entity.metadata.name }); + raw.push({ key: 'metadata.namespace', value: entity.metadata.namespace }); + raw.push({ key: 'metadata.uid', value: entity.metadata.uid }); // Namespace not specified has the default value "default", so we want to // match on that as well if (!entity.metadata.namespace) { - result.push({ - entity_id: entityId, - key: 'metadata.namespace', - value: toValue(ENTITY_DEFAULT_NAMESPACE), - }); + raw.push({ key: 'metadata.namespace', value: ENTITY_DEFAULT_NAMESPACE }); } - // Visit the entire structure recursively - visitEntityPart(entityId, '', entity, result); - - return result; + return mapToRows(raw, entityId); } diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 29f85005c1..3c8c45d246 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graphql", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,9 +20,9 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.24", - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/config": "^0.1.1-alpha.24", + "@backstage/backend-common": "^0.1.1-alpha.25", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/config": "^0.1.1-alpha.25", "@graphql-modules/core": "^0.7.17", "apollo-server": "^2.16.1", "graphql": "^15.3.0", @@ -32,8 +32,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", "@graphql-codegen/cli": "^1.17.7", "@graphql-codegen/typescript": "^1.17.7", "@graphql-codegen/typescript-resolvers": "^1.17.7", diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 23eeb7c8a1..3d0c932cdd 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,11 +21,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/core": "^0.1.1-alpha.24", - "@backstage/plugin-scaffolder": "^0.1.1-alpha.24", - "@backstage/plugin-techdocs": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/core": "^0.1.1-alpha.25", + "@backstage/plugin-scaffolder": "^0.1.1-alpha.25", + "@backstage/plugin-techdocs": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -41,9 +41,9 @@ "swr": "^0.3.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/dev-utils": "^0.1.1-alpha.24", - "@backstage/test-utils": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/dev-utils": "^0.1.1-alpha.25", + "@backstage/test-utils": "^0.1.1-alpha.25", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 761f22f4b1..06c1dee340 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/core": "^0.1.1-alpha.24", - "@backstage/plugin-catalog": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/core": "^0.1.1-alpha.25", + "@backstage/plugin-catalog": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -38,8 +38,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/dev-utils": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/dev-utils": "^0.1.1-alpha.25", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/circleci/src/components/Router.tsx b/plugins/circleci/src/components/Router.tsx index 41764b3d7d..5fe9a20eff 100644 --- a/plugins/circleci/src/components/Router.tsx +++ b/plugins/circleci/src/components/Router.tsx @@ -21,16 +21,14 @@ import { BuildWithStepsPage } from './BuildWithStepsPage/'; import { BuildsPage } from './BuildsPage'; import { CIRCLECI_ANNOTATION } from '../constants'; import { Entity } from '@backstage/catalog-model'; -import { WarningPanel } from '@backstage/core'; +import { MissingAnnotationEmptyState } from '@backstage/core'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[CIRCLECI_ANNOTATION]); export const Router = ({ entity }: { entity: Entity }) => !isPluginApplicableToEntity(entity) ? ( - -
{CIRCLECI_ANNOTATION}
annotation is missing on the entity. -
+ ) : ( } /> diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 290e3f2e80..80eb1021bc 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.1.1-alpha.24", + "version": "0.1.1-alpha.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,10 +21,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.1.1-alpha.24", - "@backstage/core": "^0.1.1-alpha.24", - "@backstage/plugin-catalog": "^0.1.1-alpha.24", - "@backstage/theme": "^0.1.1-alpha.24", + "@backstage/catalog-model": "^0.1.1-alpha.25", + "@backstage/core": "^0.1.1-alpha.25", + "@backstage/plugin-catalog": "^0.1.1-alpha.25", + "@backstage/theme": "^0.1.1-alpha.25", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.45", @@ -40,8 +40,8 @@ "react-use": "^15.3.3" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.24", - "@backstage/dev-utils": "^0.1.1-alpha.24", + "@backstage/cli": "^0.1.1-alpha.25", + "@backstage/dev-utils": "^0.1.1-alpha.25", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^10.4.1", "@testing-library/user-event": "^12.0.7", diff --git a/plugins/cloudbuild/src/components/Router.tsx b/plugins/cloudbuild/src/components/Router.tsx index b2653a05f2..af4959f4be 100644 --- a/plugins/cloudbuild/src/components/Router.tsx +++ b/plugins/cloudbuild/src/components/Router.tsx @@ -20,7 +20,7 @@ import { rootRouteRef, buildRouteRef } from '../plugin'; import { WorkflowRunDetails } from './WorkflowRunDetails'; import { WorkflowRunsTable } from './WorkflowRunsTable'; import { CLOUDBUILD_ANNOTATION } from './useProjectName'; -import { WarningPanel } from '@backstage/core'; +import { MissingAnnotationEmptyState } from '@backstage/core'; export const isPluginApplicableToEntity = (entity: Entity) => Boolean(entity.metadata.annotations?.[CLOUDBUILD_ANNOTATION]); @@ -28,9 +28,7 @@ export const isPluginApplicableToEntity = (entity: Entity) => export const Router = ({ entity }: { entity: Entity }) => // TODO(shmidt-i): move warning to a separate standardized component !isPluginApplicableToEntity(entity) ? ( - -
{CLOUDBUILD_ANNOTATION}
annotation is missing on the entity. -
+ ) : ( , ): Promise; /** diff --git a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx index 6e6f935137..0600f89c60 100644 --- a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx +++ b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.test.tsx @@ -17,19 +17,18 @@ import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; import AlertActionCard from './AlertActionCard'; -import { AlertType, ProjectGrowthAlert } from '../../types'; -import { getAlertText } from '../../utils/alerts'; +import { ProjectGrowthAlert, ProjectGrowthData } from '../../types'; import { MockScrollProvider } from '../../utils/tests'; -const alert = { - id: AlertType.ProjectGrowth, +const data: ProjectGrowthData = { aggregation: [500000.8, 970502.8], project: 'test-project', periodStart: '2019-10-01', periodEnd: '2020-03-31', change: { ratio: 120, amount: 120000 }, products: [], -} as ProjectGrowthAlert; +}; +const alert = new ProjectGrowthAlert(data); describe('', () => { it('Renders an alert', async () => { @@ -40,11 +39,7 @@ describe('', () => { ); expect(rendered.getByText('1')).toBeInTheDocument(); - const text = getAlertText(alert); - expect(text).toBeDefined(); - if (text) { - expect(rendered.getByText(text.title)).toBeInTheDocument(); - expect(rendered.getByText(text.subtitle)).toBeInTheDocument(); - } + expect(rendered.getByText(alert.title)).toBeInTheDocument(); + expect(rendered.getByText(alert.subtitle)).toBeInTheDocument(); }); }); diff --git a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx index f568f999f8..a83c7bc7ce 100644 --- a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx +++ b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCard.tsx @@ -17,10 +17,9 @@ import React from 'react'; import { Avatar, Card, CardHeader } from '@material-ui/core'; import { useScroll } from '../../hooks'; import { Alert } from '../../types'; -import { getAlertText, getAlertNavigation } from '../../utils/alerts'; import { - useAlertActionCardStyles as useStyles, useAlertActionCardHeader as useHeaderStyles, + useAlertActionCardStyles as useStyles, } from '../../utils/styles'; type AlertActionCardProps = { @@ -29,9 +28,8 @@ type AlertActionCardProps = { }; const AlertActionCard = ({ alert, number }: AlertActionCardProps) => { - const { scrollIntoView } = useScroll(getAlertNavigation(alert, number)); + const { scrollIntoView } = useScroll(`alert-${number}`); const headerClasses = useHeaderStyles(); - const text = getAlertText(alert); const classes = useStyles(); return ( @@ -39,8 +37,8 @@ const AlertActionCard = ({ alert, number }: AlertActionCardProps) => { {number}} - title={text?.title} - subheader={text?.subtitle} + title={alert.title} + subheader={alert.subtitle} /> ); diff --git a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx index 4ddc26e040..1ea1fb6a96 100644 --- a/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx +++ b/plugins/cost-insights/src/components/AlertActionCardList/AlertActionCardList.tsx @@ -25,7 +25,7 @@ type AlertActionCardList = { const AlertActionCardList: FC = ({ alerts }) => ( {alerts.map((alert, index) => ( - + {index < alerts.length - 1 && } diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx index 59dac3a758..e0aa0f02d9 100644 --- a/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx +++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsights.tsx @@ -19,7 +19,6 @@ import { Grid } from '@material-ui/core'; import AlertInsightsSection from './AlertInsightsSection'; import AlertInsightsHeader from './AlertInsightsHeader'; import { Alert } from '../../types'; -import { renderAlert } from '../../utils/alerts'; const title = "Your team's action items"; const subtitle = @@ -37,11 +36,7 @@ const AlertInsights = ({ alerts }: AlertInsightsProps) => ( {alerts.map((alert, index) => ( - + ))} diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx index c00d924cf2..d2305e390d 100644 --- a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx +++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSection.tsx @@ -16,45 +16,28 @@ import React from 'react'; import { Box, Button } from '@material-ui/core'; import AlertInsightsSectionHeader from './AlertInsightsSectionHeader'; -import { - getAlertButtonText, - getAlertText, - getAlertUrl, -} from '../../utils/alerts'; -import { Alert, Currency } from '../../types'; -import { useCurrency } from '../../hooks'; +import { Alert } from '../../types'; type AlertInsightsSectionProps = { alert: Alert; number: number; - render: (alert: Alert, currency: Currency) => JSX.Element; }; -const AlertInsightsSection = ({ - alert, - number, - render, -}: AlertInsightsSectionProps) => { - const [currency] = useCurrency(); - const text = getAlertText(alert); - const url = getAlertUrl(alert); - const buttonText = getAlertButtonText(alert); - +const AlertInsightsSection = ({ alert, number }: AlertInsightsSectionProps) => { return ( - {/* */} - {render(alert, currency)} + {alert.element} ); }; diff --git a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx index 5d5f94b9be..a561735d58 100644 --- a/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx +++ b/plugins/cost-insights/src/components/AlertInsights/AlertInsightsSectionHeader.tsx @@ -15,26 +15,22 @@ */ import React from 'react'; -import { Avatar, Box, Typography, Grid } from '@material-ui/core'; -import { Alert } from '../../types'; -import { getAlertNavigation } from '../../utils/alerts'; +import { Avatar, Box, Grid, Typography } from '@material-ui/core'; import { useAlertInsightsSectionStyles as useStyles } from '../../utils/styles'; import { useScroll } from '../../hooks'; type AlertInsightsSectionHeaderProps = { - alert: Alert; number: number; title: string; subtitle: string; }; const AlertInsightsSectionHeader = ({ - alert, number, title, subtitle, }: AlertInsightsSectionHeaderProps) => { - const { ScrollAnchor } = useScroll(getAlertNavigation(alert, number)); + const { ScrollAnchor } = useScroll(`alert-${number}`); const classes = useStyles(); return ( diff --git a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx index 0a422c335a..e7ecc8894b 100644 --- a/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx +++ b/plugins/cost-insights/src/components/AlertInstructionsLayout/AlertInstructionsLayout.tsx @@ -19,6 +19,7 @@ import { Box, Button, Container, makeStyles } from '@material-ui/core'; import ChevronLeftIcon from '@material-ui/icons/ChevronLeft'; import { Header, Page, pageTheme } from '@backstage/core'; import { CostInsightsThemeProvider } from '../CostInsightsPage/CostInsightsThemeProvider'; +import { ConfigProvider, CurrencyProvider } from '../../hooks'; const useStyles = makeStyles(theme => ({ root: { @@ -39,21 +40,29 @@ const AlertInstructionsLayout = ({ const classes = useStyles(); return ( - -
- - - - - {children} - - + + + +
+ + + + + {children} + + + + ); }; diff --git a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx index c426eff8bc..8922ac31e0 100644 --- a/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx +++ b/plugins/cost-insights/src/components/CostInsightsPage/CostInsightsPage.tsx @@ -15,7 +15,7 @@ */ import React, { useCallback, useEffect, useState } from 'react'; -import { Box, Container, Divider, Grid } from '@material-ui/core'; +import { Box, Container, Divider, Grid, Typography } from '@material-ui/core'; import { Progress, useApi, featureFlagsApiRef } from '@backstage/core'; import { default as MaterialAlert } from '@material-ui/lab/Alert'; import { costInsightsApiRef } from '../../api'; @@ -41,6 +41,7 @@ import { } from '../../hooks'; import { Alert, Cost, intervalsOf, Maybe, Project } from '../../types'; import { mapLoadingToProps } from './selector'; +import ProjectSelect from '../ProjectSelect'; const CostInsightsPage = () => { const flags = useApi(featureFlagsApiRef).getFlags(); @@ -55,7 +56,7 @@ const CostInsightsPage = () => { const [alerts, setAlerts] = useState>(null); const [error, setError] = useState>(null); - const { pageFilters } = useFilters(p => p); + const { pageFilters, setPageFilters } = useFilters(p => p); const { loadingActions, loadingGroups, @@ -63,6 +64,7 @@ const CostInsightsPage = () => { dispatchInitial, dispatchInsights, dispatchNone, + dispatchReset, } = useLoading(mapLoadingToProps); /* eslint-disable react-hooks/exhaustive-deps */ @@ -75,8 +77,15 @@ const CostInsightsPage = () => { const dispatchLoadingInitial = useCallback(dispatchInitial, []); const dispatchLoadingInsights = useCallback(dispatchInsights, []); const dispatchLoadingNone = useCallback(dispatchNone, []); + const dispatchLoadingReset = useCallback(dispatchReset, []); /* eslint-enable react-hooks/exhaustive-deps */ + const setProject = (project: Maybe) => + setPageFilters({ + ...pageFilters, + project: project === 'all' ? null : project, + }); + useEffect(() => { async function getInsights() { setError(null); @@ -165,6 +174,41 @@ const CostInsightsPage = () => { ); } + const onProjectSelect = (project: Maybe) => { + setProject(project); + dispatchLoadingReset(loadingActions); + }; + + const CostOverviewBanner = () => ( + + + Cost Overview + + + {!!flags.get('cost-insights-currencies') && ( + + + + )} + + + + ); + return ( @@ -180,15 +224,6 @@ const CostInsightsPage = () => { justifyContent="flex-end" mb={2} > - {!!flags.get('cost-insights-currencies') && ( - - - - )} @@ -212,6 +247,9 @@ const CostInsightsPage = () => { )} + + + {!!dailyCost.aggregation.length && ( diff --git a/plugins/cost-insights/src/components/CostInsightsPage/selector.tsx b/plugins/cost-insights/src/components/CostInsightsPage/selector.tsx index 51db47c082..8a9cf021c9 100644 --- a/plugins/cost-insights/src/components/CostInsightsPage/selector.tsx +++ b/plugins/cost-insights/src/components/CostInsightsPage/selector.tsx @@ -14,7 +14,11 @@ * limitations under the License. */ import { MapLoadingToProps } from '../../hooks'; -import { getResetState, DefaultLoadingAction } from '../../types'; +import { + getResetState, + DefaultLoadingAction, + getResetStateWithoutInitial, +} from '../../types'; type CostInsightsPageLoadingProps = { loadingActions: Array; @@ -23,6 +27,7 @@ type CostInsightsPageLoadingProps = { dispatchInitial: (isLoading: boolean) => void; dispatchInsights: (isLoading: boolean) => void; dispatchNone: (loadingActions: string[]) => void; + dispatchReset: (loadingActions: string[]) => void; }; export const mapLoadingToProps: MapLoadingToProps = ({ @@ -39,4 +44,6 @@ export const mapLoadingToProps: MapLoadingToProps dispatch({ [DefaultLoadingAction.CostInsightsPage]: isLoading }), dispatchNone: (loadingActions: string[]) => dispatch(getResetState(loadingActions)), + dispatchReset: (loadingActions: string[]) => + dispatch(getResetStateWithoutInitial(loadingActions)), }); diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index ef174132a3..b8d1be5d01 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -19,10 +19,8 @@ import { Box, Card, CardContent, Divider } from '@material-ui/core'; import CostOverviewChart from '../CostOverviewChart'; import CostOverviewChartLegend from '../CostOverviewChartLegend'; import CostOverviewHeader from './CostOverviewHeader'; -import CostOverviewFooter from './CostOverviewFooter'; import MetricSelect from '../MetricSelect'; import PeriodSelect from '../PeriodSelect'; -import ProjectSelect from '../ProjectSelect'; import { useScroll, useFilters, useConfig } from '../../hooks'; import { mapFiltersToProps } from './selector'; import { DefaultNavigation } from '../../utils/navigation'; @@ -45,7 +43,6 @@ const CostOverviewCard = ({ change, aggregation, trendline, - projects, }: CostOverviewCardProps) => { const { metrics } = useConfig(); const { ScrollAnchor } = useScroll(DefaultNavigation.CostOverviewCard); @@ -64,7 +61,7 @@ const CostOverviewCard = ({ - + - - + - + ); diff --git a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx index 1d24139c64..c9790a5bfa 100644 --- a/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx +++ b/plugins/cost-insights/src/components/ProductInsightsCard/ProductInsightsCard.tsx @@ -39,7 +39,7 @@ const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { const [resource, setResource] = useState>(null); const [error, setError] = useState>(null); - const { group, product: productFilter, setProduct } = useFilters( + const { group, product: productFilter, setProduct, project } = useFilters( mapFiltersToProps(product.kind), ); const { loadingProduct, dispatchLoading } = useLoading( @@ -68,6 +68,7 @@ const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { product.kind, group!, productFilter!.duration, + project, ); setResource(p); } catch (e) { @@ -87,6 +88,7 @@ const ProductInsightsCard = ({ product }: ProductInsightsCardProps) => { productFilter, group, product.kind, + project, ]); const onPeriodSelect = (duration: Duration) => { diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx index 5c3856cc9e..b914b3885d 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.test.tsx @@ -17,7 +17,7 @@ import React from 'react'; import { renderInTestApp } from '@backstage/test-utils'; import ProjectGrowthAlertCard from './ProjectGrowthAlertCard'; -import { createMockProjectGrowthAlert } from '../../utils/mockData'; +import { createMockProjectGrowthData } from '../../utils/mockData'; import { MockCurrencyProvider, MockConfigProvider } from '../../utils/tests'; import { AlertCost, defaultCurrencies, findAlways } from '../../types'; @@ -29,8 +29,8 @@ const MockAlertCosts: AlertCost[] = [ { id: 'test-id-2', aggregation: [235, 400] }, ]; -const MockProjectGrowthAlert = createMockProjectGrowthAlert(alert => ({ - ...alert, +const MockProjectGrowthAlert = createMockProjectGrowthData(data => ({ + ...data, project: MockProject, products: MockAlertCosts, })); diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx index c27b201202..8d1d1d29b1 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertCard.tsx @@ -19,11 +19,11 @@ import { Box } from '@material-ui/core'; import { InfoCard } from '@backstage/core'; import ResourceGrowthBarChart from '../ResourceGrowthBarChart'; import ResourceGrowthBarChartLegend from '../ResourceGrowthBarChartLegend'; -import { Duration, ProjectGrowthAlert } from '../../types'; +import { Duration, ProjectGrowthData } from '../../types'; import { pluralOf } from '../../utils/grammar'; type ProjectGrowthAlertProps = { - alert: ProjectGrowthAlert; + alert: ProjectGrowthData; }; const ProjectGrowthAlertCard = ({ alert }: ProjectGrowthAlertProps) => { diff --git a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx index f8f4a90a9e..cde989c9dd 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx @@ -18,20 +18,19 @@ import React from 'react'; import { Box, Typography } from '@material-ui/core'; import { InfoCard } from '@backstage/core'; import AlertInstructionsLayout from '../AlertInstructionsLayout'; -import ProjectGrowthAlertCard from '../ProjectGrowthAlertCard'; import { - AlertType, + Alert, Duration, Entity, Product, ProjectGrowthAlert, + ProjectGrowthData, } from '../../types'; import ResourceGrowthBarChartLegend from '../ResourceGrowthBarChartLegend'; import ResourceGrowthBarChart from '../ResourceGrowthBarChart'; const ProjectGrowthInstructionsPage = () => { - const projectGrowthAlert: ProjectGrowthAlert = { - id: AlertType.ProjectGrowth, + const alertData: ProjectGrowthData = { project: 'example-project', periodStart: 'Q1 2020', periodEnd: 'Q2 2020', @@ -55,6 +54,7 @@ const ProjectGrowthInstructionsPage = () => { }, ], }; + const projectGrowthAlert: Alert = new ProjectGrowthAlert(alertData); const product: Product = { kind: 'ComputeEngine', @@ -135,7 +135,7 @@ const ProjectGrowthInstructionsPage = () => { comparison of cloud products over the examined time period: - + {projectGrowthAlert.element} This allows you to quickly see which cloud products contributed to the diff --git a/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.tsx b/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.tsx index cbcd8ede4e..934ad99f63 100644 --- a/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.tsx +++ b/plugins/cost-insights/src/components/ProjectSelect/ProjectSelect.tsx @@ -48,7 +48,7 @@ const ProjectSelect = ({ project, projects, onSelect }: ProjectSelectProps) => {