Merge branch 'main' into drew08t/canvas-add-zoom
This commit is contained in:
+8
-99
@@ -1349,9 +1349,6 @@ exports[`better eslint`] = {
|
||||
"public/app/core/components/PasswordField/PasswordField.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||
],
|
||||
"public/app/core/components/PermissionList/AddPermission.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/core/components/QueryOperationRow/OperationRowHelp.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
@@ -1380,8 +1377,7 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Styles should be written using objects.", "3"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "4"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "5"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "6"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "7"]
|
||||
[0, 0, 0, "Styles should be written using objects.", "6"]
|
||||
],
|
||||
"public/app/core/components/RolePicker/RolePickerMenu.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
@@ -2769,43 +2765,8 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "4"]
|
||||
],
|
||||
"public/app/features/canvas/elements/droneFront.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/features/canvas/elements/droneSide.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/features/canvas/elements/droneTop.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"]
|
||||
],
|
||||
"public/app/features/canvas/elements/ellipse.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
||||
],
|
||||
"public/app/features/canvas/elements/icon.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/features/canvas/elements/metricValue.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"]
|
||||
],
|
||||
"public/app/features/canvas/elements/server/server.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "3"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "4"]
|
||||
],
|
||||
"public/app/features/canvas/elements/text.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"]
|
||||
],
|
||||
"public/app/features/canvas/elements/windTurbine.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/canvas/runtime/element.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
@@ -2885,6 +2846,10 @@ exports[`better eslint`] = {
|
||||
"public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/scene/PanelMenuBehavior.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/features/dashboard-scene/scene/setDashboardPanelContext.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
@@ -4193,9 +4158,6 @@ exports[`better eslint`] = {
|
||||
"public/app/features/expressions/guards.ts:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/features/folders/state/actions.test.ts:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/features/geo/editor/GazetteerPathEditor.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
@@ -7008,77 +6970,24 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/components/CanvasContextMenu.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/components/CanvasTooltip.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/components/SetBackground.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/components/connections/ConnectionAnchors.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "3"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/components/connections/ConnectionSVG.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/connectionEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/element/APIEditor.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/element/ConstraintSelectionBox.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "3"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "4"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "5"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "6"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "7"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/element/PlacementEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/element/QuickPositioning.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/element/elementEditor.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/inline/InlineEdit.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "3"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "4"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "5"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "6"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/inline/InlineEditBody.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
|
||||
[0, 0, 0, "Do not use any type assertions.", "2"],
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "4"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "3"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/layer/TreeNavigationEditor.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/layer/TreeNodeTitle.tsx:5381": [
|
||||
[0, 0, 0, "Styles should be written using objects.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "2"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "3"]
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"]
|
||||
],
|
||||
"public/app/plugins/panel/canvas/editor/layer/layerEditor.tsx:5381": [
|
||||
[0, 0, 0, "Do not use any type assertions.", "0"],
|
||||
|
||||
+158
-144
File diff suppressed because it is too large
Load Diff
@@ -635,7 +635,6 @@ embed.go @grafana/grafana-as-code
|
||||
/.github/workflows/issue-opened.yml @grafana/grafana-community-support
|
||||
/.github/workflows/metrics-collector.yml @torkelo
|
||||
/.github/workflows/milestone.yml @marefr
|
||||
/.github/workflows/ox-code-coverage.yml @grafana/explore-squad
|
||||
/.github/workflows/pr-checks.yml @marefr
|
||||
/.github/workflows/pr-codeql-analysis-go.yml @DanCech
|
||||
/.github/workflows/pr-codeql-analysis-javascript.yml @DanCech
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
"react-router-dom", // we should bump this together with history (see https://github.com/grafana/grafana/issues/76744)
|
||||
"monaco-editor", // due to us exposing this via @grafana/ui/CodeEditor's props bumping can break plugins
|
||||
"react-hook-form", // due to us exposing these hooks via @grafana/ui form components bumping can break plugins
|
||||
"react-redux", // react-beautiful-dnd depends on react-redux 7.x, we need to update that one first
|
||||
],
|
||||
"includePaths": ["package.json", "packages/**", "public/app/plugins/**"],
|
||||
"ignorePaths": ["emails/**", "plugins-bundled/**", "**/mocks/**", "packages/grafana-e2e/**"],
|
||||
@@ -74,7 +73,7 @@
|
||||
"groupName": "locker",
|
||||
"matchPackagePrefixes": [
|
||||
"@locker/"
|
||||
]
|
||||
],
|
||||
"reviewers": ["team:grafana/plugins-platform-frontend"],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
- name: Set go version
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.10'
|
||||
go-version: '1.21.3'
|
||||
- name: Build swagger
|
||||
run: |
|
||||
make -C pkg/services/ngalert/api/tooling post.json api.json
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
name: Set go version
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.10'
|
||||
go-version: '1.21.3'
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -17,6 +17,11 @@ env:
|
||||
GCP_BUCKET: integration-artifacts # Dev: plugins-community-staging
|
||||
GCOM_API: https://grafana.com # Dev: https://grafana-dev.com
|
||||
|
||||
# These permissions are needed to assume roles from Github's OIDC.
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
name: Build and publish ${{ inputs.plugin_id }}
|
||||
@@ -31,10 +36,18 @@ jobs:
|
||||
- name: Verify inputs
|
||||
run: |
|
||||
if [ -z ${{ inputs.plugin_id }} ]; then echo "Missing plugin ID"; exit 1; fi
|
||||
- id: get-secrets
|
||||
uses: grafana/shared-workflows/actions/get-vault-secrets@main
|
||||
with:
|
||||
# Secrets placed in the ci/repo/grafana/<repo>/<path> path in Vault
|
||||
repo_secrets: |
|
||||
PLUGINS_GOOGLE_CREDENTIALS=core-plugins-build-and-release:PLUGINS_GOOGLE_CREDENTIALS
|
||||
PLUGINS_GRAFANA_API_KEY=core-plugins-build-and-release:PLUGINS_GRAFANA_API_KEY
|
||||
PLUGINS_GCOM_TOKEN=core-plugins-build-and-release:PLUGINS_GCOM_TOKEN
|
||||
- name: 'Authenticate to Google Cloud'
|
||||
uses: 'google-github-actions/auth@v1'
|
||||
with:
|
||||
credentials_json: '${{ secrets.PLUGINS_GOOGLE_CREDENTIALS }}'
|
||||
credentials_json: '${{ env.PLUGINS_GOOGLE_CREDENTIALS }}'
|
||||
- name: 'Set up Cloud SDK'
|
||||
uses: 'google-github-actions/setup-gcloud@v1'
|
||||
- name: Setup nodejs environment
|
||||
@@ -140,11 +153,11 @@ jobs:
|
||||
mkdir -p ci/jobs/package
|
||||
bin/grabpl plugin package
|
||||
env:
|
||||
GRAFANA_API_KEY: ${{ secrets.PLUGINS_GRAFANA_API_KEY }}
|
||||
GRAFANA_API_KEY: ${{ env.PLUGINS_GRAFANA_API_KEY }}
|
||||
PLUGIN_SIGNATURE_TYPE: grafana
|
||||
- name: Check existing release
|
||||
env:
|
||||
GCOM_TOKEN: ${{ secrets.PLUGINS_GCOM_TOKEN }}
|
||||
GCOM_TOKEN: ${{ env.PLUGINS_GCOM_TOKEN }}
|
||||
VERSION: ${{ steps.build_frontend.outputs.version }}
|
||||
run: |
|
||||
api_res=$(curl -X 'GET' -H "Authorization: Bearer $GCOM_TOKEN" \
|
||||
@@ -177,7 +190,7 @@ jobs:
|
||||
- name: Publish new plugin version on grafana.com
|
||||
working-directory: ${{ steps.get_dir.outputs.dir }}
|
||||
env:
|
||||
GCOM_TOKEN: ${{ secrets.PLUGINS_GCOM_TOKEN }}
|
||||
GCOM_TOKEN: ${{ env.PLUGINS_GCOM_TOKEN }}
|
||||
VERSION: ${{ steps.build_frontend.outputs.version }}
|
||||
run: |
|
||||
echo "Publish new plugin version on grafana.com:"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Only runs if anything under the packages/ directory changes.
|
||||
# Only runs if anything under the packages/ directory changes.
|
||||
# (Otherwise detect-breaking-changes-build-skip.yml takes over)
|
||||
|
||||
name: Levitate / Detect breaking changes
|
||||
|
||||
on:
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'packages/**'
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
path: './pr'
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.16.0
|
||||
node-version: 20.9.0
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 16.16.0
|
||||
node-version: 20.9.0
|
||||
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
name: Observability Experience test code coverage
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'pkg/services/queryhistory/**'
|
||||
- 'pkg/tsdb/loki/**'
|
||||
- 'pkg/tsdb/elasticsearch/**'
|
||||
- 'public/app/features/explore/**'
|
||||
- 'public/app/features/correlations/**'
|
||||
- 'public/app/plugins/datasource/loki/**'
|
||||
- 'public/app/plugins/datasource/elasticsearch/**'
|
||||
branches-ignore:
|
||||
- dependabot/**
|
||||
- backport-*
|
||||
|
||||
jobs:
|
||||
workflow-call:
|
||||
uses: grafana/code-coverage/.github/workflows/code-coverage.yml@v0.1.20
|
||||
with:
|
||||
frontend-path-regexp: public\/app\/features\/(explore|correlations)|public\/app\/plugins\/datasource\/(loki|elasticsearch)
|
||||
backend-path-regexp: pkg\/services\/(queryhistory)|pkg\/tsdb\/(loki|elasticsearch)
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- name: Set go version
|
||||
uses: actions/setup-go@v4
|
||||
with:
|
||||
go-version: '1.20.10'
|
||||
go-version: '1.21.3'
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
- name: "Setup Go"
|
||||
uses: "actions/setup-go@v4"
|
||||
with:
|
||||
go-version: '1.20.10'
|
||||
go-version: '1.21.3'
|
||||
|
||||
- name: "Verify kinds"
|
||||
run: go run .github/workflows/scripts/kinds/verify-kinds.go
|
||||
|
||||
@@ -39,7 +39,7 @@ jobs:
|
||||
- name: "Setup Go"
|
||||
uses: "actions/setup-go@v4"
|
||||
with:
|
||||
go-version: '1.20.10'
|
||||
go-version: '1.21.3'
|
||||
|
||||
- name: "Verify kinds"
|
||||
run: go run .github/workflows/scripts/kinds/verify-kinds.go
|
||||
|
||||
@@ -12,16 +12,21 @@ jobs:
|
||||
|
||||
- name: Update procedure
|
||||
run: |
|
||||
BRANCH=update-make-docs
|
||||
git checkout -b "${BRANCH}"
|
||||
curl -s -Lo docs/docs.mk https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/docs.mk
|
||||
curl -s -Lo docs/make-docs https://raw.githubusercontent.com/grafana/writers-toolkit/main/docs/make-docs
|
||||
if git diff --exit-code; then exit 0; fi
|
||||
BRANCH="$(date +%Y-%m-%d)/update-make-docs"
|
||||
git checkout -b "${BRANCH}"
|
||||
git add .
|
||||
git config --local user.email "bot@grafana.com"
|
||||
git config --local user.name "grafanabot"
|
||||
git commit -m "Update \`make docs\` procedure"
|
||||
git commit --message "Update \`make docs\` procedure"
|
||||
git push -v origin "refs/heads/${BRANCH}"
|
||||
gh pr create --fill --label no-changelog --label --no-backport --label type/docs
|
||||
gh pr create --fill \
|
||||
--label 'backport v10.0.x' \
|
||||
--label 'backport v10.1.x' \
|
||||
--label 'backport v10.2.x' \
|
||||
--label no-changelog \
|
||||
--label type/docs || true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: "Setup Go"
|
||||
uses: "actions/setup-go@v4"
|
||||
with:
|
||||
go-version: '1.20.10'
|
||||
go-version: '1.21.3'
|
||||
|
||||
- name: "Verify kinds"
|
||||
run: go run .github/workflows/scripts/kinds/verify-kinds.go
|
||||
|
||||
Binary file not shown.
Binary file not shown.
-546
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Vendored
-874
File diff suppressed because one or more lines are too long
+893
File diff suppressed because one or more lines are too long
+5
-5
@@ -1,3 +1,7 @@
|
||||
compressionLevel: mixed
|
||||
|
||||
enableGlobalCache: false
|
||||
|
||||
enableTelemetry: false
|
||||
|
||||
nodeLinker: pnp
|
||||
@@ -28,14 +32,10 @@ packageExtensions:
|
||||
react-dom: 17.0.1
|
||||
|
||||
plugins:
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-typescript.cjs
|
||||
spec: '@yarnpkg/plugin-typescript'
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-interactive-tools.cjs
|
||||
spec: '@yarnpkg/plugin-interactive-tools'
|
||||
- path: .yarn/plugins/@yarnpkg/plugin-outdated.cjs
|
||||
spec: 'https://mskelton.dev/yarn-outdated/v2'
|
||||
|
||||
yarnPath: .yarn/releases/yarn-3.6.4.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.0.0.cjs
|
||||
# Uncomment the following lines if you want to use Verdaccio local npm registry. Read more at packages/README.md
|
||||
# npmScopes:
|
||||
# grafana:
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@
|
||||
ARG BASE_IMAGE=alpine:3.18.3
|
||||
ARG JS_IMAGE=node:20-alpine3.18
|
||||
ARG JS_PLATFORM=linux/amd64
|
||||
ARG GO_IMAGE=golang:1.20.10-alpine3.18
|
||||
ARG GO_IMAGE=golang:1.21.3-alpine3.18
|
||||
|
||||
ARG GO_SRC=go-builder
|
||||
ARG JS_SRC=js-builder
|
||||
|
||||
@@ -261,7 +261,7 @@ build-docker-full-ubuntu: ## Build Docker image based on Ubuntu for development.
|
||||
--build-arg COMMIT_SHA=$$(git rev-parse HEAD) \
|
||||
--build-arg BUILD_BRANCH=$$(git rev-parse --abbrev-ref HEAD) \
|
||||
--build-arg BASE_IMAGE=ubuntu:22.04 \
|
||||
--build-arg GO_IMAGE=golang:1.20.10 \
|
||||
--build-arg GO_IMAGE=golang:1.21.3 \
|
||||
--tag grafana/grafana$(TAG_SUFFIX):dev-ubuntu \
|
||||
$(DOCKER_BUILD_ARGS)
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# Deprecation policy
|
||||
|
||||
We do our best to limit breaking changes and the deprecation of features to major releases. We always do our best **not** to introduce breaking changes in order to make upgrading Grafana as easy and reliable as possible. However, at times we have to introduce a breaking change by changing behavior or by removing a feature.
|
||||
|
||||
To minimize the negative effects of removing a feature we require a deprecation plan that includes:
|
||||
|
||||
- Determine usage levels of the feature.
|
||||
- Find alternative solutions and possible migration paths.
|
||||
- Announce deprecation of the feature.
|
||||
- Migrate users if possible
|
||||
- Give users time to adjust to the deprecation.
|
||||
- Disable the feature by default.
|
||||
- Remove the feature from the code base.
|
||||
|
||||
Depending on the size and importance of the feature this can be a design doc or an issue. We want this to be written communication for all parties so we know it's intentional and that did a reasonable attempt to avoid breaking changes unless needed. The size of the feature also means different notice times between Depreciation and disabling as well as disabling and removal. The actual duration will depend on releases of Grafana and the table below should be used as a guide.
|
||||
|
||||
Grafana employees can find more details in our internal docs.
|
||||
|
||||
## Grace period between announcement and disabling feature by default
|
||||
|
||||
| Size | Duration | Example |
|
||||
| ------ | ---------- | ---------------------------------------------------------------- |
|
||||
| Large | 1-2 years | Classic alerting, scripted dashboards, AngularJS |
|
||||
| Medium | 6 months | Supported Database for Grafana's backend |
|
||||
| Small | 1-3 months | Refresh OAuth access_token automatically using the refresh_token |
|
||||
|
||||
## Announced deprecations.
|
||||
|
||||
| Name | Annoucement Date | Disabling date | Removal Date | Description | Status |
|
||||
| ------------------------------------------------------------------------ | ---------------- | -------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| [Support for Mysql 5.7](https://github.com/grafana/grafana/issues/68446) | 2023-05-15 | October 2023 | | MySQL 5.7 is being deprecated in October 2023 and Grafana's policy is to test against the officially supported version. | Planned |
|
||||
+2
-3
@@ -10,7 +10,6 @@ include docs.mk
|
||||
.PHONY: sources/panels-visualizations/query-transform-data/transform-data/index.md
|
||||
sources/panels-visualizations/query-transform-data/transform-data/index.md: ## Generate the Transform Data page source.
|
||||
sources/panels-visualizations/query-transform-data/transform-data/index.md:
|
||||
cd $(CURDIR)/..
|
||||
npx tsc ./scripts/docs/generate-transformations.ts
|
||||
node ./scripts/docs/generate-transformations.js > $(CURDIR)/$@
|
||||
cd $(CURDIR)/.. && npx tsc ./scripts/docs/generate-transformations.ts && \
|
||||
node ./scripts/docs/generate-transformations.js > $(CURDIR)/$@ && \
|
||||
npx prettier -w $(CURDIR)/$@
|
||||
|
||||
@@ -59,13 +59,14 @@ If you have a literal `$` in your value and want to avoid interpolation, `$$` ca
|
||||
|
||||
Currently we do not provide any scripts/manifests for configuring Grafana. Rather than spending time learning and creating scripts/manifests for each tool, we think our time is better spent making Grafana easier to provision. Therefore, we heavily rely on the expertise of the community.
|
||||
|
||||
| Tool | Project |
|
||||
| --------- | -------------------------------------------------------------------------------------------------------------- |
|
||||
| Puppet | [https://forge.puppet.com/puppet/grafana](https://forge.puppet.com/puppet/grafana) |
|
||||
| Ansible | [https://github.com/grafana/grafana-ansible-collection](https://github.com/grafana/grafana-ansible-collection) |
|
||||
| Chef | [https://github.com/sous-chefs/chef-grafana](https://github.com/sous-chefs/chef-grafana) |
|
||||
| Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://github.com/salt-formulas/salt-formula-grafana) |
|
||||
| Jsonnet | [https://github.com/grafana/grafonnet-lib/](https://github.com/grafana/grafonnet-lib/) |
|
||||
| Tool | Project |
|
||||
| --------- | ------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| Puppet | [https://forge.puppet.com/puppet/grafana](https://forge.puppet.com/puppet/grafana) |
|
||||
| Ansible | [https://github.com/grafana/grafana-ansible-collection](https://github.com/grafana/grafana-ansible-collection) |
|
||||
| Chef | [https://github.com/sous-chefs/chef-grafana](https://github.com/sous-chefs/chef-grafana) |
|
||||
| Saltstack | [https://github.com/salt-formulas/salt-formula-grafana](https://github.com/salt-formulas/salt-formula-grafana) |
|
||||
| Jsonnet | [https://github.com/grafana/grafonnet-lib/](https://github.com/grafana/grafonnet-lib/) |
|
||||
| NixOS | [services.grafana.provision module](https://github.com/NixOS/nixpkgs/blob/master/nixos/modules/services/monitoring/grafana.nix) |
|
||||
|
||||
## Data sources
|
||||
|
||||
|
||||
@@ -29,13 +29,59 @@ Starting from version 9.1, there is also a [OpenAPI v3 specification](https://ed
|
||||
|
||||
Users can browser and try out both via the Swagger UI editor (served by the grafana server) by navigating to `/swagger-ui` and `/openapi3` respectively.
|
||||
|
||||
## Authenticating API requests
|
||||
|
||||
You can authenticate requests using basic auth, a service account token or a session cookie (acquired using regular login or OAuth).
|
||||
|
||||
### Basic Auth
|
||||
|
||||
If basic auth is enabled (it is enabled by default), then you can authenticate your HTTP request via
|
||||
standard basic auth. Basic auth will also authenticate LDAP users.
|
||||
|
||||
curl example:
|
||||
|
||||
```bash
|
||||
curl http://admin:admin@localhost:3000/api/org
|
||||
{"id":1,"name":"Main Org."}
|
||||
```
|
||||
|
||||
### Service Account Token
|
||||
|
||||
To create a service account token, click on **Administration** in the left-side menu, and then **Service Accounts**.
|
||||
For more information on how to use service account tokens, refer to the [Service Accounts]({{< relref "../../administration/service-accounts/" >}}) documentation.
|
||||
|
||||
You use the token in all requests in the `Authorization` header, like this:
|
||||
|
||||
**Example**:
|
||||
|
||||
```http
|
||||
GET http://your.grafana.com/api/dashboards/db/mydash HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
The `Authorization` header value should be `Bearer <your service account token>`.
|
||||
|
||||
## X-Grafana-Org-Id Header
|
||||
|
||||
**X-Grafana-Org-Id** is an optional property that specifies the organization to which the action is applied. If it is not set, the created key belongs to the current context org. Use this header in all requests except those regarding admin.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
GET /api/org/ HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-Grafana-Org-Id: 2
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
## HTTP APIs
|
||||
|
||||
- [Admin API]({{< relref "admin/" >}})
|
||||
- [Alerting API (unstable)](https://editor.swagger.io/?url=https://raw.githubusercontent.com/grafana/grafana/main/pkg/services/ngalert/api/tooling/post.json)
|
||||
- [Alerting Provisioning API]({{< relref "alerting_provisioning/" >}})
|
||||
- [Annotations API]({{< relref "annotations/" >}})
|
||||
- [Authentication API]({{< relref "auth/" >}})
|
||||
- [Correlations API]({{< relref "correlations/" >}})
|
||||
- [Dashboard API]({{< relref "dashboard/" >}})
|
||||
- [Dashboard Permissions API]({{< relref "dashboard_permissions/" >}})
|
||||
@@ -59,6 +105,7 @@ Users can browser and try out both via the Swagger UI editor (served by the graf
|
||||
|
||||
- [Alerting Notification Channels API]({{< relref "alerting_notification_channels/" >}})
|
||||
- [Alerting API]({{< relref "alerting/" >}})
|
||||
- [Authentication API]({{< relref "auth/" >}})
|
||||
|
||||
## Grafana Enterprise HTTP APIs
|
||||
|
||||
|
||||
@@ -19,72 +19,15 @@ title: 'Authentication HTTP API '
|
||||
|
||||
# Authentication API
|
||||
|
||||
The Authentication HTTP API is used to manage API keys.
|
||||
|
||||
{{% admonition type="note" %}}
|
||||
If you use Grafana v9.1 or newer, use service accounts instead of API keys. For more information, refer to [Grafana service account API reference]({{< relref "./serviceaccount/" >}}).
|
||||
{{% /admonition %}}
|
||||
|
||||
> If you are running Grafana Enterprise, for some endpoints you would need to have relevant permissions. Refer to [Role-based access control permissions]({{< relref "../../administration/roles-and-permissions/access-control/custom-role-actions-scopes/" >}}) for more information.
|
||||
|
||||
## Tokens
|
||||
|
||||
Currently, you can authenticate via an `API Token` or via a `Session cookie` (acquired using regular login or OAuth).
|
||||
|
||||
## X-Grafana-Org-Id Header
|
||||
|
||||
**X-Grafana-Org-Id** is an optional property that specifies the organization to which the action is applied. If it is not set, the created key belongs to the current context org. Use this header in all requests except those regarding admin.
|
||||
|
||||
**Example Request**:
|
||||
|
||||
```http
|
||||
POST /api/auth/keys HTTP/1.1
|
||||
Accept: application/json
|
||||
Content-Type: application/json
|
||||
X-Grafana-Org-Id: 2
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
|
||||
{
|
||||
"name": "mykey",
|
||||
"role": "Admin",
|
||||
"secondsToLive": 86400
|
||||
}
|
||||
```
|
||||
|
||||
## Basic Auth
|
||||
|
||||
If basic auth is enabled (it is enabled by default), then you can authenticate your HTTP request via
|
||||
standard basic auth. Basic auth will also authenticate LDAP users.
|
||||
|
||||
curl example:
|
||||
|
||||
```bash
|
||||
curl http://admin:admin@localhost:3000/api/org
|
||||
{"id":1,"name":"Main Org."}
|
||||
```
|
||||
|
||||
## Create API Token
|
||||
|
||||
Open the sidemenu and click the organization dropdown and select the `API Keys` option.
|
||||
|
||||
You use the token in all requests in the `Authorization` header, like this:
|
||||
|
||||
**Example**:
|
||||
|
||||
```http
|
||||
GET http://your.grafana.com/api/dashboards/db/mydash HTTP/1.1
|
||||
Accept: application/json
|
||||
Authorization: Bearer eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk
|
||||
```
|
||||
|
||||
The `Authorization` header value should be `Bearer <your api key>`.
|
||||
|
||||
The API Token can also be passed as a Basic authorization password with the special username `api_key`:
|
||||
|
||||
curl example:
|
||||
|
||||
```bash
|
||||
curl http://api_key:eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk@localhost:3000/api/org
|
||||
{"id":1,"name":"Main Org."}
|
||||
```
|
||||
|
||||
# Auth HTTP resources / actions
|
||||
|
||||
## Api Keys
|
||||
## List API keys
|
||||
|
||||
`GET /api/auth/keys`
|
||||
|
||||
|
||||
@@ -14,11 +14,11 @@ labels:
|
||||
products:
|
||||
- enterprise
|
||||
- oss
|
||||
title: 'API Tutorial: Create API tokens and dashboards for an organization'
|
||||
title: 'API Tutorial: Create Service Account tokens and dashboards for an organization'
|
||||
weight: 150
|
||||
---
|
||||
|
||||
# Create API tokens and dashboards for an organization
|
||||
# Create Service Account tokens and dashboards for an organization
|
||||
|
||||
Use the Grafana API to set up new Grafana organizations or to add dynamically generated dashboards to an existing organization.
|
||||
|
||||
@@ -27,9 +27,9 @@ Use the Grafana API to set up new Grafana organizations or to add dynamically ge
|
||||
There are two authentication methods to access the API:
|
||||
|
||||
- Basic authentication: A Grafana Admin user can access some parts of the Grafana API through basic authentication.
|
||||
- API Tokens: All organization actions are accessed through an API Token. An API Token is associated with an organization. It can be used to create dashboards and other components specific for that organization.
|
||||
- Service Account tokens: All organization actions can be accessed through a Service Account token. A Service Account token is associated with an organization. It can be used to create dashboards and other components specific for that organization.
|
||||
|
||||
## How to create a new organization and an API Token
|
||||
## How to create a new organization and a Service Account Token
|
||||
|
||||
The task is to create a new organization and then add a Token that can be used by other users. In the examples below which use basic auth, the user is `admin` and the password is `admin`.
|
||||
|
||||
@@ -53,13 +53,30 @@ The task is to create a new organization and then add a Token that can be used b
|
||||
curl -X POST http://admin:admin@localhost:3000/api/user/using/<id of new org>
|
||||
```
|
||||
|
||||
1. [Create the API token](http://docs.grafana.org/http_api/auth/#create-api-key):
|
||||
1. [Create a Service Account]({{< relref "./serviceaccount/#create-service-account" >}}):
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"name":"apikeycurl", "role": "Admin"}' http://admin:admin@localhost:3000/api/auth/keys
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"name":"test", "role": "Admin"}' http://admin:admin@localhost:3000/api/serviceaccounts
|
||||
```
|
||||
|
||||
This should return a response: `{"name":"apikeycurl","key":"eyJrIjoiR0ZXZmt1UFc0OEpIOGN5RWdUalBJTllUTk83VlhtVGwiLCJuIjoiYXBpa2V5Y3VybCIsImlkIjo2fQ=="}`.
|
||||
1. [Create a Service Account token]({{< relref "./serviceaccount/#create-service-account-tokens" >}}) for the service account created in the previous step:
|
||||
|
||||
```bash
|
||||
curl -X POST -H "Content-Type: application/json" -d '{"name":"test-token"}' http://admin:admin@localhost:3000/api/serviceaccounts/<service account id>/tokens
|
||||
```
|
||||
|
||||
This should return a response:
|
||||
|
||||
```http
|
||||
HTTP/1.1 200
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"id": 7,
|
||||
"name": "test-token",
|
||||
"key": "eyJrIjoiVjFxTHZ6dGdPSjg5Um92MjN1RlhjMkNqYkZUbm9jYkwiLCJuIjoiZ3JhZmFuYSIsImlkIjoxfQ=="
|
||||
}
|
||||
```
|
||||
|
||||
Save the key returned here in your password manager as it is not possible to fetch again it in the future.
|
||||
|
||||
|
||||
+1
@@ -28,5 +28,6 @@ title: GrafanaPyroscopeDataQuery kind
|
||||
| `hide` | boolean | No | | true if query is disabled (ie should not be returned to the dashboard)<br/>Note this does not always imply that the query should not be executed since<br/>the results from a hidden query may be used as the input to other queries (SSE etc) |
|
||||
| `maxNodes` | integer | No | | Sets the maximum number of nodes in the flamegraph. |
|
||||
| `queryType` | string | No | | Specify the query flavor<br/>TODO make this required and give it a default |
|
||||
| `spanSelector` | string[] | No | | Specifies the query span selectors. |
|
||||
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ The following table contains a list of calculations you can perform in Grafana.
|
||||
| Difference percent | Percentage change between first and last value of a field |
|
||||
| Distinct count | Number of unique values in a field |
|
||||
| First | First value in a field |
|
||||
| First\* (not null) | First, not null value in a field |
|
||||
| First\* (not null) | First, not null value in a field (also excludes NaNs) |
|
||||
| Last | Last value in a field |
|
||||
| Last\* (not null) | Last, not null value in a field |
|
||||
| Last\* (not null) | Last, not null value in a field (also excludes NaNs) |
|
||||
| Max | Maximum value of a field |
|
||||
| Mean | Mean value of all values in a field |
|
||||
| Variance | Variance of all values in a field |
|
||||
|
||||
@@ -132,6 +132,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `grafanaAPIServer` | Enable Kubernetes API Server for Grafana resources |
|
||||
| `grafanaAPIServerWithExperimentalAPIs` | Register experimental APIs with the k8s API server |
|
||||
| `featureToggleAdminPage` | Enable admin page for managing feature toggles from the Grafana front-end |
|
||||
| `traceToProfiles` | Enables linking between traces and profiles |
|
||||
| `permissionsFilterRemoveSubquery` | Alternative permission filter implementation that does not use subqueries for fetching the dashboard folder |
|
||||
| `influxdbSqlSupport` | Enable InfluxDB SQL query language support with new querying UI |
|
||||
| `angularDeprecationUI` | Display new Angular deprecation-related UI features |
|
||||
@@ -147,6 +148,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `formatString` | Enable format string transformer |
|
||||
| `transformationsVariableSupport` | Allows using variables in transformations |
|
||||
| `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists |
|
||||
| `kubernetesPlaylistsAPI` | Route /api/playlist API to k8s handlers |
|
||||
| `navAdminSubsections` | Splits the administration section of the nav tree into subsections |
|
||||
| `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression |
|
||||
| `teamHttpHeaders` | Enables datasources to apply team headers to the client requests |
|
||||
@@ -159,6 +161,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `alertmanagerRemoteSecondary` | Enable Grafana to sync configuration and state with a remote Alertmanager. |
|
||||
| `alertmanagerRemotePrimary` | Enable Grafana to have a remote Alertmanager instance as the primary Alertmanager. |
|
||||
| `alertmanagerRemoteOnly` | Disable the internal Alertmanager and only use the external one defined. |
|
||||
| `annotationPermissionUpdate` | Separate annotation permissions from dashboard permissions to allow for more granular control. |
|
||||
|
||||
## Development feature toggles
|
||||
|
||||
|
||||
+2
-1
@@ -3,7 +3,8 @@ import { e2e } from '../utils';
|
||||
const DASHBOARD_ID = 'c01bf42b-b783-4447-a304-8554cee1843b';
|
||||
const DATAGRID_SELECT_SERIES = 'Datagrid Select series';
|
||||
|
||||
describe('Datagrid data changes', () => {
|
||||
//TODO enable this test when panel goes live
|
||||
describe.skip('Datagrid data changes', () => {
|
||||
beforeEach(() => {
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
+2
-1
@@ -3,7 +3,8 @@ import { e2e } from '../utils';
|
||||
const DASHBOARD_ID = 'c01bf42b-b783-4447-a304-8554cee1843b';
|
||||
const DATAGRID_CANVAS = 'data-grid-canvas';
|
||||
|
||||
describe('Datagrid data changes', () => {
|
||||
//TODO enable this test when panel goes live
|
||||
describe.skip('Datagrid data changes', () => {
|
||||
beforeEach(() => {
|
||||
e2e.flows.login(Cypress.env('USERNAME'), Cypress.env('PASSWORD'));
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/grafana/grafana
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
// Override docker/docker to avoid:
|
||||
// go: github.com/drone-runners/drone-runner-docker@v1.8.2 requires
|
||||
@@ -61,7 +61,7 @@ require (
|
||||
github.com/google/uuid v1.3.1 // @grafana/backend-platform
|
||||
github.com/google/wire v0.5.0 // @grafana/backend-platform
|
||||
github.com/gorilla/websocket v1.5.0 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/alerting v0.0.0-20231026192550-079966731bbe // @grafana/alerting-squad-backend
|
||||
github.com/grafana/alerting v0.0.0-20231101090315-bf12694896a8 // @grafana/alerting-squad-backend
|
||||
github.com/grafana/cuetsy v0.1.10 // @grafana/grafana-as-code
|
||||
github.com/grafana/grafana-aws-sdk v0.19.1 // @grafana/aws-datasources
|
||||
github.com/grafana/grafana-azure-sdk-go v1.9.0 // @grafana/backend-platform
|
||||
@@ -88,8 +88,8 @@ require (
|
||||
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/alertmanager v0.25.0 // @grafana/alerting-squad-backend
|
||||
github.com/prometheus/client_golang v1.16.0 // @grafana/alerting-squad-backend
|
||||
github.com/prometheus/client_model v0.4.0 // @grafana/backend-platform
|
||||
github.com/prometheus/client_golang v1.17.0 // @grafana/alerting-squad-backend
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // @grafana/backend-platform
|
||||
github.com/prometheus/common v0.44.0 // @grafana/alerting-squad-backend
|
||||
github.com/prometheus/prometheus v1.8.2-0.20221021121301-51a44e6657c3 // @grafana/alerting-squad-backend
|
||||
github.com/robfig/cron/v3 v3.0.1 // @grafana/backend-platform
|
||||
@@ -113,7 +113,7 @@ require (
|
||||
golang.org/x/oauth2 v0.13.0 // @grafana/grafana-authnz-team
|
||||
golang.org/x/sync v0.4.0 // @grafana/alerting-squad-backend
|
||||
golang.org/x/time v0.3.0 // @grafana/backend-platform
|
||||
golang.org/x/tools v0.12.0 // @grafana/grafana-as-code
|
||||
golang.org/x/tools v0.13.0 // @grafana/grafana-as-code
|
||||
gonum.org/v1/gonum v0.12.0 // @grafana/observability-metrics
|
||||
google.golang.org/api v0.148.0 // @grafana/backend-platform
|
||||
google.golang.org/grpc v1.58.3 // @grafana/plugins-platform-backend
|
||||
@@ -164,7 +164,7 @@ require (
|
||||
github.com/go-openapi/validate v0.22.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 // @grafana/backend-platform
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
|
||||
github.com/golang/glog v1.1.0 // indirect
|
||||
github.com/golang/glog v1.1.1 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/golang/protobuf v1.5.3 // @grafana/backend-platform
|
||||
github.com/google/btree v1.1.2 // indirect
|
||||
@@ -199,9 +199,9 @@ require (
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/common/sigv4 v0.1.0 // indirect
|
||||
github.com/prometheus/exporter-toolkit v0.10.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.0 // indirect
|
||||
github.com/prometheus/procfs v0.11.1 // indirect
|
||||
github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b // indirect
|
||||
github.com/rs/cors v1.9.0 // indirect
|
||||
github.com/rs/cors v1.10.1 // indirect
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect
|
||||
github.com/segmentio/encoding v0.3.6 // indirect
|
||||
github.com/sergi/go-diff v1.3.1 // indirect
|
||||
@@ -286,7 +286,7 @@ require (
|
||||
|
||||
require github.com/grafana/gofpdf v0.0.0-20231002120153-857cc45be447 // @grafana/sharing-squad
|
||||
|
||||
require github.com/grafana/pyroscope/api v0.2.0 // @grafana/observability-traces-and-profiling
|
||||
require github.com/grafana/pyroscope/api v0.2.1 // @grafana/observability-traces-and-profiling
|
||||
|
||||
require github.com/apache/arrow/go/v13 v13.0.0 // @grafana/observability-metrics
|
||||
|
||||
@@ -316,7 +316,7 @@ require (
|
||||
github.com/cristalhq/jwt/v4 v4.0.2 // indirect
|
||||
github.com/dave/jennifer v1.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.0 // indirect
|
||||
github.com/dgraph-io/ristretto v0.1.1 // indirect
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||
github.com/docker/distribution v2.8.1+incompatible // indirect
|
||||
github.com/docker/go-connections v0.4.0 // indirect
|
||||
@@ -425,7 +425,7 @@ require (
|
||||
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.1.2 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // @grafana-app-platform-squad
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect; @grafana-app-platform-squad
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -500,6 +500,6 @@ replace xorm.io/xorm => github.com/grafana/xorm v0.8.3-0.20230627081928-d04aa38a
|
||||
|
||||
// Use our fork of the upstream alertmanagers.
|
||||
// This is required in order to get notification delivery errors from the receivers API.
|
||||
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20230918083811-3513be6760b7
|
||||
replace github.com/prometheus/alertmanager => github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758
|
||||
|
||||
exclude github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
|
||||
@@ -837,7 +837,9 @@ github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
|
||||
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
|
||||
github.com/bsm/ginkgo/v2 v2.5.0 h1:aOAnND1T40wEdAtkGSkvSICWeQ8L3UASX7YVCqQx+eQ=
|
||||
github.com/bsm/ginkgo/v2 v2.5.0/go.mod h1:AiKlXPm7ItEHNc/2+OkrNG4E0ITzojb9/xWzvQ9XZ9w=
|
||||
github.com/bsm/gomega v1.20.0 h1:JhAwLmtRzXFTx2AkALSLa8ijZafntmhSoU63Ok18Uq8=
|
||||
github.com/bsm/gomega v1.20.0/go.mod h1:JifAceMQ4crZIWYUKrlGcmbN3bqHogVTADMD2ATsbwk=
|
||||
github.com/bufbuild/connect-go v1.10.0 h1:QAJ3G9A1OYQW2Jbk3DeoJbkCxuKArrvZgDt47mjdTbg=
|
||||
github.com/bufbuild/connect-go v1.10.0/go.mod h1:CAIePUgkDR5pAFaylSMtNK45ANQjp9JvpluG20rhpV8=
|
||||
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
|
||||
@@ -902,6 +904,7 @@ github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWH
|
||||
github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20230112175826-46e39c7b9b43/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4 h1:/inchEIKaYC1Akx+H+gqO04wryn5h75LSazbRlnya1k=
|
||||
github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
|
||||
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
|
||||
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
|
||||
@@ -992,6 +995,8 @@ github.com/dgraph-io/ristretto v0.0.1/go.mod h1:T40EBc7CJke8TkpiYfGGKAeFjSaxuFXh
|
||||
github.com/dgraph-io/ristretto v0.0.2/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
|
||||
github.com/dgraph-io/ristretto v0.1.0 h1:Jv3CGQHp9OjuMBSne1485aDpUkTKEcUqF+jm/LuerPI=
|
||||
github.com/dgraph-io/ristretto v0.1.0/go.mod h1:fux0lOrBhrVCJd3lcTHsIJhq1T2rokOu6v9Vcb3Q9ug=
|
||||
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
|
||||
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
|
||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WAFKLNi6ZS0675eEUC9y3AlwSbQu1Y=
|
||||
@@ -1059,6 +1064,7 @@ github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027 h1:1L0aalTpPz7YlMx
|
||||
github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac h1:9yrT5tmn9Zc0ytWPASlaPwQfQMQYnRf0RSDe1XvHw0Q=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20220115173737-adb46da277ac/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/emicklei/go-restful/v3 v3.8.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/emicklei/go-restful/v3 v3.10.1 h1:rc42Y5YTp7Am7CS630D7JmhRjq4UlEUuEKfrDac4bSQ=
|
||||
@@ -1080,10 +1086,12 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
|
||||
github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34=
|
||||
github.com/envoyproxy/go-control-plane v0.11.0/go.mod h1:VnHyVMpzcLvCFt9yUz1UnCwHLhwx1WguiVDV7pTG/tI=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1 h1:wSUXTlLfiAQRWs2F+p+EKOY9rUyis1MyGqJ2DIk5HpM=
|
||||
github.com/envoyproxy/go-control-plane v0.11.1/go.mod h1:uhMcXKCQMEJHiAb0w+YGefQLaTEw+YhGluxZkrTmD0g=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.2/go.mod h1:GpiZQP3dDbg4JouG/NNS7QWXpgx6x8QiMKdmN72jogE=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
|
||||
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||
@@ -1191,6 +1199,7 @@ github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
|
||||
github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
|
||||
github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8=
|
||||
github.com/go-openapi/analysis v0.0.0-20180825180245-b006789cd277/go.mod h1:k70tL6pCuVxPJOHXQ+wIac1FUrvNkHolPie/cLEU6hI=
|
||||
github.com/go-openapi/analysis v0.17.0/go.mod h1:IowGgpVeD0vNm45So8nr+IcQ3pxVtpRoBWb8PVZO0ik=
|
||||
@@ -1312,7 +1321,9 @@ github.com/go-stack/stack v1.8.1 h1:ntEHSVwIt7PNXNpgPmVfMrNhLtgjlmnZha2kOpuRiDw=
|
||||
github.com/go-stack/stack v1.8.1/go.mod h1:dcoOX6HbPZSZptuspn9bctJ+N/CnF5gGygcUP3XYfe4=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM=
|
||||
github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
|
||||
github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
|
||||
github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
|
||||
@@ -1620,6 +1631,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
|
||||
github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4=
|
||||
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
|
||||
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
|
||||
github.com/golang/glog v1.1.1 h1:jxpi2eWoU84wbX9iIEyAeeoac3FLuifZpY9tcNUD9kw=
|
||||
github.com/golang/glog v1.1.1/go.mod h1:zR+okUeTbrL6EL3xHUDxZuEtGv04p5shwip1+mL/rLQ=
|
||||
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
@@ -1664,6 +1677,7 @@ github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEW
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219 h1:utua3L2IbQJmauC5IXdEA547bcoU5dozgQAfc8Onsg4=
|
||||
github.com/golangci/lint-1 v0.0.0-20181222135242-d2cdd8c08219/go.mod h1:/X8TswGSh1pIozq4ZwCfxS0WA5JGXguxk94ar/4c87Y=
|
||||
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
@@ -1799,10 +1813,10 @@ github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWm
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gotestyourself/gotestyourself v1.3.0/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||
github.com/gotestyourself/gotestyourself v2.2.0+incompatible/go.mod h1:zZKM6oeNM8k+FRljX1mnzVYeS8wiGgQyvST1/GafPbY=
|
||||
github.com/grafana/alerting v0.0.0-20231017091417-a53b5db2235d h1:fxHDUyKFc1mfyJAtW+Qxi66dMahYu+o5/lH+f8SoMHg=
|
||||
github.com/grafana/alerting v0.0.0-20231017091417-a53b5db2235d/go.mod h1:6BES5CyEqz7fDAG3MYvJLe0hqGwvIoGDN8A1aNrLGus=
|
||||
github.com/grafana/alerting v0.0.0-20231026192550-079966731bbe h1:6jY5mWR//GbYOjvqpnoncgkdxbeYImkTsy9rPvVFOlk=
|
||||
github.com/grafana/alerting v0.0.0-20231026192550-079966731bbe/go.mod h1:6BES5CyEqz7fDAG3MYvJLe0hqGwvIoGDN8A1aNrLGus=
|
||||
github.com/grafana/alerting v0.0.0-20231101090315-bf12694896a8 h1:uoaAgS743libLwhuQUQEDO2YpFaQI5viyY4NrcyibUg=
|
||||
github.com/grafana/alerting v0.0.0-20231101090315-bf12694896a8/go.mod h1:lR9bhQrESIeOqKtC4Y+fK4mqtLmJFDffFt9q4cWRa8k=
|
||||
github.com/grafana/codejen v0.0.3 h1:tAWxoTUuhgmEqxJPOLtJoxlPBbMULFwKFOcRsPRPXDw=
|
||||
github.com/grafana/codejen v0.0.3/go.mod h1:zmwwM/DRyQB7pfuBjTWII3CWtxcXh8LTwAYGfDfpR6s=
|
||||
github.com/grafana/cue v0.0.0-20230926092038-971951014e3f h1:TmYAMnqg3d5KYEAaT6PtTguL2GjLfvr6wnAX8Azw6tQ=
|
||||
@@ -1829,10 +1843,12 @@ github.com/grafana/grafana-plugin-sdk-go v0.187.0 h1:lOwoFbbTs27KqR3F32GvOX9Et3E
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.187.0/go.mod h1:PHK8eQOz3ES28RmImdTHNOTxBZaH6mb/ytJGxk7VVJc=
|
||||
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482 h1:1YNoeIhii4UIIQpCPU+EXidnqf449d0C3ZntAEt4KSo=
|
||||
github.com/grafana/kindsys v0.0.0-20230508162304-452481b63482/go.mod h1:GNcfpy5+SY6RVbNGQW264gC0r336Dm+0zgQ5vt6+M8Y=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20230918083811-3513be6760b7 h1:7gsywzIb39SYZEp9qOnNaxD4d9OOkAfJGvnRUQUtlTM=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20230918083811-3513be6760b7/go.mod h1:0SOsbwZb277OdNdUR+376IxcAYV3Pp+igiAOJdr/98M=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758 h1:ATUhvJSJwzdzhnmzUI92fxVFqyqmcnzJ47wtHTK3LW4=
|
||||
github.com/grafana/prometheus-alertmanager v0.25.1-0.20231027171310-70c52bf65758/go.mod h1:MmLemcsGjpbOwEeT3k7K+gnvIImXgkatCfVX6sOtx80=
|
||||
github.com/grafana/pyroscope/api v0.2.0 h1:TzOxL0s6SiaLEy944ZAKgHcx/JDRJXu4O8ObwkqR6p4=
|
||||
github.com/grafana/pyroscope/api v0.2.0/go.mod h1:nhH+xai9cYFgs6lMy/+L0pKj0d5yCMwji/QAiQFCP+U=
|
||||
github.com/grafana/pyroscope/api v0.2.1 h1:V/GSrwSN5HgA4Ijf/2SN9Sib55E/xObswaCMkdOOsxs=
|
||||
github.com/grafana/pyroscope/api v0.2.1/go.mod h1:vNO/Rym3pwNIN4y/f0ACrk5iR7DdWlsdfZGSZE+XChU=
|
||||
github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
|
||||
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA=
|
||||
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
|
||||
@@ -2342,6 +2358,7 @@ github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0Gq
|
||||
github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2/go.mod h1:TjQg8pa4iejrUrjiz0MCtMV38jdMNW4doKSiBrEvCQQ=
|
||||
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA=
|
||||
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
@@ -2399,6 +2416,7 @@ github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
|
||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||
github.com/oleiade/reflections v1.0.0/go.mod h1:RbATFBbKYkVdqmSFtx13Bb/tVhR0lgOBXunWTZKeL4w=
|
||||
github.com/oleiade/reflections v1.0.1 h1:D1XO3LVEYroYskEsoSiGItp9RUxG6jWnCVvrqH0HHQM=
|
||||
github.com/oleiade/reflections v1.0.1/go.mod h1:rdFxbxq4QXVZWj0F+e9jqjDkc7dbp97vkRixKo2JR60=
|
||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
@@ -2412,12 +2430,14 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
||||
github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E=
|
||||
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
|
||||
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
|
||||
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c=
|
||||
github.com/onsi/ginkgo/v2 v2.1.4/go.mod h1:um6tUpWM/cxCK3/FK8BXqEiUMUwRgSM4JXG47RKZmLU=
|
||||
github.com/onsi/ginkgo/v2 v2.1.6/go.mod h1:MEH45j8TBi6u9BMogfbp0stKC5cdGjumZj5Y7AG4VIk=
|
||||
github.com/onsi/ginkgo/v2 v2.3.0/go.mod h1:Eew0uilEqZmIEZr8JrvYlvOM7Rr6xzTmMV8AyFNU9d0=
|
||||
github.com/onsi/ginkgo/v2 v2.4.0/go.mod h1:iHkDK1fKGcBoEHT5W7YBq4RFWaQulw+caOMkAt4OrFo=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU=
|
||||
github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM=
|
||||
github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
@@ -2434,6 +2454,7 @@ github.com/onsi/gomega v1.21.1/go.mod h1:iYAIXgPSaDHak0LCMA+AWBpIKBr8WZicMxnE8lu
|
||||
github.com/onsi/gomega v1.22.1/go.mod h1:x6n7VNe4hw0vkyYUM4mjIXx3JbLiPaBPNgB7PRQ1tuM=
|
||||
github.com/onsi/gomega v1.23.0/go.mod h1:Z/NWtiqwBrwUt4/2loMmHL63EDLnYHmVbuBpDr2vQAg=
|
||||
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
|
||||
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -2558,8 +2579,8 @@ github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrb
|
||||
github.com/prometheus/client_golang v1.13.0/go.mod h1:vTeo+zgvILHsnnj/39Ou/1fPN5nJFOEMgftOUOmlvYQ=
|
||||
github.com/prometheus/client_golang v1.14.0/go.mod h1:8vpkKitgIVNcqrRBWh1C4TIUQgYNtG/XQE4E/Zae36Y=
|
||||
github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
|
||||
github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8=
|
||||
github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc=
|
||||
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
|
||||
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
@@ -2567,8 +2588,9 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
||||
github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
|
||||
github.com/prometheus/client_model v0.4.0 h1:5lQXD3cAg1OXBf4Wq03gTrXHeaV0TQvGfUooCfx1yqY=
|
||||
github.com/prometheus/client_model v0.4.0/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 h1:v7DLqVdK4VrYkVD5diGdl4sxJurKJEMnODWRJlxV9oM=
|
||||
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16/go.mod h1:oMQmHW1/JoDwqLtg57MGgP/Fb1CJEYF2imWWhWtMkYU=
|
||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
@@ -2608,9 +2630,8 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1
|
||||
github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
|
||||
github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
|
||||
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/prometheus/procfs v0.11.0 h1:5EAgkfkMl659uZPbe9AS2N68a7Cc1TJbPEuGzFuRbyk=
|
||||
github.com/prometheus/procfs v0.11.0/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
|
||||
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
|
||||
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
|
||||
github.com/prometheus/prometheus v0.43.0 h1:18iCSfrbAHbXvYFvR38U1Pt4uZmU9SmDcCpCrBKUiGg=
|
||||
github.com/prometheus/prometheus v0.43.0/go.mod h1:2BA14LgBeqlPuzObSEbh+Y+JwLH2GcqDlJKbF2sA6FM=
|
||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||
@@ -2649,8 +2670,8 @@ github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncj
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
|
||||
github.com/rs/cors v1.9.0 h1:l9HGsTsHJcvW14Nk7J9KFz8bzeAWXn3CG6bgt7LsrAE=
|
||||
github.com/rs/cors v1.9.0/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/cors v1.10.1 h1:L0uuZVXIKlI1SShY2nhFfo44TYvDPQ1w4oFkUJNfhyo=
|
||||
github.com/rs/cors v1.10.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
|
||||
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||
@@ -2824,12 +2845,14 @@ github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhV
|
||||
github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
github.com/tidwall/sjson v1.0.4/go.mod h1:bURseu1nuBkFpIES5cz6zBtjmYeOQmEESshn7VpF15Y=
|
||||
github.com/tidwall/sjson v1.1.5/go.mod h1:VuJzsZnTowhSxWdOgsAnb886i4AjEyTkk7tNtsL7EYE=
|
||||
github.com/tinylib/msgp v1.1.2/go.mod h1:+d+yLhGm8mzTaHzB+wgMYrodPfmZrzkirds8fDWklFE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk=
|
||||
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
|
||||
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f h1:A+MmlgpvrHLeUP8dkBVn4Pnf5Bp5Yk2OALm7SEJLLE8=
|
||||
github.com/ua-parser/uap-go v0.0.0-20211112212520-00c877edfe0f/go.mod h1:OBcG9bn7sHtXgarhUEb3OfCnNsgtGnkVf41ilSZ3K3E=
|
||||
@@ -2849,6 +2872,7 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
|
||||
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
|
||||
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
|
||||
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
|
||||
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
|
||||
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8 h1:aVGB3YnaS/JNfOW3tiHIlmNmTDg618va+eT0mVomgyI=
|
||||
github.com/unknwon/bra v0.0.0-20200517080246-1e3013ecaff8/go.mod h1:fVle4kNr08ydeohzYafr20oZzbAkhQT39gKK/pFQ5M4=
|
||||
github.com/unknwon/com v1.0.1 h1:3d1LTxD+Lnf3soQiD4Cp/0BRB+Rsa/+RTvz8GMMzIXs=
|
||||
@@ -2945,6 +2969,7 @@ go.elastic.co/fastjson v1.0.0/go.mod h1:PmeUOMMtLHQr9ZS9J9owrAVg0FkaZDRZJEFTTGHt
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU=
|
||||
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738 h1:VcrIfasaLFkyjk6KNlXQSzO+B0fZcnECiDrKJsfxka0=
|
||||
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
|
||||
go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
|
||||
@@ -2958,13 +2983,17 @@ go.etcd.io/etcd/client/pkg/v3 v3.5.9/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4
|
||||
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
|
||||
go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU=
|
||||
go.etcd.io/etcd/client/v2 v2.305.7 h1:AELPkjNR3/igjbO7CjyF1fPuVPjrblliiKj+Y6xSGOU=
|
||||
go.etcd.io/etcd/client/v2 v2.305.7/go.mod h1:GQGT5Z3TBuAQGvgPfhR7VPySu/SudxmEkRq9BgzFU6s=
|
||||
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
||||
go.etcd.io/etcd/client/v3 v3.5.9 h1:r5xghnU7CwbUxD/fbUtRyJGaYNfDun8sp/gTr1hew6E=
|
||||
go.etcd.io/etcd/client/v3 v3.5.9/go.mod h1:i/Eo5LrZ5IKqpbtpPDuaUnDOUv471oDg8cjQaUr2MbA=
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.7 h1:obOzeVwerFwZ9trMWapU/VjDcYUJb5OfgC1zqEGWO/0=
|
||||
go.etcd.io/etcd/pkg/v3 v3.5.7/go.mod h1:kcOfWt3Ov9zgYdOiJ/o1Y9zFfLhQjylTgL4Lru8opRo=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.7 h1:aN79qxLmV3SvIq84aNTliYGmjwsW6NqJSnqmI1HLJKc=
|
||||
go.etcd.io/etcd/raft/v3 v3.5.7/go.mod h1:TflkAb/8Uy6JFBxcRaH2Fr6Slm9mCPVdI2efzxY96yU=
|
||||
go.etcd.io/etcd/server/v3 v3.5.7 h1:BTBD8IJUV7YFgsczZMHhMTS67XuA4KpRquL0MFOJGRk=
|
||||
go.etcd.io/etcd/server/v3 v3.5.7/go.mod h1:gxBgT84issUVBRpZ3XkW1T55NjOb4vZZRI4wVvNhf4A=
|
||||
go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM=
|
||||
@@ -3134,7 +3163,7 @@ golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58
|
||||
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw=
|
||||
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
|
||||
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
|
||||
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
|
||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
@@ -3313,7 +3342,7 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI=
|
||||
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
|
||||
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -3370,7 +3399,6 @@ golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJ
|
||||
golang.org/x/sync v0.0.0-20220819030929-7fc1605a5dde/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
@@ -3532,6 +3560,7 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -3540,7 +3569,9 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
@@ -3555,7 +3586,7 @@ golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
|
||||
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
|
||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
@@ -3574,7 +3605,6 @@ golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
@@ -3714,8 +3744,8 @@ golang.org/x/tools v0.3.0/go.mod h1:/rWhSS2+zyEVwoJf8YAX6L2f0ntZ7Kn/mGgAWcipA5k=
|
||||
golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ=
|
||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||
golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s=
|
||||
golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss=
|
||||
golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM=
|
||||
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
|
||||
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
|
||||
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
@@ -4218,12 +4248,14 @@ modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
|
||||
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
|
||||
modernc.org/tcl v1.13.1/go.mod h1:XOLfOwzhkljL4itZkK6T72ckMgvj0BDsnKNdZVUOecw=
|
||||
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
|
||||
modernc.org/tcl v1.15.0/go.mod h1:xRoGotBZ6dU+Zo2tca+2EqVEeMmOUBzHnhIwq4YrVnE=
|
||||
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I=
|
||||
modernc.org/z v1.5.1/go.mod h1:eWFB510QWW5Th9YGZT81s+LwvaAs3Q2yr4sP0rmLkv8=
|
||||
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
|
||||
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=
|
||||
nhooyr.io/websocket v1.8.7/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
|
||||
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
+6
-8
@@ -82,7 +82,7 @@
|
||||
"@betterer/cli": "5.4.0",
|
||||
"@betterer/eslint": "5.4.0",
|
||||
"@betterer/regexp": "5.4.0",
|
||||
"@cypress/webpack-preprocessor": "5.17.1",
|
||||
"@cypress/webpack-preprocessor": "6.0.0",
|
||||
"@emotion/eslint-plugin": "11.11.0",
|
||||
"@grafana/eslint-config": "6.0.1",
|
||||
"@grafana/eslint-plugin": "link:./packages/grafana-eslint-rules",
|
||||
@@ -96,7 +96,7 @@
|
||||
"@swc/core": "1.3.38",
|
||||
"@swc/helpers": "0.4.14",
|
||||
"@testing-library/dom": "9.3.3",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/jest-dom": "6.1.4",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.5.1",
|
||||
"@types/angular": "1.8.5",
|
||||
@@ -124,7 +124,7 @@
|
||||
"@types/lucene": "^2",
|
||||
"@types/marked": "5.0.1",
|
||||
"@types/mousetrap": "1.6.11",
|
||||
"@types/node": "18.18.4",
|
||||
"@types/node": "20.8.10",
|
||||
"@types/node-forge": "^1",
|
||||
"@types/ol-ext": "npm:@siedlerchr/types-ol-ext@3.2.0",
|
||||
"@types/papaparse": "5.3.7",
|
||||
@@ -135,7 +135,6 @@
|
||||
"@types/react-dom": "18.2.7",
|
||||
"@types/react-grid-layout": "1.3.2",
|
||||
"@types/react-highlight-words": "0.16.4",
|
||||
"@types/react-redux": "7.1.25",
|
||||
"@types/react-router-dom": "5.3.3",
|
||||
"@types/react-table": "7.7.14",
|
||||
"@types/react-test-renderer": "18.0.0",
|
||||
@@ -228,7 +227,6 @@
|
||||
"ts-jest": "29.1.1",
|
||||
"ts-node": "10.9.1",
|
||||
"typescript": "4.8.4",
|
||||
"wait-on": "7.0.1",
|
||||
"webpack": "5.89.0",
|
||||
"webpack-bundle-analyzer": "4.9.0",
|
||||
"webpack-cli": "5.1.4",
|
||||
@@ -257,7 +255,7 @@
|
||||
"@grafana/lezer-traceql": "0.0.8",
|
||||
"@grafana/monaco-logql": "^0.0.7",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "^1.18.0",
|
||||
"@grafana/scenes": "^1.20.1",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
"@kusto/monaco-kusto": "^7.4.0",
|
||||
@@ -383,7 +381,7 @@
|
||||
"react-moveable": "0.46.1",
|
||||
"react-popper": "2.3.0",
|
||||
"react-popper-tooltip": "4.4.2",
|
||||
"react-redux": "7.2.6",
|
||||
"react-redux": "8.1.3",
|
||||
"react-resizable": "3.0.5",
|
||||
"react-responsive-carousel": "^3.2.23",
|
||||
"react-router-dom": "5.3.3",
|
||||
@@ -445,7 +443,7 @@
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
},
|
||||
"packageManager": "yarn@3.6.4",
|
||||
"packageManager": "yarn@4.0.0",
|
||||
"dependenciesMeta": {
|
||||
"prettier@3.0.0": {
|
||||
"unplugged": true
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
"@rollup/plugin-json": "6.0.0",
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@testing-library/dom": "9.3.3",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/jest-dom": "6.1.4",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.5.1",
|
||||
"@types/dompurify": "^2",
|
||||
@@ -76,7 +76,7 @@
|
||||
"@types/jquery": "3.5.16",
|
||||
"@types/lodash": "4.14.195",
|
||||
"@types/marked": "5.0.1",
|
||||
"@types/node": "18.18.4",
|
||||
"@types/node": "20.8.10",
|
||||
"@types/papaparse": "5.3.7",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/react-dom": "18.2.7",
|
||||
|
||||
@@ -54,7 +54,7 @@ describe('Stats Calculators', () => {
|
||||
it('should calculate basic stats', () => {
|
||||
const stats = reduceField({
|
||||
field: basicTable.fields[0],
|
||||
reducers: ['first', 'last', 'mean', 'count'],
|
||||
reducers: [ReducerID.first, ReducerID.last, ReducerID.mean, ReducerID.count],
|
||||
});
|
||||
|
||||
expect(stats.first).toEqual(10);
|
||||
@@ -67,7 +67,7 @@ describe('Stats Calculators', () => {
|
||||
basicTable.fields[0].state = undefined; // clear the cache
|
||||
const stats = reduceField({
|
||||
field: basicTable.fields[0],
|
||||
reducers: ['first'],
|
||||
reducers: [ReducerID.first],
|
||||
});
|
||||
|
||||
// Should do the simple version that just looks up value
|
||||
@@ -172,6 +172,58 @@ describe('Stats Calculators', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('consistent results for first/last value with NaN', () => {
|
||||
const info = [
|
||||
{
|
||||
data: [NaN, 200, NaN], // first/last value is NaN
|
||||
result: 200,
|
||||
},
|
||||
{
|
||||
data: [NaN, NaN, NaN], // All NaN
|
||||
result: null,
|
||||
},
|
||||
{
|
||||
data: [undefined, undefined, undefined], // Empty row
|
||||
result: null,
|
||||
},
|
||||
];
|
||||
|
||||
const stats = reduceField({
|
||||
field: createField('x', info[0].data),
|
||||
reducers: [ReducerID.first, ReducerID.last, ReducerID.firstNotNull, ReducerID.lastNotNull, ReducerID.diffperc],
|
||||
});
|
||||
|
||||
expect(stats[ReducerID.first]).toEqual(NaN);
|
||||
expect(stats[ReducerID.last]).toEqual(NaN);
|
||||
expect(stats[ReducerID.firstNotNull]).toEqual(200);
|
||||
expect(stats[ReducerID.lastNotNull]).toEqual(200);
|
||||
expect(stats[ReducerID.diffperc]).toEqual(0);
|
||||
|
||||
const reducers = [ReducerID.lastNotNull, ReducerID.firstNotNull];
|
||||
for (const input of info) {
|
||||
for (const reducer of reducers) {
|
||||
const v1 = reduceField({
|
||||
field: createField('x', input.data),
|
||||
reducers: [reducer, ReducerID.mean], // uses standard path
|
||||
})[reducer];
|
||||
|
||||
const v2 = reduceField({
|
||||
field: createField('x', input.data),
|
||||
reducers: [reducer], // uses optimized path
|
||||
})[reducer];
|
||||
|
||||
if (v1 !== v2 || v1 !== input.result) {
|
||||
const msg =
|
||||
`Invalid ${reducer} result for: ` +
|
||||
input.data.join(', ') +
|
||||
` Expected: ${input.result}` + // configured
|
||||
` Received: Multiple: ${v1}, Single: ${v2}`;
|
||||
expect(msg).toEqual(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it('count should ignoreNulls by default', () => {
|
||||
const someNulls = createField('x', [1, null, null, 1]);
|
||||
expect(reduce(someNulls, ReducerID.count)).toEqual(2);
|
||||
|
||||
@@ -137,7 +137,7 @@ export const fieldReducers = new Registry<FieldReducerInfo>(() => [
|
||||
{
|
||||
id: ReducerID.lastNotNull,
|
||||
name: 'Last *',
|
||||
description: 'Last non-null value',
|
||||
description: 'Last non-null value (also excludes NaNs)',
|
||||
standard: true,
|
||||
aliasIds: ['current'],
|
||||
reduce: calculateLastNotNull,
|
||||
@@ -152,7 +152,7 @@ export const fieldReducers = new Registry<FieldReducerInfo>(() => [
|
||||
{
|
||||
id: ReducerID.firstNotNull,
|
||||
name: 'First *',
|
||||
description: 'First non-null value',
|
||||
description: 'First non-null value (also excludes NaNs)',
|
||||
standard: true,
|
||||
reduce: calculateFirstNotNull,
|
||||
},
|
||||
@@ -320,8 +320,8 @@ export function doStandardCalcs(field: Field, ignoreNulls: boolean, nullAsZero:
|
||||
|
||||
calcs.count++;
|
||||
|
||||
if (currentValue != null) {
|
||||
// null || undefined
|
||||
if (currentValue != null && !Number.isNaN(currentValue)) {
|
||||
// null || undefined || NaN
|
||||
const isFirst = calcs.firstNotNull === null;
|
||||
if (isFirst) {
|
||||
calcs.firstNotNull = currentValue;
|
||||
@@ -418,7 +418,7 @@ function calculateFirstNotNull(field: Field, ignoreNulls: boolean, nullAsZero: b
|
||||
const data = field.values;
|
||||
for (let idx = 0; idx < data.length; idx++) {
|
||||
const v = data[idx];
|
||||
if (v != null && v !== undefined) {
|
||||
if (v != null && !Number.isNaN(v)) {
|
||||
return { firstNotNull: v };
|
||||
}
|
||||
}
|
||||
@@ -435,7 +435,7 @@ function calculateLastNotNull(field: Field, ignoreNulls: boolean, nullAsZero: bo
|
||||
let idx = data.length - 1;
|
||||
while (idx >= 0) {
|
||||
const v = data[idx--];
|
||||
if (v != null && v !== undefined) {
|
||||
if (v != null && !Number.isNaN(v)) {
|
||||
return { lastNotNull: v };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ export interface FeatureToggles {
|
||||
awsAsyncQueryCaching?: boolean;
|
||||
splitScopes?: boolean;
|
||||
azureMonitorDataplane?: boolean;
|
||||
traceToProfiles?: boolean;
|
||||
permissionsFilterRemoveSubquery?: boolean;
|
||||
prometheusConfigOverhaulAuth?: boolean;
|
||||
configurableSchedulerTick?: boolean;
|
||||
@@ -139,6 +140,7 @@ export interface FeatureToggles {
|
||||
formatString?: boolean;
|
||||
transformationsVariableSupport?: boolean;
|
||||
kubernetesPlaylists?: boolean;
|
||||
kubernetesPlaylistsAPI?: boolean;
|
||||
cloudWatchBatchQueries?: boolean;
|
||||
navAdminSubsections?: boolean;
|
||||
recoveryThreshold?: boolean;
|
||||
@@ -153,4 +155,5 @@ export interface FeatureToggles {
|
||||
alertmanagerRemoteSecondary?: boolean;
|
||||
alertmanagerRemotePrimary?: boolean;
|
||||
alertmanagerRemoteOnly?: boolean;
|
||||
annotationPermissionUpdate?: boolean;
|
||||
}
|
||||
|
||||
@@ -140,9 +140,9 @@ export interface DataSourceWithLogsContextSupport<TQuery extends DataQuery = Dat
|
||||
getLogRowContextQuery?: (row: LogRowModel, options?: LogRowContextOptions, query?: TQuery) => Promise<TQuery | null>;
|
||||
|
||||
/**
|
||||
* This method can be used to show "context" button based on runtime conditions (for example row model data or plugin settings, etc.)
|
||||
* @deprecated Deprecated since 10.3. To display the context option and support the feature implement DataSourceWithLogsContextSupport interface instead.
|
||||
*/
|
||||
showContextToggle(row?: LogRowModel): boolean;
|
||||
showContextToggle?(row?: LogRowModel): boolean;
|
||||
|
||||
/**
|
||||
* This method can be used to display a custom UI in the context view.
|
||||
@@ -157,7 +157,7 @@ export const hasLogsContextSupport = (datasource: unknown): datasource is DataSo
|
||||
return false;
|
||||
}
|
||||
|
||||
return 'getLogRowContext' in datasource && 'showContextToggle' in datasource;
|
||||
return 'getLogRowContext' in datasource;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -350,19 +350,25 @@ describe('to nanoseconds', () => {
|
||||
it('should correctly display as minutes', () => {
|
||||
const eightMinutes = toNanoSeconds(480000000000);
|
||||
expect(eightMinutes.text).toBe('8');
|
||||
expect(eightMinutes.suffix).toBe(' mins');
|
||||
});
|
||||
|
||||
it('should correctly display as minute', () => {
|
||||
const eightMinutes = toNanoSeconds(60000000000);
|
||||
expect(eightMinutes.text).toBe('1');
|
||||
expect(eightMinutes.suffix).toBe(' min');
|
||||
});
|
||||
|
||||
it('should correctly display as hours', () => {
|
||||
const nineHours = toNanoSeconds(32400000000000);
|
||||
expect(nineHours.text).toBe('9');
|
||||
expect(nineHours.suffix).toBe(' hour');
|
||||
expect(nineHours.suffix).toBe(' hours');
|
||||
});
|
||||
|
||||
it('should correctly display as days', () => {
|
||||
const tenDays = toNanoSeconds(864000000000000);
|
||||
expect(tenDays.text).toBe('10');
|
||||
expect(tenDays.suffix).toBe(' day');
|
||||
expect(tenDays.suffix).toBe(' days');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -25,7 +25,8 @@ describe('valueFormats', () => {
|
||||
${'ms'} | ${4} | ${0.0024} | ${'0.0024 ms'}
|
||||
${'ms'} | ${0} | ${100} | ${'100 ms'}
|
||||
${'ms'} | ${2} | ${1250} | ${'1.25 s'}
|
||||
${'ms'} | ${1} | ${10000086.123} | ${'2.8 hour'}
|
||||
${'ms'} | ${1} | ${10000086.123} | ${'2.8 hours'}
|
||||
${'ms'} | ${1} | ${-10000086.123} | ${'-2.8 hours'}
|
||||
${'ms'} | ${undefined} | ${1000} | ${'1 s'}
|
||||
${'ms'} | ${0} | ${1200} | ${'1 s'}
|
||||
${'short'} | ${undefined} | ${1000} | ${'1 K'}
|
||||
@@ -69,6 +70,7 @@ describe('valueFormats', () => {
|
||||
${'dateTimeAsUS'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'07/02/2010 12:00:00 am'}
|
||||
${'dateTimeAsSystem'} | ${0} | ${dateTime(new Date(2010, 6, 2)).valueOf()} | ${'2010-07-02 00:00:00'}
|
||||
${'dtdurationms'} | ${undefined} | ${100000} | ${'1 minute'}
|
||||
${'dtdurationms'} | ${undefined} | ${150000} | ${'2 minutes'}
|
||||
`(
|
||||
'With format=$format decimals=$decimals and value=$value then result shoudl be = $expected',
|
||||
async ({ format, value, decimals, expected }) => {
|
||||
|
||||
@@ -102,10 +102,27 @@ function getDecimalsForValue(value: number): number {
|
||||
export function toFixedScaled(value: number, decimals: DecimalCount, ext?: string): FormattedValue {
|
||||
return {
|
||||
text: toFixed(value, decimals),
|
||||
suffix: ext,
|
||||
suffix: appendPluralIf(ext, Math.abs(value) > 1),
|
||||
};
|
||||
}
|
||||
|
||||
function appendPluralIf(ext: string | undefined, condition: boolean): string | undefined {
|
||||
if (!condition) {
|
||||
return ext;
|
||||
}
|
||||
|
||||
switch (ext) {
|
||||
case ' min':
|
||||
case ' hour':
|
||||
case ' day':
|
||||
case ' week':
|
||||
case ' year':
|
||||
return `${ext}s`;
|
||||
default:
|
||||
return ext;
|
||||
}
|
||||
}
|
||||
|
||||
export function toFixedUnit(unit: string, asPrefix?: boolean): ValueFormatter {
|
||||
return (size: number, decimals?: DecimalCount) => {
|
||||
if (size === null) {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "25.0.2",
|
||||
"@rollup/plugin-node-resolve": "15.2.3",
|
||||
"@types/node": "18.18.4",
|
||||
"@types/node": "20.8.10",
|
||||
"esbuild": "0.18.12",
|
||||
"rimraf": "5.0.1",
|
||||
"rollup": "2.79.1",
|
||||
|
||||
+5
@@ -34,9 +34,14 @@ export interface GrafanaPyroscopeDataQuery extends common.DataQuery {
|
||||
* Specifies the type of profile to query.
|
||||
*/
|
||||
profileTypeId: string;
|
||||
/**
|
||||
* Specifies the query span selectors.
|
||||
*/
|
||||
spanSelector?: Array<string>;
|
||||
}
|
||||
|
||||
export const defaultGrafanaPyroscopeDataQuery: Partial<GrafanaPyroscopeDataQuery> = {
|
||||
groupBy: [],
|
||||
labelSelector: '{}',
|
||||
spanSelector: [],
|
||||
};
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"@storybook/react-webpack5": "7.4.5",
|
||||
"@storybook/theming": "7.4.5",
|
||||
"@testing-library/dom": "9.3.3",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/jest-dom": "6.1.4",
|
||||
"@testing-library/react": "14.0.0",
|
||||
"@testing-library/user-event": "14.5.1",
|
||||
"@types/common-tags": "^1.8.0",
|
||||
@@ -145,7 +145,7 @@
|
||||
"@types/jquery": "3.5.16",
|
||||
"@types/lodash": "4.14.195",
|
||||
"@types/mock-raf": "1.0.3",
|
||||
"@types/node": "18.18.4",
|
||||
"@types/node": "20.8.10",
|
||||
"@types/prismjs": "1.26.0",
|
||||
"@types/react": "18.2.15",
|
||||
"@types/react-beautiful-dnd": "13.1.4",
|
||||
|
||||
@@ -39,7 +39,7 @@ export const ContextMenu = React.memo(
|
||||
setPositionStyles({
|
||||
position: 'fixed',
|
||||
left: collisions.right ? x - rect.width - OFFSET : x - OFFSET,
|
||||
top: collisions.bottom ? y - rect.height - OFFSET : y + OFFSET,
|
||||
top: Math.max(0, collisions.bottom ? y - rect.height - OFFSET : y + OFFSET),
|
||||
});
|
||||
}
|
||||
}, [x, y]);
|
||||
|
||||
@@ -110,6 +110,7 @@ const getRadioButtonStyles = stylesFactory((theme: GrafanaTheme2, size: RadioBut
|
||||
radioLabel: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontSize,
|
||||
height: `${labelHeight}px`,
|
||||
// Deduct border from line-height for perfect vertical centering on windows and linux
|
||||
|
||||
@@ -8,13 +8,15 @@ import { TimeRange, GraphSeriesXY, TimeZone, createDimension } from '@grafana/da
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
|
||||
import { VizTooltipProps, VizTooltipContentProps, ActiveDimensions, VizTooltip } from '../VizTooltip';
|
||||
import { FlotPosition } from '../VizTooltip/VizTooltip';
|
||||
|
||||
import { GraphContextMenu, GraphContextMenuProps, ContextDimensions } from './GraphContextMenu';
|
||||
import { GraphTooltip } from './GraphTooltip/GraphTooltip';
|
||||
import { GraphDimensions } from './GraphTooltip/types';
|
||||
import { FlotPosition, FlotItem } from './types';
|
||||
import { FlotItem } from './types';
|
||||
import { graphTimeFormat, graphTickFormatter } from './utils';
|
||||
|
||||
/** @deprecated */
|
||||
export interface GraphProps {
|
||||
ariaLabel?: string;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
@@ -31,6 +33,7 @@ export interface GraphProps {
|
||||
onHorizontalRegionSelected?: (from: number, to: number) => void;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
interface GraphState {
|
||||
pos?: FlotPosition;
|
||||
contextPos?: FlotPosition;
|
||||
|
||||
@@ -21,8 +21,10 @@ import { SeriesIcon } from '../VizLegend/SeriesIcon';
|
||||
|
||||
import { GraphDimensions } from './GraphTooltip/types';
|
||||
|
||||
/** @deprecated */
|
||||
export type ContextDimensions<T extends Dimensions = any> = { [key in keyof T]: [number, number | undefined] | null };
|
||||
|
||||
/** @deprecated */
|
||||
export type GraphContextMenuProps = ContextMenuProps & {
|
||||
getContextMenuSource: () => FlotDataPoint | null;
|
||||
timeZone?: TimeZone;
|
||||
|
||||
@@ -3,22 +3,26 @@ import React, { Component } from 'react';
|
||||
|
||||
import { GraphSeriesXY } from '@grafana/data';
|
||||
|
||||
/** @deprecated */
|
||||
export interface GraphSeriesTogglerAPI {
|
||||
onSeriesToggle: (label: string, event: React.MouseEvent<HTMLElement>) => void;
|
||||
toggledSeries: GraphSeriesXY[];
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export interface GraphSeriesTogglerProps {
|
||||
children: (api: GraphSeriesTogglerAPI) => JSX.Element;
|
||||
series: GraphSeriesXY[];
|
||||
onHiddenSeriesChanged?: (hiddenSeries: string[]) => void;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export interface GraphSeriesTogglerState {
|
||||
hiddenSeries: string[];
|
||||
toggledSeries: GraphSeriesXY[];
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export class GraphSeriesToggler extends Component<GraphSeriesTogglerProps, GraphSeriesTogglerState> {
|
||||
constructor(props: GraphSeriesTogglerProps) {
|
||||
super(props);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { MultiModeGraphTooltip } from './MultiModeGraphTooltip';
|
||||
import { SingleModeGraphTooltip } from './SingleModeGraphTooltip';
|
||||
import { GraphDimensions } from './types';
|
||||
|
||||
/** @deprecated */
|
||||
export const GraphTooltip = ({
|
||||
mode = TooltipDisplayMode.Single,
|
||||
dimensions,
|
||||
|
||||
@@ -3,16 +3,18 @@ import React from 'react';
|
||||
import { getValueFromDimension } from '@grafana/data';
|
||||
|
||||
import { SeriesTable } from '../../VizTooltip';
|
||||
import { FlotPosition } from '../types';
|
||||
import { FlotPosition } from '../../VizTooltip/VizTooltip';
|
||||
import { getMultiSeriesGraphHoverInfo } from '../utils';
|
||||
|
||||
import { GraphTooltipContentProps } from './types';
|
||||
|
||||
/** @deprecated */
|
||||
type Props = GraphTooltipContentProps & {
|
||||
// We expect position to figure out correct values when not hovering over a datapoint
|
||||
pos: FlotPosition;
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
export const MultiModeGraphTooltip = ({ dimensions, activeDimensions, pos, timeZone }: Props) => {
|
||||
let activeSeriesIndex: number | null = null;
|
||||
// when no x-axis provided, skip rendering
|
||||
|
||||
@@ -11,6 +11,7 @@ import { SeriesTable } from '../../VizTooltip';
|
||||
|
||||
import { GraphTooltipContentProps } from './types';
|
||||
|
||||
/** @deprecated */
|
||||
export const SingleModeGraphTooltip = ({ dimensions, activeDimensions, timeZone }: GraphTooltipContentProps) => {
|
||||
// not hovering over a point, skip rendering
|
||||
if (
|
||||
|
||||
@@ -2,11 +2,13 @@ import { Dimension, Dimensions, TimeZone } from '@grafana/data';
|
||||
|
||||
import { ActiveDimensions } from '../../VizTooltip';
|
||||
|
||||
/** @deprecated */
|
||||
export interface GraphDimensions extends Dimensions {
|
||||
xAxis: Dimension<number>;
|
||||
yAxis: Dimension<number>;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export interface GraphTooltipContentProps {
|
||||
dimensions: GraphDimensions; // Dimension[]
|
||||
activeDimensions: ActiveDimensions<GraphDimensions>;
|
||||
|
||||
@@ -1,12 +1,4 @@
|
||||
export interface FlotPosition {
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
x: number;
|
||||
x1: number;
|
||||
y: number;
|
||||
y1: number;
|
||||
}
|
||||
|
||||
/** @deprecated */
|
||||
export interface FlotItem<T> {
|
||||
datapoint: [number, number];
|
||||
dataIndex: number;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
*
|
||||
* @param posX
|
||||
* @param series
|
||||
* @deprecated
|
||||
*/
|
||||
export const findHoverIndexFromData = (xAxisDimension: Field, xPos: number) => {
|
||||
let lower = 0;
|
||||
@@ -50,6 +51,7 @@ interface MultiSeriesHoverInfo {
|
||||
*
|
||||
* @param seriesList list of series visible on the Graph
|
||||
* @param pos mouse cursor position, based on jQuery.flot position
|
||||
* @deprecated
|
||||
*/
|
||||
export const getMultiSeriesGraphHoverInfo = (
|
||||
// x and y axis dimensions order is aligned
|
||||
@@ -102,6 +104,7 @@ export const getMultiSeriesGraphHoverInfo = (
|
||||
};
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
export const graphTickFormatter = (epoch: number, axis: any) => {
|
||||
return dateTimeFormat(epoch, {
|
||||
format: axis?.options?.timeformat,
|
||||
@@ -109,6 +112,7 @@ export const graphTickFormatter = (epoch: number, axis: any) => {
|
||||
});
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
export const graphTimeFormat = (ticks: number | null, min: number | null, max: number | null): string => {
|
||||
if (min && max && ticks) {
|
||||
const range = max - min;
|
||||
|
||||
@@ -16,7 +16,7 @@ Use it whenever you would use custom CSS.
|
||||
|
||||
#### When not to use
|
||||
|
||||
If you need layout styles, use the Stack, Flex or Grid components instead.
|
||||
If you need layout styles, use the Stack or Grid components instead.
|
||||
|
||||
### How to add a prop to Box
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
import { Text } from '../../Text/Text';
|
||||
import { Flex } from '../Flex/Flex';
|
||||
import { Stack } from '../Stack/Stack';
|
||||
|
||||
import { Box, BackgroundColor, BorderColor, BorderStyle, BorderRadius, BoxShadow } from './Box';
|
||||
import mdx from './Box.mdx';
|
||||
@@ -48,11 +48,11 @@ const Item = ({ background }: { background?: string }) => {
|
||||
|
||||
export const Basic: StoryFn<typeof Box> = (args) => {
|
||||
return (
|
||||
<Flex>
|
||||
<Stack>
|
||||
<Box borderColor="medium" {...args}>
|
||||
Box
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -88,64 +88,64 @@ Basic.args = {
|
||||
|
||||
export const Background: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<Stack gap={4}>
|
||||
{backgroundOptions.map((background) => (
|
||||
<Flex key={background} direction="column" alignItems="flex-start">
|
||||
<Stack key={background} direction="column" alignItems="flex-start">
|
||||
{background}
|
||||
<Box backgroundColor={background} borderColor="strong" borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Border: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex direction="column" gap={4}>
|
||||
<Stack direction="column" gap={4}>
|
||||
<div>
|
||||
<Text variant="h4">Border Color</Text>
|
||||
<Flex gap={4} wrap="wrap">
|
||||
<Stack gap={4} wrap="wrap">
|
||||
{borderColorOptions.map((border) => (
|
||||
<Flex key={border} direction="column" alignItems="flex-start">
|
||||
<Stack key={border} direction="column" alignItems="flex-start">
|
||||
{border}
|
||||
<Box borderColor={border} borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
<div>
|
||||
<Text variant="h4">Border Style</Text>
|
||||
<Flex gap={4} wrap="wrap">
|
||||
<Stack gap={4} wrap="wrap">
|
||||
{borderStyleOptions.map((border) => (
|
||||
<Flex key={border} direction="column" alignItems="flex-start">
|
||||
<Stack key={border} direction="column" alignItems="flex-start">
|
||||
{border}
|
||||
<Box borderColor="info" borderStyle={border}>
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
</div>
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const Shadow: StoryFn<typeof Box> = () => {
|
||||
return (
|
||||
<Flex gap={4}>
|
||||
<Stack gap={4}>
|
||||
{boxShadowOptions.map((shadow) => (
|
||||
<Flex key={shadow} direction="column" alignItems="flex-start">
|
||||
<Stack key={shadow} direction="column" alignItems="flex-start">
|
||||
{shadow}
|
||||
<Box boxShadow={shadow} borderColor="strong" borderStyle="solid">
|
||||
<Item />
|
||||
</Box>
|
||||
</Flex>
|
||||
</Stack>
|
||||
))}
|
||||
</Flex>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { ElementType, forwardRef, PropsWithChildren } from 'react';
|
||||
import { GrafanaTheme2, ThemeSpacingTokens, ThemeShape, ThemeShadows } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { AlignItems, JustifyContent } from '../Flex/Flex';
|
||||
import { AlignItems, JustifyContent } from '../Stack/Stack';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
type Display = 'flex' | 'block' | 'inline' | 'none';
|
||||
|
||||
@@ -1,202 +0,0 @@
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useTheme2 } from '../../../themes';
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
|
||||
import { Flex, JustifyContent, Wrap, Direction } from './Flex';
|
||||
import mdx from './Flex.mdx';
|
||||
|
||||
const Item = ({ color, text, height }: { color: string; text?: string | number; height?: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '5em',
|
||||
height: height,
|
||||
backgroundColor: color,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{text && <h3 style={{ color: 'black' }}>{text}</h3>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const meta: Meta<typeof Flex> = {
|
||||
title: 'General/Layout/Flex',
|
||||
component: Flex,
|
||||
parameters: {
|
||||
docs: {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export const Basic: StoryFn<typeof Flex> = ({ direction, wrap, alignItems, justifyContent, gap }) => {
|
||||
const theme = useTheme2();
|
||||
return (
|
||||
<div style={{ width: '600px', height: '600px', border: '1px solid grey' }}>
|
||||
<Flex direction={direction} wrap={wrap} alignItems={alignItems} justifyContent={justifyContent} gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Basic.argTypes = {
|
||||
gap: SpacingTokenControl,
|
||||
direction: { control: 'select', options: ['row', 'row-reverse', 'column', 'column-reverse'] },
|
||||
wrap: { control: 'select', options: ['nowrap', 'wrap', 'wrap-reverse'] },
|
||||
alignItems: {
|
||||
control: 'select',
|
||||
options: ['stretch', 'flex-start', 'flex-end', 'center', 'baseline', 'start', 'end', 'self-start', 'self-end'],
|
||||
},
|
||||
justifyContent: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'start',
|
||||
'end',
|
||||
'left',
|
||||
'right',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignItemsExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
<p>Align items flex-start</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="flex-start" justifyContent="start" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items flex-end</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="flex-end" justifyContent="end" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items baseline</p>
|
||||
<Flex direction="row" wrap="nowrap" alignItems="baseline" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items center</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
<p>Align items stretch</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="stretch" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.error.main} height="10em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} height="3em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} />
|
||||
</Flex>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const JustifyContentExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const justifyContentOptions: JustifyContent[] = [
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{justifyContentOptions.map((justifyContent) => (
|
||||
<>
|
||||
<p>Justify Content {justifyContent}</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent={justifyContent} gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const GapExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const gapOptions: ThemeSpacingTokens[] = [2, 8, 10];
|
||||
return (
|
||||
<div style={{ width: '800px' }}>
|
||||
{gapOptions.map((gap) => (
|
||||
<>
|
||||
<p>Gap with spacingToken set to {gap}</p>
|
||||
<Flex direction="row" wrap="wrap" alignItems="flex-start" justifyContent="flex-start" gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WrapExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const wrapOptions: Wrap[] = ['nowrap', 'wrap', 'wrap-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{wrapOptions.map((wrap) => (
|
||||
<>
|
||||
<p>Wrap examples with {wrap} and gap set to spacingToken 2 (16px)</p>
|
||||
<Flex direction="row" wrap={wrap} alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DirectionExamples: StoryFn<typeof Flex> = () => {
|
||||
const theme = useTheme2();
|
||||
const directionOptions: Direction[] = ['row', 'row-reverse', 'column', 'column-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{directionOptions.map((direction) => (
|
||||
<>
|
||||
<p>Direction {direction}</p>
|
||||
<Flex direction={direction} wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Flex>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default meta;
|
||||
@@ -1,138 +0,0 @@
|
||||
import { Meta, ArgTypes } from '@storybook/blocks';
|
||||
import { Flex } from './Flex';
|
||||
|
||||
<Meta title="MDX|Flex" component={Flex} />
|
||||
|
||||
# Flex
|
||||
|
||||
The Flex Component aims at providing a more efficient way to lay out, align and distribute space among items in a container and
|
||||
the decision to create it is to ensure consistency in design across Grafana.
|
||||
|
||||
### Usage
|
||||
|
||||
#### When to use
|
||||
|
||||
Use when in need to align components and small parts of the application. Use as parent container to wrap elements that you wish to align in a certain way.
|
||||
|
||||
Also:
|
||||
|
||||
* when working with one dimension layout
|
||||
* to display the direction of the elements
|
||||
* to set the elements to wrap
|
||||
* to align items (vertically or horizontally)
|
||||
|
||||
#### When not to use
|
||||
|
||||
When you need to lay out bigger parts of the application or when you want to create page lay out.
|
||||
|
||||
Also:
|
||||
|
||||
* for complex grid layouts with various rows and columns
|
||||
* bidirectional layouts
|
||||
* complex nesting
|
||||
* equal height columns
|
||||
|
||||
### Variants
|
||||
|
||||
Flex component has few variants that can be used based on the desired alignment you need for your case.
|
||||
|
||||
Some examples of how to use the Flex component can be seen below:
|
||||
|
||||
- AlignItems stretch
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items stretch and justify-content to be center</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap" alignItems="stretch" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.warning.main} height="10em" />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} height="3em" />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- Wrap items wrap-reverse
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with wrap-reverse property</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap-reverse" alignItems="center" justifyContent="center" gap={4}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- JustifyContent flex-start
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with justify-content property</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent="flex-start" gap={2}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- Gap of 16px using the ThemeSpacingTokens
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with gap of 16px</h1>
|
||||
</header>
|
||||
<Flex direction="row" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
- Direction column
|
||||
|
||||
```ts
|
||||
import { Flex } from '@grafana/ui'
|
||||
import { useTheme2 } from '../../themes';
|
||||
|
||||
const theme = useTheme2();
|
||||
|
||||
<header>
|
||||
<h1>Using Flex component to align-items with direction column</h1>
|
||||
</header>
|
||||
<Flex direction="column" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
<Item color={theme.colors.warning.main} />
|
||||
</Flex>
|
||||
```
|
||||
|
||||
### Props
|
||||
|
||||
<ArgTypes of={Flex} />
|
||||
@@ -1,89 +0,0 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
export type AlignItems =
|
||||
| 'stretch'
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'baseline'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'self-start'
|
||||
| 'self-end';
|
||||
|
||||
export type JustifyContent =
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'left'
|
||||
| 'right';
|
||||
|
||||
export type Direction = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
||||
|
||||
export type Wrap = 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||
|
||||
interface FlexProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className' | 'style'> {
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
alignItems?: ResponsiveProp<AlignItems>;
|
||||
justifyContent?: ResponsiveProp<JustifyContent>;
|
||||
direction?: ResponsiveProp<Direction>;
|
||||
wrap?: ResponsiveProp<Wrap>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Flex = React.forwardRef<HTMLDivElement, FlexProps>(
|
||||
({ gap = 1, alignItems, justifyContent, direction, wrap, children, ...rest }, ref) => {
|
||||
const styles = useStyles2(getStyles, gap, alignItems, justifyContent, direction, wrap);
|
||||
|
||||
return (
|
||||
<div ref={ref} className={styles.flex} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Flex.displayName = 'Flex';
|
||||
|
||||
const getStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
gap: FlexProps['gap'],
|
||||
alignItems: FlexProps['alignItems'],
|
||||
justifyContent: FlexProps['justifyContent'],
|
||||
direction: FlexProps['direction'],
|
||||
wrap: FlexProps['wrap']
|
||||
) => {
|
||||
return {
|
||||
flex: css([
|
||||
{
|
||||
display: 'flex',
|
||||
},
|
||||
getResponsiveStyle<Direction>(theme, direction, (val) => ({
|
||||
flexDirection: val,
|
||||
})),
|
||||
getResponsiveStyle<Wrap>(theme, wrap, (val) => ({
|
||||
flexWrap: val,
|
||||
})),
|
||||
getResponsiveStyle<AlignItems>(theme, alignItems, (val) => ({
|
||||
alignItems: val,
|
||||
})),
|
||||
getResponsiveStyle<JustifyContent>(theme, justifyContent, (val) => ({
|
||||
justifyContent: val,
|
||||
})),
|
||||
getResponsiveStyle<ThemeSpacingTokens>(theme, gap, (val) => ({
|
||||
gap: theme.spacing(val),
|
||||
})),
|
||||
]),
|
||||
};
|
||||
};
|
||||
@@ -17,11 +17,6 @@ Use the Grid component when you want to create structured and organized layouts
|
||||
|
||||
Use the `Stack` component instead for these use cases:
|
||||
|
||||
- **Simple layouts:** When you need to arrange elements in a linear format, either vertically or horizontally.
|
||||
- **Regular flow:** When you want a "regular" site flow but with standardized spacing between the elements.
|
||||
|
||||
Use the `Flex` component instead for these use cases:
|
||||
|
||||
- **Alignment:** More options for item alignment.
|
||||
- **Flex items:** Custom flex basis or configure how items stretch and wrap.
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { ResponsiveProp } from '../utils/responsiveness';
|
||||
|
||||
import { Stack } from './Stack';
|
||||
|
||||
interface HorizontalStackProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className' | 'style'> {
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
}
|
||||
|
||||
export const HorizontalStack = React.forwardRef<HTMLDivElement, React.PropsWithChildren<HorizontalStackProps>>(
|
||||
({ children, gap = 1, ...rest }, ref) => (
|
||||
<Stack ref={ref} direction="row" gap={gap} {...rest}>
|
||||
{children}
|
||||
</Stack>
|
||||
)
|
||||
);
|
||||
HorizontalStack.displayName = 'HorizontalStack';
|
||||
@@ -1,16 +1,31 @@
|
||||
import { Meta, StoryFn } from '@storybook/react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useTheme2 } from '../../../themes';
|
||||
import { SpacingTokenControl } from '../../../utils/storybook/themeStorybookControls';
|
||||
import { Alert } from '../../Alert/Alert';
|
||||
import { Button } from '../../Button';
|
||||
import { Card } from '../../Card/Card';
|
||||
import { Text } from '../../Text/Text';
|
||||
|
||||
import { HorizontalStack } from './HorizontalStack';
|
||||
import { Stack } from './Stack';
|
||||
import { Stack, JustifyContent, Wrap, Direction } from './Stack';
|
||||
import mdx from './Stack.mdx';
|
||||
|
||||
const Item = ({ color, text, height }: { color: string; text?: string | number; height?: string }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
width: '5em',
|
||||
height: height,
|
||||
backgroundColor: color,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{text && <h3 style={{ color: 'black' }}>{text}</h3>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const meta: Meta<typeof Stack> = {
|
||||
title: 'General/Layout/Stack',
|
||||
component: Stack,
|
||||
@@ -19,204 +34,169 @@ const meta: Meta<typeof Stack> = {
|
||||
page: mdx,
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
gap: SpacingTokenControl,
|
||||
direction: { control: 'select', options: ['row', 'column'] },
|
||||
},
|
||||
};
|
||||
|
||||
const Item = ({ children }: { children: ReactNode }) => (
|
||||
<div style={{ backgroundColor: 'lightgrey', width: '100px', height: '50px' }}>{children}</div>
|
||||
);
|
||||
|
||||
export const Basic: StoryFn<typeof Stack> = ({ direction = 'column', gap = 2 }) => {
|
||||
export const Basic: StoryFn<typeof Stack> = ({ direction, wrap, alignItems, justifyContent, gap }) => {
|
||||
const theme = useTheme2();
|
||||
return (
|
||||
<Stack direction={direction} gap={gap}>
|
||||
<Item>Item 1</Item>
|
||||
<Item>Item 2</Item>
|
||||
<Item>Item 3</Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export const TestCases: StoryFn<typeof Stack> = () => {
|
||||
return (
|
||||
<div style={{ width: '100%' }}>
|
||||
<Stack gap={4}>
|
||||
<h2>Comparisons Stack vs No stack</h2>
|
||||
<HorizontalStack>
|
||||
<Example title="No stack">
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack">
|
||||
<HorizontalStack>
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Vertical stack">
|
||||
<Stack>
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
</Stack>
|
||||
</Example>
|
||||
</HorizontalStack>
|
||||
|
||||
<HorizontalStack>
|
||||
<Example title="No stack, mismatched heights">
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description and some actions</Card.Description>
|
||||
<Card.Actions>
|
||||
<Button variant="secondary">Settings</Button>
|
||||
<Button variant="secondary">Explore</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
<Button>Please press me!</Button>
|
||||
</Example>
|
||||
<Example title="Vertical stack, mismatched heights">
|
||||
<Stack>
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description and some actions</Card.Description>
|
||||
<Card.Actions>
|
||||
<Button variant="secondary">Settings</Button>
|
||||
<Button variant="secondary">Explore</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
<Button>Please press me!</Button>
|
||||
</Stack>
|
||||
</Example>
|
||||
</HorizontalStack>
|
||||
|
||||
<div style={{ width: 500 }}>
|
||||
<Example title="No stack, too many items">
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
<Button>Another button</Button>
|
||||
<Button>And another</Button>
|
||||
<Button>Why not - one last button!</Button>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, too many items">
|
||||
<HorizontalStack>
|
||||
<Button>A button</Button>
|
||||
<Button>Longer button button</Button>
|
||||
<Button>Another button</Button>
|
||||
<Button>And another</Button>
|
||||
<Button>Why not - one last button!</Button>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
</div>
|
||||
|
||||
<h2>Child alignment</h2>
|
||||
|
||||
<div style={{ width: 500 }}>
|
||||
<Example title="Row, mismatched heights">
|
||||
<HorizontalStack>
|
||||
<MyComponent>
|
||||
<div style={{ height: 50, width: 100, background: 'blue' }} />
|
||||
</MyComponent>
|
||||
<MyComponent>
|
||||
<div style={{ height: 150, width: 100, background: 'orange' }} />
|
||||
</MyComponent>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
</div>
|
||||
|
||||
<Example title="Horizontal stack, mismatched heights">
|
||||
<HorizontalStack>
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description and some actions</Card.Description>
|
||||
<Card.Actions>
|
||||
<Button variant="secondary">Settings</Button>
|
||||
<Button variant="secondary">Explore</Button>
|
||||
</Card.Actions>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, mismatched heights with different components">
|
||||
<HorizontalStack>
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<Card.Heading>I am a card heading</Card.Heading>
|
||||
<Card.Description>Ohhhhh - and now a description!</Card.Description>
|
||||
</Card>
|
||||
|
||||
<Alert severity="info" title="Plus an alert!" />
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, alerts with even heights">
|
||||
<HorizontalStack>
|
||||
<Alert severity="info" title="Plus an alert!" />
|
||||
<Alert severity="success" title="Plus an alert!" />
|
||||
<Alert severity="warning" title="Plus an alert!" />
|
||||
<Alert severity="error" title="Plus an alert!" />
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
|
||||
<Example title="Horizontal stack, alerts with mismatched heights">
|
||||
<HorizontalStack>
|
||||
<Alert severity="info" title="Plus an alert!" />
|
||||
<Alert severity="success" title="Plus an alert!" />
|
||||
<Alert severity="warning" title="Plus an alert!">
|
||||
Surprise - a description! What will happen to the height of all the other alerts?
|
||||
</Alert>
|
||||
<Alert severity="error" title="Plus an alert!" />
|
||||
</HorizontalStack>
|
||||
</Example>
|
||||
<div style={{ width: '600px', height: '600px', border: '1px solid grey' }}>
|
||||
<Stack direction={direction} wrap={wrap} alignItems={alignItems} justifyContent={justifyContent} gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function Example({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
Basic.argTypes = {
|
||||
gap: SpacingTokenControl,
|
||||
direction: { control: 'select', options: ['row', 'row-reverse', 'column', 'column-reverse'] },
|
||||
wrap: { control: 'select', options: ['nowrap', 'wrap', 'wrap-reverse'] },
|
||||
alignItems: {
|
||||
control: 'select',
|
||||
options: ['stretch', 'flex-start', 'flex-end', 'center', 'baseline', 'start', 'end', 'self-start', 'self-end'],
|
||||
},
|
||||
justifyContent: {
|
||||
control: 'select',
|
||||
options: [
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'start',
|
||||
'end',
|
||||
'left',
|
||||
'right',
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
export const AlignItemsExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Text variant="h3">{title}</Text>
|
||||
<div style={{ background: 'rgba(255,255,255,0.1)', border: '1px dashed green' }}>{children}</div>
|
||||
<div style={{ width: '600px' }}>
|
||||
<p>Align items flex-start</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="flex-start" justifyContent="start" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items flex-end</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="flex-end" justifyContent="end" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items baseline</p>
|
||||
<Stack direction="row" wrap="nowrap" alignItems="baseline" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items center</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
<p>Align items stretch</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="stretch" justifyContent="center" gap={2}>
|
||||
<Item color={theme.colors.error.main} height="10em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} height="3em" />
|
||||
<Item color={theme.colors.error.main} />
|
||||
<Item color={theme.colors.error.main} />
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function MyComponent({ children }: { children: React.ReactNode }) {
|
||||
return <div style={{ background: 'rgba(0,255,255, 0.2)', padding: 16 }}>{children}</div>;
|
||||
}
|
||||
export const JustifyContentExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const justifyContentOptions: JustifyContent[] = [
|
||||
'space-between',
|
||||
'space-around',
|
||||
'space-evenly',
|
||||
'flex-start',
|
||||
'flex-end',
|
||||
'center',
|
||||
];
|
||||
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{justifyContentOptions.map((justifyContent) => (
|
||||
<>
|
||||
<p>Justify Content {justifyContent}</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="center" justifyContent={justifyContent} gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const GapExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const gapOptions: ThemeSpacingTokens[] = [2, 8, 10];
|
||||
return (
|
||||
<div style={{ width: '800px' }}>
|
||||
{gapOptions.map((gap) => (
|
||||
<>
|
||||
<p>Gap with spacingToken set to {gap}</p>
|
||||
<Stack direction="row" wrap="wrap" alignItems="flex-start" justifyContent="flex-start" gap={gap}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.error.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const WrapExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const wrapOptions: Wrap[] = ['nowrap', 'wrap', 'wrap-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{wrapOptions.map((wrap) => (
|
||||
<>
|
||||
<p>Wrap examples with {wrap} and gap set to spacingToken 2 (16px)</p>
|
||||
<Stack direction="row" wrap={wrap} alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 10 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DirectionExamples: StoryFn<typeof Stack> = () => {
|
||||
const theme = useTheme2();
|
||||
const directionOptions: Direction[] = ['row', 'row-reverse', 'column', 'column-reverse'];
|
||||
return (
|
||||
<div style={{ width: '600px' }}>
|
||||
{directionOptions.map((direction) => (
|
||||
<>
|
||||
<p>Direction {direction}</p>
|
||||
<Stack direction={direction} wrap="wrap" alignItems="center" justifyContent="center" gap={2}>
|
||||
{Array.from({ length: 5 }).map((_, i) => (
|
||||
<Item key={i} color={theme.colors.warning.main} text={i + 1} />
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
@@ -1,44 +1,27 @@
|
||||
import { Meta, ArgTypes, Canvas } from '@storybook/blocks';
|
||||
import { Stack, HorizontalStack } from './index';
|
||||
import * as Stories from './Stack.internal.story';
|
||||
import { Meta, ArgTypes } from '@storybook/blocks';
|
||||
import { Stack } from './Stack';
|
||||
|
||||
<Meta title="MDX|Stack" component={Stack} />
|
||||
|
||||
# Stack
|
||||
|
||||
The `Stack` component is designed to assist with layout and positioning of elements within a container, offering a simple and flexible way to stack elements vertically or horizontally. This documentation outlines the proper usage of the Stack component and provides guidance on when to use it over the Grid or Flex components.
|
||||
|
||||
There is also a `HorizontalStack` component, which is a thin wrapper around Stack, equivalent to `<Stack direction="row">`.
|
||||
The Stack component is a simple wrapper around the flexbox layout model that allows to easily create responsive and flexible layouts. It provides a simple and intuitive way to align and distribute items within a container either horizontally or vertically.
|
||||
|
||||
### Usage
|
||||
|
||||
#### When to use
|
||||
|
||||
Use the Stack component when you need to arrange elements in a linear format, either vertically or horizontally. It's particularly useful when you want to maintain consistent spacing between items.
|
||||
|
||||
Use the Stack component when:
|
||||
|
||||
- You need a simple way to stack elements with predictable spacing.
|
||||
- You want to ensure consistent alignment.
|
||||
- For creating responsive and flexible layouts that can adapt to different screen sizes and orientations.
|
||||
- When needing a simple and intuitive way to align and distribute items within a container either horizontally or vertically.
|
||||
- To easily reorder and rearrange elements without changing the HTML structure.
|
||||
- When aiming to create equal height columns.
|
||||
- To create a grid-like structure with automatic wrapping and sizing of items based on the available space.
|
||||
|
||||
#### When not to use
|
||||
|
||||
Use the `Grid` component instead for these use cases:
|
||||
- For complex multi-dimensional layouts with intricate requirements that are better suited for CSS frameworks or grid systems.
|
||||
- When precise control over spacing and positioning of elements is necessary.
|
||||
|
||||
- **Intricate Layouts:** Grids are ideal for complex dashboard and magazine-style designs with rows and columns.
|
||||
- **Structured Grid:** Use Grids when items need to span multiple rows/columns, creating structured layouts.
|
||||
|
||||
Use the `Flex` component instead for these use cases:
|
||||
|
||||
- **Alignment:** More options for item alignment.
|
||||
- **Flex items:** Custom flex basis or configure how items stretch and wrap.
|
||||
|
||||
## Props
|
||||
|
||||
### Stack
|
||||
### Props
|
||||
|
||||
<ArgTypes of={Stack} />
|
||||
|
||||
### HorizontalStack
|
||||
|
||||
<ArgTypes of={HorizontalStack} />
|
||||
|
||||
@@ -1,26 +1,89 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { ThemeSpacingTokens } from '@grafana/data';
|
||||
import { GrafanaTheme2, ThemeSpacingTokens } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../../themes';
|
||||
import { ResponsiveProp, getResponsiveStyle } from '../utils/responsiveness';
|
||||
|
||||
export type AlignItems =
|
||||
| 'stretch'
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'baseline'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'self-start'
|
||||
| 'self-end';
|
||||
|
||||
export type JustifyContent =
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'center'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly'
|
||||
| 'start'
|
||||
| 'end'
|
||||
| 'left'
|
||||
| 'right';
|
||||
|
||||
export type Direction = 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
||||
|
||||
export type Wrap = 'nowrap' | 'wrap' | 'wrap-reverse';
|
||||
|
||||
import { Flex } from '../Flex/Flex';
|
||||
import { ResponsiveProp } from '../utils/responsiveness';
|
||||
interface StackProps extends Omit<React.HTMLAttributes<HTMLElement>, 'className' | 'style'> {
|
||||
direction?: ResponsiveProp<'column' | 'row'>;
|
||||
gap?: ResponsiveProp<ThemeSpacingTokens>;
|
||||
alignItems?: ResponsiveProp<AlignItems>;
|
||||
justifyContent?: ResponsiveProp<JustifyContent>;
|
||||
direction?: ResponsiveProp<Direction>;
|
||||
wrap?: ResponsiveProp<Wrap>;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const Stack = React.forwardRef<HTMLDivElement, React.PropsWithChildren<StackProps>>(
|
||||
({ gap = 1, direction = 'column', children, ...rest }, ref) => {
|
||||
export const Stack = React.forwardRef<HTMLDivElement, StackProps>(
|
||||
({ gap = 1, alignItems, justifyContent, direction, wrap, children, ...rest }, ref) => {
|
||||
const styles = useStyles2(getStyles, gap, alignItems, justifyContent, direction, wrap);
|
||||
|
||||
return (
|
||||
<Flex ref={ref} gap={gap} direction={direction} wrap="wrap" {...rest}>
|
||||
{React.Children.toArray(children)
|
||||
.filter(Boolean)
|
||||
.map((child, index) => (
|
||||
<div key={index}>{child}</div>
|
||||
))}
|
||||
</Flex>
|
||||
<div ref={ref} className={styles.flex} {...rest}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Stack.displayName = 'Stack';
|
||||
|
||||
const getStyles = (
|
||||
theme: GrafanaTheme2,
|
||||
gap: StackProps['gap'],
|
||||
alignItems: StackProps['alignItems'],
|
||||
justifyContent: StackProps['justifyContent'],
|
||||
direction: StackProps['direction'],
|
||||
wrap: StackProps['wrap']
|
||||
) => {
|
||||
return {
|
||||
flex: css([
|
||||
{
|
||||
display: 'flex',
|
||||
},
|
||||
getResponsiveStyle<Direction>(theme, direction, (val) => ({
|
||||
flexDirection: val,
|
||||
})),
|
||||
getResponsiveStyle<Wrap>(theme, wrap, (val) => ({
|
||||
flexWrap: val,
|
||||
})),
|
||||
getResponsiveStyle<AlignItems>(theme, alignItems, (val) => ({
|
||||
alignItems: val,
|
||||
})),
|
||||
getResponsiveStyle<JustifyContent>(theme, justifyContent, (val) => ({
|
||||
justifyContent: val,
|
||||
})),
|
||||
getResponsiveStyle<ThemeSpacingTokens>(theme, gap, (val) => ({
|
||||
gap: theme.spacing(val),
|
||||
})),
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { Stack } from './Stack';
|
||||
export { HorizontalStack } from './HorizontalStack';
|
||||
@@ -465,7 +465,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
zIndex: theme.zIndex.tooltip,
|
||||
zIndex: 1,
|
||||
}),
|
||||
rightActions: css({
|
||||
display: 'flex',
|
||||
|
||||
@@ -33,7 +33,7 @@ const SegmentFrame = ({
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<>
|
||||
<SegmentSection label="Segment Name">
|
||||
<SegmentSection label="Segment">
|
||||
{children}
|
||||
<Segment Component={AddButton} onChange={({ value }) => action('New value added')(value)} options={options} />
|
||||
</SegmentSection>
|
||||
|
||||
@@ -29,12 +29,13 @@ const SegmentFrame = ({
|
||||
loadOptions: (options: Array<SelectableValue<string>>) => Promise<Array<SelectableValue<string>>>;
|
||||
}>) => (
|
||||
<>
|
||||
<SegmentSection label="Segment Name">
|
||||
<SegmentSection label="Segment">
|
||||
{children}
|
||||
<SegmentAsync
|
||||
Component={AddButton}
|
||||
onChange={(value) => action('New value added')(value)}
|
||||
loadOptions={() => loadOptions(options)}
|
||||
inputMinWidth={100}
|
||||
/>
|
||||
</SegmentSection>
|
||||
</>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { SegmentInputProps } from './SegmentInput';
|
||||
|
||||
const SegmentFrame = ({ children }: React.PropsWithChildren) => (
|
||||
<>
|
||||
<SegmentSection label="Segment Name">{children}</SegmentSection>
|
||||
<SegmentSection label="Segment">{children}</SegmentSection>
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
@@ -149,8 +149,8 @@ Toggletip.displayName = 'Toggletip';
|
||||
export const getStyles = (theme: GrafanaTheme2) => {
|
||||
const info = buildTooltipTheme(
|
||||
theme,
|
||||
theme.components.tooltip.background,
|
||||
theme.components.tooltip.background,
|
||||
theme.colors.background.primary,
|
||||
theme.colors.border.weak,
|
||||
theme.components.tooltip.text,
|
||||
{ topBottom: 2, rightLeft: 2 }
|
||||
);
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2, GraphSeriesValue } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
|
||||
import { VizTooltipColorIndicator } from './VizTooltipColorIndicator';
|
||||
import { ColorIndicator } from './types';
|
||||
|
||||
export interface SeriesListProps {
|
||||
series: SingleSeriesProps[];
|
||||
}
|
||||
|
||||
// Based on SeriesTable, with new styling
|
||||
export const SeriesList = ({ series }: SeriesListProps) => {
|
||||
return (
|
||||
<>
|
||||
{series.map((series, index) => {
|
||||
return (
|
||||
<SingleSeries
|
||||
isActive={series.isActive}
|
||||
label={series.label}
|
||||
color={series.color}
|
||||
value={series.value}
|
||||
key={`${series.label}-${index}`}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export interface SingleSeriesProps {
|
||||
color?: string;
|
||||
label?: React.ReactNode;
|
||||
value?: string | GraphSeriesValue;
|
||||
isActive?: boolean;
|
||||
colorIndicator?: ColorIndicator;
|
||||
}
|
||||
|
||||
const SingleSeries = ({ label, value, color, colorIndicator = ColorIndicator.series, isActive }: SingleSeriesProps) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<HorizontalGroup justify="space-between" spacing="md" className={styles.hgContainer}>
|
||||
<>
|
||||
{color && <VizTooltipColorIndicator color={color} colorIndicator={colorIndicator} />}
|
||||
{label && <div className={cx(styles.label, isActive && styles.activeSeries)}>{label}</div>}
|
||||
</>
|
||||
{value && <div className={cx(isActive && styles.activeSeries)}>{value}</div>}
|
||||
</HorizontalGroup>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
hgContainer: css({
|
||||
flexGrow: 1,
|
||||
}),
|
||||
activeSeries: css({
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
color: theme.colors.text.maxContrast,
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontWeight: 400,
|
||||
}),
|
||||
});
|
||||
@@ -5,11 +5,19 @@ import { Dimensions, TimeZone } from '@grafana/data';
|
||||
import { TooltipDisplayMode } from '@grafana/schema';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
import { FlotPosition } from '../Graph/types';
|
||||
import { Portal } from '../Portal/Portal';
|
||||
|
||||
import { VizTooltipContainer } from './VizTooltipContainer';
|
||||
|
||||
export interface FlotPosition {
|
||||
pageX: number;
|
||||
pageY: number;
|
||||
x: number;
|
||||
x1: number;
|
||||
y: number;
|
||||
y1: number;
|
||||
}
|
||||
|
||||
// Describes active dimensions user interacts with
|
||||
// It's a key-value pair where:
|
||||
// - key is the name of the dimension
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
import { useStyles2 } from '../../themes';
|
||||
|
||||
import { ColorIndicator } from './types';
|
||||
import { getColorIndicatorClass } from './utils';
|
||||
|
||||
interface Props {
|
||||
color: string;
|
||||
colorIndicator: ColorIndicator;
|
||||
}
|
||||
|
||||
export type ColorIndicatorStyles = ReturnType<typeof getStyles>;
|
||||
|
||||
export const VizTooltipColorIndicator = ({ color, colorIndicator = ColorIndicator.value }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<span
|
||||
style={{ backgroundColor: color }}
|
||||
className={cx(styles.colorIndicator, getColorIndicatorClass(colorIndicator, styles))}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
// @TODO Update classes/add svgs
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
colorIndicator: css({
|
||||
marginRight: theme.spacing(0.5),
|
||||
}),
|
||||
series: css({
|
||||
width: '14px',
|
||||
height: '4px',
|
||||
borderRadius: theme.shape.radius.pill,
|
||||
}),
|
||||
value: css({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: theme.shape.radius.default,
|
||||
fontWeight: 500,
|
||||
}),
|
||||
hexagon: css({}),
|
||||
pie_1_4: css({}),
|
||||
pie_2_4: css({}),
|
||||
pie_3_4: css({}),
|
||||
marker_sm: css({
|
||||
width: '4px',
|
||||
height: '4px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
}),
|
||||
marker_md: css({
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
}),
|
||||
marker_lg: css({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
}),
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import { css } from '@emotion/css';
|
||||
import React from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
@@ -6,15 +6,13 @@ import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { HorizontalGroup } from '..';
|
||||
import { useStyles2 } from '../../themes';
|
||||
|
||||
import { VizTooltipColorIndicator } from './VizTooltipColorIndicator';
|
||||
import { LabelValue } from './types';
|
||||
import { getColorIndicatorClass } from './utils';
|
||||
|
||||
interface Props {
|
||||
keyValuePairs?: LabelValue[];
|
||||
}
|
||||
|
||||
export type HeaderLabelValueStyles = ReturnType<typeof getStyles>;
|
||||
|
||||
export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@@ -25,10 +23,9 @@ export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => {
|
||||
<HorizontalGroup justify="space-between" spacing="md" className={styles.hgContainer} key={i}>
|
||||
<div className={styles.label}>{keyValuePair.label}</div>
|
||||
<>
|
||||
<span
|
||||
style={{ backgroundColor: keyValuePair.color }}
|
||||
className={cx(styles.colorIndicator, getColorIndicatorClass(keyValuePair.colorIndicator!, styles))}
|
||||
/>
|
||||
{keyValuePair.color && (
|
||||
<VizTooltipColorIndicator color={keyValuePair.color} colorIndicator={keyValuePair.colorIndicator!} />
|
||||
)}
|
||||
{keyValuePair.value}
|
||||
</>
|
||||
</HorizontalGroup>
|
||||
@@ -38,46 +35,12 @@ export const VizTooltipHeaderLabelValue = ({ keyValuePairs }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
// @TODO Update classes/add svgs?
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
hgContainer: css({
|
||||
flexGrow: 1,
|
||||
}),
|
||||
colorIndicator: css({
|
||||
marginRight: theme.spacing(0.5),
|
||||
}),
|
||||
label: css({
|
||||
color: theme.colors.text.secondary,
|
||||
fontWeight: 400,
|
||||
}),
|
||||
series: css({
|
||||
width: '14px',
|
||||
height: '4px',
|
||||
borderRadius: theme.shape.radius.pill,
|
||||
}),
|
||||
value: css({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: theme.shape.radius.default,
|
||||
fontWeight: 500,
|
||||
}),
|
||||
hexagon: css({}),
|
||||
pie_1_4: css({}),
|
||||
pie_2_4: css({}),
|
||||
pie_3_4: css({}),
|
||||
marker_sm: css({
|
||||
width: '4px',
|
||||
height: '4px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
}),
|
||||
marker_md: css({
|
||||
width: '8px',
|
||||
height: '8px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
}),
|
||||
marker_lg: css({
|
||||
width: '12px',
|
||||
height: '12px',
|
||||
borderRadius: theme.shape.radius.circle,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { HeaderLabelValueStyles } from './VizTooltipHeaderLabelValue';
|
||||
import { ColorIndicatorStyles } from './VizTooltipColorIndicator';
|
||||
import { ColorIndicator } from './types';
|
||||
|
||||
export const calculateTooltipPosition = (
|
||||
@@ -42,7 +42,7 @@ export const calculateTooltipPosition = (
|
||||
return { x, y };
|
||||
};
|
||||
|
||||
export const getColorIndicatorClass = (colorIndicator: string, styles: HeaderLabelValueStyles) => {
|
||||
export const getColorIndicatorClass = (colorIndicator: string, styles: ColorIndicatorStyles) => {
|
||||
switch (colorIndicator) {
|
||||
case ColorIndicator.value:
|
||||
return styles.value;
|
||||
|
||||
@@ -9,7 +9,5 @@
|
||||
* be subject to the standard policies
|
||||
*/
|
||||
|
||||
export * from './components/Layout/Flex/Flex';
|
||||
|
||||
export { Grid } from './components/Layout/Grid/Grid';
|
||||
export { Stack, HorizontalStack } from './components/Layout/Stack';
|
||||
export { Stack } from './components/Layout/Stack/Stack';
|
||||
|
||||
@@ -230,7 +230,7 @@ func TestAdminAPIEndpoint(t *testing.T) {
|
||||
|
||||
respJSON, err := simplejson.NewJson(sc.resp.Body.Bytes())
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "user already exists", respJSON.Get("error").MustString())
|
||||
assert.Equal(t, "User with email '' or username 'existing@example.com' already exists", respJSON.Get("message").MustString())
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
+1
-8
@@ -499,14 +499,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
})
|
||||
|
||||
// Playlist
|
||||
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) {
|
||||
playlistRoute.Get("/", routing.Wrap(hs.SearchPlaylists))
|
||||
playlistRoute.Get("/:uid", hs.ValidateOrgPlaylist, routing.Wrap(hs.GetPlaylist))
|
||||
playlistRoute.Get("/:uid/items", hs.ValidateOrgPlaylist, routing.Wrap(hs.GetPlaylistItems))
|
||||
playlistRoute.Delete("/:uid", reqEditorRole, hs.ValidateOrgPlaylist, routing.Wrap(hs.DeletePlaylist))
|
||||
playlistRoute.Put("/:uid", reqEditorRole, hs.ValidateOrgPlaylist, routing.Wrap(hs.UpdatePlaylist))
|
||||
playlistRoute.Post("/", reqEditorRole, routing.Wrap(hs.CreatePlaylist))
|
||||
})
|
||||
hs.registerPlaylistAPI(apiRoute)
|
||||
|
||||
// Search
|
||||
apiRoute.Get("/search/sorting", routing.Wrap(hs.ListSortOptions))
|
||||
|
||||
+11
-6
@@ -521,12 +521,13 @@ func (hs *HTTPServer) postDashboard(c *contextmodel.ReqContext, cmd dashboards.S
|
||||
|
||||
c.TimeRequest(metrics.MApiDashboardSave)
|
||||
return response.JSON(http.StatusOK, util.DynMap{
|
||||
"status": "success",
|
||||
"slug": dashboard.Slug,
|
||||
"version": dashboard.Version,
|
||||
"id": dashboard.ID,
|
||||
"uid": dashboard.UID,
|
||||
"url": dashboard.GetURL(),
|
||||
"status": "success",
|
||||
"slug": dashboard.Slug,
|
||||
"version": dashboard.Version,
|
||||
"id": dashboard.ID,
|
||||
"uid": dashboard.UID,
|
||||
"url": dashboard.GetURL(),
|
||||
"folderUid": dashboard.FolderUID,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1301,6 +1302,10 @@ type PostDashboardResponse struct {
|
||||
// required: true
|
||||
// example: /d/nHz3SXiiz/my-dashboard
|
||||
URL string `json:"url"`
|
||||
|
||||
// FolderUID The unique identifier (uid) of the folder the dashboard belongs to.
|
||||
// required: false
|
||||
FolderUID string `json:"folderUid"`
|
||||
} `json:"body"`
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ package api
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -261,7 +260,7 @@ func TestSetFeatureToggles(t *testing.T) {
|
||||
res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
p := readBody(t, res.Body)
|
||||
assert.Equal(t, fmt.Sprintf("invalid toggle passed in: %s", featuremgmt.FlagFeatureToggleAdminPage), p["error"])
|
||||
assert.Equal(t, "invalid toggle passed in", p["message"])
|
||||
})
|
||||
|
||||
t.Run("because it is not GA or Deprecated", func(t *testing.T) {
|
||||
@@ -274,7 +273,7 @@ func TestSetFeatureToggles(t *testing.T) {
|
||||
res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
p := readBody(t, res.Body)
|
||||
assert.Equal(t, "invalid toggle passed in: toggle2", p["error"])
|
||||
assert.Equal(t, "invalid toggle passed in", p["message"])
|
||||
})
|
||||
|
||||
t.Run("because it is configured to be read-only", func(t *testing.T) {
|
||||
@@ -287,7 +286,7 @@ func TestSetFeatureToggles(t *testing.T) {
|
||||
res := runSetScenario(t, features, updates, s, writePermissions, http.StatusBadRequest)
|
||||
defer func() { require.NoError(t, res.Body.Close()) }()
|
||||
p := readBody(t, res.Body)
|
||||
assert.Equal(t, "invalid toggle passed in: toggle3", p["error"])
|
||||
assert.Equal(t, "invalid toggle passed in", p["message"])
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -4,10 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
|
||||
@@ -16,6 +16,8 @@ import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/avatar"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
httpstatic "github.com/grafana/grafana/pkg/api/static"
|
||||
@@ -205,6 +207,7 @@ type HTTPServer struct {
|
||||
authnService authn.Service
|
||||
starApi *starApi.API
|
||||
promRegister prometheus.Registerer
|
||||
clientConfigProvider grafanaapiserver.DirectRestConfigProvider
|
||||
}
|
||||
|
||||
type ServerOptions struct {
|
||||
@@ -246,8 +249,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
accesscontrolService accesscontrol.Service, navTreeService navtree.Service,
|
||||
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService,
|
||||
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service,
|
||||
starApi *starApi.API, promRegister prometheus.Registerer,
|
||||
|
||||
starApi *starApi.API, promRegister prometheus.Registerer, clientConfigProvider grafanaapiserver.DirectRestConfigProvider,
|
||||
) (*HTTPServer, error) {
|
||||
web.Env = cfg.Env
|
||||
m := web.New()
|
||||
@@ -348,6 +350,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
pluginsCDNService: pluginsCDNService,
|
||||
starApi: starApi,
|
||||
promRegister: promRegister,
|
||||
clientConfigProvider: clientConfigProvider,
|
||||
}
|
||||
if hs.Listener != nil {
|
||||
hs.log.Debug("Using provided listener")
|
||||
|
||||
@@ -162,7 +162,7 @@ func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) {
|
||||
var resObj secretsErrorResponseBody
|
||||
err = json.Unmarshal(buf.Bytes(), &resObj)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "unknown error", resObj.Error)
|
||||
require.Equal(t, "", resObj.Error)
|
||||
require.Contains(t, resObj.Message, "Secrets Plugin error:")
|
||||
})
|
||||
}
|
||||
@@ -264,12 +264,12 @@ func TestDataSourceQueryError(t *testing.T) {
|
||||
{
|
||||
request: reqDatasourceByUidNotFound,
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: `{"error":"data source not found","message":"Data source not found","traceID":""}`,
|
||||
expectedBody: `{"message":"Data source not found","traceID":""}`,
|
||||
},
|
||||
{
|
||||
request: reqDatasourceByIdNotFound,
|
||||
expectedStatus: http.StatusNotFound,
|
||||
expectedBody: `{"error":"data source not found","message":"Data source not found","traceID":""}`,
|
||||
expectedBody: `{"message":"Data source not found","traceID":""}`,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
+131
-1
@@ -2,15 +2,145 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/dtos"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/api/routing"
|
||||
internalplaylist "github.com/grafana/grafana/pkg/apis/playlist"
|
||||
"github.com/grafana/grafana/pkg/middleware"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/util/errutil/errhttp"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func (hs *HTTPServer) ValidateOrgPlaylist(c *contextmodel.ReqContext) {
|
||||
type playlistAPIHandler struct {
|
||||
SearchPlaylists []web.Handler
|
||||
GetPlaylist []web.Handler
|
||||
GetPlaylistItems []web.Handler
|
||||
DeletePlaylist []web.Handler
|
||||
UpdatePlaylist []web.Handler
|
||||
CreatePlaylist []web.Handler
|
||||
}
|
||||
|
||||
func chainHandlers(h ...web.Handler) []web.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) registerPlaylistAPI(apiRoute routing.RouteRegister) {
|
||||
handler := playlistAPIHandler{
|
||||
SearchPlaylists: chainHandlers(routing.Wrap(hs.SearchPlaylists)),
|
||||
GetPlaylist: chainHandlers(hs.validateOrgPlaylist, routing.Wrap(hs.GetPlaylist)),
|
||||
GetPlaylistItems: chainHandlers(hs.validateOrgPlaylist, routing.Wrap(hs.GetPlaylistItems)),
|
||||
DeletePlaylist: chainHandlers(middleware.ReqEditorRole, hs.validateOrgPlaylist, routing.Wrap(hs.DeletePlaylist)),
|
||||
UpdatePlaylist: chainHandlers(middleware.ReqEditorRole, hs.validateOrgPlaylist, routing.Wrap(hs.UpdatePlaylist)),
|
||||
CreatePlaylist: chainHandlers(middleware.ReqEditorRole, routing.Wrap(hs.CreatePlaylist)),
|
||||
}
|
||||
|
||||
// Alternative implementations for k8s
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagKubernetesPlaylistsAPI) {
|
||||
namespacer := request.GetNamespaceMapper(hs.Cfg)
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: internalplaylist.GroupName,
|
||||
Version: internalplaylist.VersionID,
|
||||
Resource: "playlists",
|
||||
}
|
||||
|
||||
clientGetter := func(c *contextmodel.ReqContext) (dynamic.ResourceInterface, bool) {
|
||||
dyn, err := dynamic.NewForConfig(hs.clientConfigProvider.GetDirectRestConfig(c))
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "client", err)
|
||||
return nil, false
|
||||
}
|
||||
return dyn.Resource(gvr).Namespace(namespacer(c.OrgID)), true
|
||||
}
|
||||
|
||||
errorWriter := func(c *contextmodel.ReqContext, err error) {
|
||||
//nolint:errorlint
|
||||
statusError, ok := err.(*errors.StatusError)
|
||||
if ok {
|
||||
c.JsonApiErr(int(statusError.Status().Code),
|
||||
statusError.Status().Message, err)
|
||||
return
|
||||
}
|
||||
errhttp.Write(c.Req.Context(), err, c.Resp)
|
||||
}
|
||||
|
||||
handler.SearchPlaylists = []web.Handler{func(c *contextmodel.ReqContext) {
|
||||
client, ok := clientGetter(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
out, err := client.List(c.Req.Context(), v1.ListOptions{})
|
||||
if err != nil {
|
||||
errorWriter(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := strings.ToUpper(c.Query("query"))
|
||||
playlists := []playlist.Playlist{}
|
||||
for _, item := range out.Items {
|
||||
p := internalplaylist.UnstructuredToLegacyPlaylist(item)
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
if query != "" && !strings.Contains(strings.ToUpper(p.Name), query) {
|
||||
continue // query filter
|
||||
}
|
||||
playlists = append(playlists, *p)
|
||||
}
|
||||
c.JSON(http.StatusOK, playlists)
|
||||
}}
|
||||
|
||||
handler.GetPlaylist = []web.Handler{func(c *contextmodel.ReqContext) {
|
||||
client, ok := clientGetter(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
out, err := client.Get(c.Req.Context(), uid, v1.GetOptions{})
|
||||
if err != nil {
|
||||
errorWriter(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out))
|
||||
}}
|
||||
|
||||
handler.GetPlaylistItems = []web.Handler{func(c *contextmodel.ReqContext) {
|
||||
client, ok := clientGetter(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
out, err := client.Get(c.Req.Context(), uid, v1.GetOptions{})
|
||||
if err != nil {
|
||||
errorWriter(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out).Items)
|
||||
}}
|
||||
}
|
||||
|
||||
// Register the actual handlers
|
||||
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) {
|
||||
playlistRoute.Get("/", handler.SearchPlaylists...)
|
||||
playlistRoute.Get("/:uid", handler.GetPlaylist...)
|
||||
playlistRoute.Get("/:uid/items", handler.GetPlaylistItems...)
|
||||
playlistRoute.Delete("/:uid", handler.DeletePlaylist...)
|
||||
playlistRoute.Put("/:uid", handler.UpdatePlaylist...)
|
||||
playlistRoute.Post("/", handler.CreatePlaylist...)
|
||||
})
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) validateOrgPlaylist(c *contextmodel.ReqContext) {
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
query := playlist.GetPlaylistByUidQuery{UID: uid, OrgId: c.SignedInUser.GetOrgID()}
|
||||
p, err := hs.playlistService.GetWithoutItems(c.Req.Context(), &query)
|
||||
|
||||
@@ -128,7 +128,7 @@ func TestCallResource(t *testing.T) {
|
||||
_, err = io.Copy(body, resp.Body)
|
||||
require.NoError(t, err)
|
||||
|
||||
expectedBody := `{ "error": "something went wrong", "message": "Failed to call resource", "traceID": "" }`
|
||||
expectedBody := `{ "message": "Failed to call resource", "traceID": "" }`
|
||||
require.JSONEq(t, expectedBody, body.String())
|
||||
require.NoError(t, resp.Body.Close())
|
||||
require.Equal(t, 500, resp.StatusCode)
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/middleware/requestmeta"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
@@ -253,12 +252,6 @@ func Error(status int, message string, err error) *NormalResponse {
|
||||
data["message"] = message
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
if setting.Env != setting.Prod {
|
||||
data["error"] = err.Error()
|
||||
}
|
||||
}
|
||||
|
||||
resp := JSON(status, data)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
package playlist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/grafana/grafana/pkg/kinds"
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
)
|
||||
|
||||
func UnstructuredToLegacyPlaylist(item unstructured.Unstructured) *playlist.Playlist {
|
||||
spec := item.Object["spec"].(map[string]any)
|
||||
return &playlist.Playlist{
|
||||
UID: item.GetName(),
|
||||
Name: spec["title"].(string),
|
||||
Interval: spec["interval"].(string),
|
||||
Id: getLegacyID(&item),
|
||||
}
|
||||
}
|
||||
|
||||
func UnstructuredToLegacyPlaylistDTO(item unstructured.Unstructured) *playlist.PlaylistDTO {
|
||||
spec := item.Object["spec"].(map[string]any)
|
||||
dto := &playlist.PlaylistDTO{
|
||||
Uid: item.GetName(),
|
||||
Name: spec["title"].(string),
|
||||
Interval: spec["interval"].(string),
|
||||
Id: getLegacyID(&item),
|
||||
}
|
||||
items := spec["items"]
|
||||
if items != nil {
|
||||
b, err := json.Marshal(items)
|
||||
if err == nil {
|
||||
_ = json.Unmarshal(b, &dto.Items)
|
||||
}
|
||||
}
|
||||
return dto
|
||||
}
|
||||
|
||||
func convertToK8sResource(v *playlist.PlaylistDTO, namespacer request.NamespaceMapper) *Playlist {
|
||||
spec := Spec{
|
||||
Title: v.Name,
|
||||
Interval: v.Interval,
|
||||
}
|
||||
for _, item := range v.Items {
|
||||
spec.Items = append(spec.Items, Item{
|
||||
Type: ItemType(item.Type),
|
||||
Value: item.Value,
|
||||
})
|
||||
}
|
||||
|
||||
meta := kinds.GrafanaResourceMetadata{}
|
||||
meta.SetUpdatedTimestampMillis(v.UpdatedAt)
|
||||
if v.Id > 0 {
|
||||
meta.SetOriginInfo(&kinds.ResourceOriginInfo{
|
||||
Name: "SQL",
|
||||
Key: fmt.Sprintf("%d", v.Id),
|
||||
})
|
||||
}
|
||||
return &Playlist{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: v.Uid,
|
||||
UID: types.UID(v.Uid),
|
||||
ResourceVersion: fmt.Sprintf("%d", v.UpdatedAt),
|
||||
CreationTimestamp: metav1.NewTime(time.UnixMilli(v.CreatedAt)),
|
||||
Namespace: namespacer(v.OrgID),
|
||||
Annotations: meta.Annotations,
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
}
|
||||
|
||||
func convertToLegacyUpdateCommand(p *Playlist, orgId int64) (*playlist.UpdatePlaylistCommand, error) {
|
||||
spec := p.Spec
|
||||
cmd := &playlist.UpdatePlaylistCommand{
|
||||
UID: p.Name,
|
||||
Name: spec.Title,
|
||||
Interval: spec.Interval,
|
||||
OrgId: orgId,
|
||||
}
|
||||
for _, item := range spec.Items {
|
||||
if item.Type == ItemTypeDashboardById {
|
||||
return nil, fmt.Errorf("unsupported item type: %s", item.Type)
|
||||
}
|
||||
cmd.Items = append(cmd.Items, playlist.PlaylistItem{
|
||||
Type: string(item.Type),
|
||||
Value: item.Value,
|
||||
})
|
||||
}
|
||||
return cmd, nil
|
||||
}
|
||||
|
||||
// Read legacy ID from metadata annotations
|
||||
func getLegacyID(item *unstructured.Unstructured) int64 {
|
||||
meta := kinds.GrafanaResourceMetadata{
|
||||
Annotations: item.GetAnnotations(),
|
||||
}
|
||||
info := meta.GetOriginInfo()
|
||||
if info != nil && info.Name == "SQL" {
|
||||
i, err := strconv.ParseInt(info.Key, 10, 64)
|
||||
if err == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
+9
-3
@@ -1,4 +1,4 @@
|
||||
package v0alpha1
|
||||
package playlist
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
func TestPlaylistConversion(t *testing.T) {
|
||||
src := &playlist.PlaylistDTO{
|
||||
Id: 123,
|
||||
OrgID: 3,
|
||||
Uid: "abc", // becomes k8s name
|
||||
Name: "MyPlaylists", // becomes title
|
||||
@@ -32,14 +33,19 @@ func TestPlaylistConversion(t *testing.T) {
|
||||
|
||||
out, err := json.MarshalIndent(dst, "", " ")
|
||||
require.NoError(t, err)
|
||||
//fmt.Printf("%s", string(out))
|
||||
// fmt.Printf("%s", string(out))
|
||||
require.JSONEq(t, `{
|
||||
"metadata": {
|
||||
"name": "abc",
|
||||
"namespace": "org-3",
|
||||
"uid": "abc",
|
||||
"resourceVersion": "54321",
|
||||
"creationTimestamp": "1970-01-01T00:00:12Z"
|
||||
"creationTimestamp": "1970-01-01T00:00:12Z",
|
||||
"annotations": {
|
||||
"grafana.app/originKey": "123",
|
||||
"grafana.app/originName": "SQL",
|
||||
"grafana.app/updatedTimestamp": "1970-01-01T00:00:54Z"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"title": "MyPlaylists",
|
||||
@@ -0,0 +1,4 @@
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +groupName=playlist.grafana.app
|
||||
|
||||
package playlist // import "github.com/grafana/grafana/pkg/apis/playlist"
|
||||
@@ -1,8 +1,9 @@
|
||||
package v0alpha1
|
||||
package playlist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
@@ -21,6 +22,9 @@ var (
|
||||
_ rest.Getter = (*legacyStorage)(nil)
|
||||
_ rest.Lister = (*legacyStorage)(nil)
|
||||
_ rest.Storage = (*legacyStorage)(nil)
|
||||
_ rest.Creater = (*legacyStorage)(nil)
|
||||
_ rest.Updater = (*legacyStorage)(nil)
|
||||
_ rest.GracefulDeleter = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
type legacyStorage struct {
|
||||
@@ -110,3 +114,96 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge
|
||||
|
||||
return convertToK8sResource(dto, s.namespacer), nil
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Create(ctx context.Context,
|
||||
obj runtime.Object,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
options *metav1.CreateOptions,
|
||||
) (runtime.Object, error) {
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p, ok := obj.(*Playlist)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected playlist?")
|
||||
}
|
||||
cmd, err := convertToLegacyUpdateCommand(p, info.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out, err := s.service.Create(ctx, &playlist.CreatePlaylistCommand{
|
||||
UID: p.Name,
|
||||
Name: cmd.Name,
|
||||
Interval: cmd.Interval,
|
||||
Items: cmd.Items,
|
||||
OrgId: cmd.OrgId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.Get(ctx, out.UID, nil)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Update(ctx context.Context,
|
||||
name string,
|
||||
objInfo rest.UpdatedObjectInfo,
|
||||
createValidation rest.ValidateObjectFunc,
|
||||
updateValidation rest.ValidateObjectUpdateFunc,
|
||||
forceAllowCreate bool,
|
||||
options *metav1.UpdateOptions,
|
||||
) (runtime.Object, bool, error) {
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
created := false
|
||||
old, err := s.Get(ctx, name, nil)
|
||||
if err != nil {
|
||||
return old, created, err
|
||||
}
|
||||
|
||||
obj, err := objInfo.UpdatedObject(ctx, old)
|
||||
if err != nil {
|
||||
return old, created, err
|
||||
}
|
||||
p, ok := obj.(*Playlist)
|
||||
if !ok {
|
||||
return nil, created, fmt.Errorf("expected playlist after update")
|
||||
}
|
||||
|
||||
cmd, err := convertToLegacyUpdateCommand(p, info.OrgID)
|
||||
if err != nil {
|
||||
return old, created, err
|
||||
}
|
||||
_, err = s.service.Update(ctx, cmd)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
r, err := s.Get(ctx, name, nil)
|
||||
return r, created, err
|
||||
}
|
||||
|
||||
// GracefulDeleter
|
||||
func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
v, err := s.Get(ctx, name, &metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return v, false, err // includes the not-found error
|
||||
}
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
p, ok := v.(*Playlist)
|
||||
if !ok {
|
||||
return v, false, fmt.Errorf("expected a playlist response from Get")
|
||||
}
|
||||
err = s.service.Delete(ctx, &playlist.DeletePlaylistCommand{
|
||||
UID: name,
|
||||
OrgId: info.OrgID,
|
||||
})
|
||||
return p, true, err // true is instant delete
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package playlist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
// GroupName is the group name for this API.
|
||||
const GroupName = "playlist.grafana.app"
|
||||
const VersionID = runtime.APIVersionInternal
|
||||
|
||||
var _ grafanaapiserver.APIGroupBuilder = (*PlaylistAPIBuilder)(nil)
|
||||
|
||||
// This is used just so wire has something unique to return
|
||||
type PlaylistAPIBuilder struct {
|
||||
service playlist.Service
|
||||
namespacer request.NamespaceMapper
|
||||
gv schema.GroupVersion
|
||||
}
|
||||
|
||||
func RegisterAPIService(p playlist.Service,
|
||||
apiregistration grafanaapiserver.APIRegistrar,
|
||||
cfg *setting.Cfg,
|
||||
) *PlaylistAPIBuilder {
|
||||
builder := &PlaylistAPIBuilder{
|
||||
service: p,
|
||||
namespacer: request.GetNamespaceMapper(cfg),
|
||||
gv: schema.GroupVersion{Group: GroupName, Version: VersionID},
|
||||
}
|
||||
apiregistration.RegisterAPI(builder)
|
||||
return builder
|
||||
}
|
||||
|
||||
func (b *PlaylistAPIBuilder) GetGroupVersion() schema.GroupVersion {
|
||||
return b.gv
|
||||
}
|
||||
|
||||
func (b *PlaylistAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||
scheme.AddKnownTypes(b.gv,
|
||||
&Playlist{},
|
||||
&PlaylistList{},
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *PlaylistAPIBuilder) GetAPIGroupInfo(
|
||||
scheme *runtime.Scheme,
|
||||
codecs serializer.CodecFactory, // pointer?
|
||||
optsGetter generic.RESTOptionsGetter,
|
||||
) (*genericapiserver.APIGroupInfo, error) {
|
||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(GroupName, scheme, metav1.ParameterCodec, codecs)
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
legacyStore := &legacyStorage{
|
||||
service: b.service,
|
||||
namespacer: b.namespacer,
|
||||
DefaultQualifiedResource: b.gv.WithResource("playlists").GroupResource(),
|
||||
SingularQualifiedResource: b.gv.WithResource("playlist").GroupResource(),
|
||||
}
|
||||
legacyStore.tableConverter = utils.NewTableConverter(
|
||||
legacyStore.DefaultQualifiedResource,
|
||||
[]metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Title", Type: "string", Format: "string", Description: "The playlist name"},
|
||||
{Name: "Interval", Type: "string", Format: "string", Description: "How often the playlist will update"},
|
||||
{Name: "Created At", Type: "date"},
|
||||
},
|
||||
func(obj runtime.Object) ([]interface{}, error) {
|
||||
m, ok := obj.(*Playlist)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected playlist")
|
||||
}
|
||||
return []interface{}{
|
||||
m.Name,
|
||||
m.Spec.Title,
|
||||
m.Spec.Interval,
|
||||
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
},
|
||||
)
|
||||
storage["playlists"] = legacyStore
|
||||
|
||||
// enable dual writes if a RESTOptionsGetter is provided
|
||||
if optsGetter != nil {
|
||||
store, err := newStorage(scheme, optsGetter, legacyStore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storage["playlists"] = grafanarest.NewDualWriter(legacyStore, store)
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap["v0alpha1"] = storage
|
||||
return &apiGroupInfo, nil
|
||||
}
|
||||
|
||||
func (b *PlaylistAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
||||
return nil // no custom OpenAPI definitions
|
||||
}
|
||||
|
||||
func (b *PlaylistAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
|
||||
return nil // no custom API routes
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v0alpha1
|
||||
package playlist
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -0,0 +1,65 @@
|
||||
package playlist
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type Playlist struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// Standard object's metadata
|
||||
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
|
||||
// +optional
|
||||
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||
|
||||
Spec Spec `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type PlaylistList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
// +optional
|
||||
metav1.ListMeta `json:"metadata,omitempty"`
|
||||
|
||||
Items []Playlist `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// Spec defines model for Spec.
|
||||
type Spec struct {
|
||||
// Name of the playlist.
|
||||
Title string `json:"title"`
|
||||
|
||||
// Interval sets the time between switching views in a playlist.
|
||||
Interval string `json:"interval"`
|
||||
|
||||
// The ordered list of items that the playlist will iterate over.
|
||||
Items []Item `json:"items,omitempty"`
|
||||
}
|
||||
|
||||
// Defines values for ItemType.
|
||||
const (
|
||||
ItemTypeDashboardByTag ItemType = "dashboard_by_tag"
|
||||
ItemTypeDashboardByUid ItemType = "dashboard_by_uid"
|
||||
|
||||
// deprecated -- should use UID
|
||||
ItemTypeDashboardById ItemType = "dashboard_by_id"
|
||||
)
|
||||
|
||||
// Item defines model for Item.
|
||||
type Item struct {
|
||||
// Type of the item.
|
||||
Type ItemType `json:"type"`
|
||||
|
||||
// Value depends on type and describes the playlist item.
|
||||
//
|
||||
// - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This
|
||||
// is not portable as the numerical identifier is non-deterministic between different instances.
|
||||
// Will be replaced by dashboard_by_uid in the future. (deprecated)
|
||||
// - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All
|
||||
// dashboards behind the tag will be added to the playlist.
|
||||
// - dashboard_by_uid: The value is the dashboard UID
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Type of the item.
|
||||
type ItemType string
|
||||
@@ -1,35 +0,0 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
)
|
||||
|
||||
func convertToK8sResource(v *playlist.PlaylistDTO, namespacer request.NamespaceMapper) *Playlist {
|
||||
spec := Spec{
|
||||
Title: v.Name,
|
||||
Interval: v.Interval,
|
||||
}
|
||||
for _, item := range v.Items {
|
||||
spec.Items = append(spec.Items, Item{
|
||||
Type: ItemType(item.Type),
|
||||
Value: item.Value,
|
||||
})
|
||||
}
|
||||
return &Playlist{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: v.Uid,
|
||||
UID: types.UID(v.Uid),
|
||||
ResourceVersion: fmt.Sprintf("%d", v.UpdatedAt),
|
||||
CreationTimestamp: metav1.NewTime(time.UnixMilli(v.CreatedAt)),
|
||||
Namespace: namespacer(v.OrgID),
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user