Merge remote-tracking branch 'origin/main' into resource-store
This commit is contained in:
+3
-26
@@ -3630,13 +3630,11 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
"public/app/features/datasources/components/DataSourceTestingStatus.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, "No untranslated strings. Wrap text with <Trans />", "0"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "1"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "2"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "3"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "5"],
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"]
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "4"]
|
||||
],
|
||||
"public/app/features/datasources/components/DataSourceTypeCard.tsx:5381": [
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"]
|
||||
@@ -4541,14 +4539,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"]
|
||||
],
|
||||
"public/app/features/inspector/InspectStatsTable.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/inspector/InspectStatsTraceIdsTable.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/inspector/QueryInspector.tsx:5381": [
|
||||
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "1"],
|
||||
@@ -4559,19 +4549,6 @@ exports[`better eslint`] = {
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "6"],
|
||||
[0, 0, 0, "Use data-testid for E2E selectors instead of aria-label", "7"]
|
||||
],
|
||||
"public/app/features/inspector/styles.ts: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"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "8"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "9"],
|
||||
[0, 0, 0, "Styles should be written using objects.", "10"]
|
||||
],
|
||||
"public/app/features/invites/InviteeRow.tsx:5381": [
|
||||
[0, 0, 0, "No untranslated strings. Wrap text with <Trans />", "0"]
|
||||
],
|
||||
|
||||
+60
-1
@@ -2731,6 +2731,47 @@ volumes:
|
||||
clone:
|
||||
retries: 3
|
||||
depends_on: []
|
||||
image_pull_secrets:
|
||||
- gcr
|
||||
- gar
|
||||
kind: pipeline
|
||||
name: create-release-pr
|
||||
node:
|
||||
type: no-parallel
|
||||
platform:
|
||||
arch: amd64
|
||||
os: linux
|
||||
services: []
|
||||
steps:
|
||||
- commands:
|
||||
- apk add perl
|
||||
- v_target=`echo $${TAG} | perl -pe 's/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/v\1.\2.x/'`
|
||||
- default_target=`if [[ -n $$LATEST ]]; then echo 'main'; else echo $$v_target;
|
||||
fi`
|
||||
- backport=`if [[ -n $$LATEST ]]; then echo $$v_target; fi`
|
||||
- curl -L $${GH_CLI_URL} | tar -xz --strip-components=1 -C /usr
|
||||
- gh workflow run -f dry_run=$${DRY_RUN} -f version=$${TAG} -f target=$${TARGET:-$default_target}
|
||||
-f backport=$${BACKPORT:-$default_backport} --repo=grafana/grafana release-pr.yml
|
||||
depends_on: []
|
||||
environment:
|
||||
GH_CLI_URL: https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_linux_amd64.tar.gz
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: create-release-pr
|
||||
trigger:
|
||||
event:
|
||||
- promote
|
||||
target: release-pr
|
||||
type: docker
|
||||
volumes:
|
||||
- host:
|
||||
path: /var/run/docker.sock
|
||||
name: docker
|
||||
---
|
||||
clone:
|
||||
retries: 3
|
||||
depends_on: []
|
||||
environment:
|
||||
EDITION: oss
|
||||
image_pull_secrets:
|
||||
@@ -2787,6 +2828,24 @@ steps:
|
||||
from_secret: prerelease_bucket
|
||||
image: grafana/grafana-ci-deploy:1.3.3
|
||||
name: publish-storybook
|
||||
- commands:
|
||||
- apk add perl
|
||||
- v_target=`echo $${TAG} | perl -pe 's/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/v\1.\2.x/'`
|
||||
- default_target=`if [[ -n $$LATEST ]]; then echo 'main'; else echo $$v_target;
|
||||
fi`
|
||||
- backport=`if [[ -n $$LATEST ]]; then echo $$v_target; fi`
|
||||
- curl -L $${GH_CLI_URL} | tar -xz --strip-components=1 -C /usr
|
||||
- gh workflow run -f dry_run=$${DRY_RUN} -f version=$${TAG} -f target=$${TARGET:-$default_target}
|
||||
-f backport=$${BACKPORT:-$default_backport} --repo=grafana/grafana release-pr.yml
|
||||
depends_on:
|
||||
- publish-artifacts
|
||||
- publish-static-assets
|
||||
environment:
|
||||
GH_CLI_URL: https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_linux_amd64.tar.gz
|
||||
GITHUB_TOKEN:
|
||||
from_secret: github_token
|
||||
image: byrnedo/alpine-curl:0.1.8
|
||||
name: create-release-pr
|
||||
trigger:
|
||||
event:
|
||||
- promote
|
||||
@@ -4893,6 +4952,6 @@ kind: secret
|
||||
name: gcr_credentials
|
||||
---
|
||||
kind: signature
|
||||
hmac: 08f38b820f97302de03a9fdfd39fb12c185bb36170704cf7591c16f33c3e4d31
|
||||
hmac: 043028c50d984e1ea98a294c6746df1388cb0b7d7976f82f3dd0004fc493bafc
|
||||
|
||||
...
|
||||
|
||||
@@ -57,6 +57,6 @@ jobs:
|
||||
- name: Create PR with backports
|
||||
if: "${{ github.event.inputs.backport != '' }}"
|
||||
run: >
|
||||
gh pr create -l "backport-${{ inputs.backport }}" --dry-run=${{ inputs.dry_run }} -H "release/${{ inputs.version }}" -B "${{ inputs.target }}" --title "Release: ${{ inputs.version }}" --body "These code changes must be merged after a release is complete"
|
||||
gh pr create -l "backport ${{ inputs.backport }}" --dry-run=${{ inputs.dry_run }} -H "release/${{ inputs.version }}" -B "${{ inputs.target }}" --title "Release: ${{ inputs.version }}" --body "These code changes must be merged after a release is complete"
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.generate_token.outputs.token }}
|
||||
|
||||
@@ -77,6 +77,7 @@ With a Grafana Enterprise license, you also get access to premium data sources,
|
||||
- [AppDynamics](/grafana/plugins/dlopes7-appdynamics-datasource)
|
||||
- [Azure CosmosDB](/grafana/plugins/grafana-azurecosmosdb-datasource)
|
||||
- [Azure Devops](/grafana/plugins/grafana-azuredevops-datasource)
|
||||
- [Catchpoint](/grafana/plugins/grafana-catchpoint-datasource)
|
||||
- [Databricks](/grafana/plugins/grafana-databricks-datasource)
|
||||
- [DataDog](/grafana/plugins/grafana-datadog-datasource)
|
||||
- [Dynatrace](/grafana/plugins/grafana-dynatrace-datasource)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
import { Button, Icon, Modal } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Button, Icon, Modal, useStyles2 } from '@grafana/ui';
|
||||
|
||||
type ConfirmModalProps = {
|
||||
isOpen: boolean;
|
||||
@@ -10,6 +12,7 @@ type ConfirmModalProps = {
|
||||
};
|
||||
export function ConfirmModal({ isOpen, onCancel, onDiscard, onCopy }: ConfirmModalProps) {
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
// Moved from grafana/ui
|
||||
useEffect(() => {
|
||||
@@ -24,7 +27,7 @@ export function ConfirmModal({ isOpen, onCancel, onDiscard, onCopy }: ConfirmMod
|
||||
title={
|
||||
<div className="modal-header-title">
|
||||
<Icon name="exclamation-triangle" size="lg" />
|
||||
<span className="p-l-1">Warning</span>
|
||||
<span className={styles.titleText}>Warning</span>
|
||||
</div>
|
||||
}
|
||||
onDismiss={onCancel}
|
||||
@@ -49,3 +52,9 @@ export function ConfirmModal({ isOpen, onCancel, onDiscard, onCopy }: ConfirmMod
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
titleText: css({
|
||||
paddingLeft: theme.spacing(2),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useState, useCallback, useId } from 'react';
|
||||
|
||||
import { SelectableValue } from '@grafana/data';
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { useTheme2 } from '../../themes';
|
||||
import { useStyles2, useTheme2 } from '../../themes';
|
||||
import { FormField } from '../FormField/FormField';
|
||||
import { InlineFormLabel } from '../FormLabel/FormLabel';
|
||||
import { InlineField } from '../Forms/InlineField';
|
||||
@@ -37,28 +37,38 @@ const DEFAULT_ACCESS_OPTION = {
|
||||
value: 'proxy',
|
||||
};
|
||||
|
||||
const HttpAccessHelp = () => (
|
||||
<div className="grafana-info-box m-t-2">
|
||||
<p>
|
||||
Access mode controls how requests to the data source will be handled.
|
||||
<strong>
|
||||
<i>Server</i>
|
||||
</strong>{' '}
|
||||
should be the preferred way if nothing else is stated.
|
||||
</p>
|
||||
<div className="alert-title">Server access mode (Default):</div>
|
||||
<p>
|
||||
All requests will be made from the browser to Grafana backend/server which in turn will forward the requests to
|
||||
the data source and by that circumvent possible Cross-Origin Resource Sharing (CORS) requirements. The URL needs
|
||||
to be accessible from the grafana backend/server if you select this access mode.
|
||||
</p>
|
||||
<div className="alert-title">Browser access mode:</div>
|
||||
<p>
|
||||
All requests will be made from the browser directly to the data source and may be subject to Cross-Origin Resource
|
||||
Sharing (CORS) requirements. The URL needs to be accessible from the browser if you select this access mode.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
const HttpAccessHelp = () => {
|
||||
const styles = useStyles2(getAccessStyles);
|
||||
return (
|
||||
<div className={cx('grafana-info-box', styles.infoBox)}>
|
||||
<p>
|
||||
Access mode controls how requests to the data source will be handled.
|
||||
<strong>
|
||||
<i>Server</i>
|
||||
</strong>{' '}
|
||||
should be the preferred way if nothing else is stated.
|
||||
</p>
|
||||
<div className="alert-title">Server access mode (Default):</div>
|
||||
<p>
|
||||
All requests will be made from the browser to Grafana backend/server which in turn will forward the requests to
|
||||
the data source and by that circumvent possible Cross-Origin Resource Sharing (CORS) requirements. The URL needs
|
||||
to be accessible from the grafana backend/server if you select this access mode.
|
||||
</p>
|
||||
<div className="alert-title">Browser access mode:</div>
|
||||
<p>
|
||||
All requests will be made from the browser directly to the data source and may be subject to Cross-Origin
|
||||
Resource Sharing (CORS) requirements. The URL needs to be accessible from the browser if you select this access
|
||||
mode.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getAccessStyles = (theme: GrafanaTheme2) => ({
|
||||
infoBox: css({
|
||||
marginTop: theme.spacing(3),
|
||||
}),
|
||||
});
|
||||
|
||||
const LABEL_WIDTH = 26;
|
||||
|
||||
|
||||
@@ -5,8 +5,10 @@ import { useTheme2 } from '../ThemeContext';
|
||||
|
||||
import { getAgularPanelStyles } from './angularPanelStyles';
|
||||
import { getCardStyles } from './card';
|
||||
import { getCodeStyles } from './code';
|
||||
import { getElementStyles } from './elements';
|
||||
import { getExtraStyles } from './extra';
|
||||
import { getFontStyles } from './fonts';
|
||||
import { getFormElementStyles } from './forms';
|
||||
import { getLegacySelectStyles } from './legacySelect';
|
||||
import { getMarkdownStyles } from './markdownStyles';
|
||||
@@ -22,8 +24,10 @@ export function GlobalStyles() {
|
||||
return (
|
||||
<Global
|
||||
styles={[
|
||||
getCodeStyles(theme),
|
||||
getElementStyles(theme),
|
||||
getExtraStyles(theme),
|
||||
getFontStyles(theme),
|
||||
getFormElementStyles(theme),
|
||||
getPageStyles(theme),
|
||||
getCardStyles(theme),
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export function getCodeStyles(theme: GrafanaTheme2) {
|
||||
return css({
|
||||
'code, pre': {
|
||||
...theme.typography.code,
|
||||
fontSize: theme.typography.bodySmall.fontSize,
|
||||
backgroundColor: theme.colors.background.primary,
|
||||
color: theme.colors.text.primary,
|
||||
border: `1px solid ${theme.colors.border.medium}`,
|
||||
borderRadius: '4px',
|
||||
},
|
||||
|
||||
code: {
|
||||
whiteSpace: 'nowrap',
|
||||
padding: '2px 5px',
|
||||
margin: '0 2px',
|
||||
},
|
||||
|
||||
pre: {
|
||||
display: 'block',
|
||||
margin: `0 0 ${theme.typography.body.lineHeight}`,
|
||||
lineHeight: theme.typography.body.lineHeight,
|
||||
wordBreak: 'break-all',
|
||||
wordWrap: 'break-word',
|
||||
whiteSpace: 'pre-wrap',
|
||||
padding: '10px',
|
||||
|
||||
code: {
|
||||
padding: 0,
|
||||
color: 'inherit',
|
||||
whiteSpace: 'pre-wrap',
|
||||
backgroundColor: 'transparent',
|
||||
border: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import { css } from '@emotion/react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
|
||||
export function getFontStyles(theme: GrafanaTheme2) {
|
||||
return css([
|
||||
{
|
||||
/* latin */
|
||||
'@font-face': {
|
||||
fontFamily: 'Roboto Mono',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 400,
|
||||
fontDisplay: 'swap',
|
||||
src: "url('./public/fonts/roboto/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2') format('woff2')",
|
||||
unicodeRange:
|
||||
'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
|
||||
},
|
||||
},
|
||||
{
|
||||
/* latin */
|
||||
'@font-face': {
|
||||
fontFamily: 'Roboto Mono',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 500,
|
||||
fontDisplay: 'swap',
|
||||
src: "url('./public/fonts/roboto/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2') format('woff2')",
|
||||
unicodeRange:
|
||||
'U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD',
|
||||
},
|
||||
},
|
||||
{
|
||||
/*
|
||||
To add new variations/version of Inter, download from https://rsms.me/inter/ and add the
|
||||
web font files to the public/fonts/inter folder. Do not download the fonts from Google Fonts
|
||||
or somewhere else because they don't support the features we require (like tabular numerals).
|
||||
|
||||
If adding additional weights, consider switching to the InterVariable variable font as combined
|
||||
it may take less space than multiple static weights.
|
||||
*/
|
||||
'@font-face': {
|
||||
fontFamily: 'Inter',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 400,
|
||||
fontDisplay: 'swap',
|
||||
src: "url('./public/fonts/inter/Inter-Regular.woff2') format('woff2')",
|
||||
},
|
||||
},
|
||||
{
|
||||
'@font-face': {
|
||||
fontFamily: 'Inter',
|
||||
fontStyle: 'normal',
|
||||
fontWeight: 500,
|
||||
fontDisplay: 'swap',
|
||||
src: "url('./public/fonts/inter/Inter-Medium.woff2') format('woff2')",
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
+1
-1
@@ -158,7 +158,7 @@ func (hs *HTTPServer) setIndexViewData(c *contextmodel.ReqContext) (*dtos.IndexV
|
||||
|
||||
hs.HooksService.RunIndexDataHooks(&data, c)
|
||||
|
||||
data.NavTree.ApplyAdminIA()
|
||||
data.NavTree.ApplyCostManagementIA()
|
||||
data.NavTree.ApplyHelpVersion(data.Settings.BuildInfo.VersionString) // RunIndexDataHooks can modify the version string
|
||||
data.NavTree.Sort()
|
||||
|
||||
|
||||
@@ -272,7 +272,9 @@ func (ots *TracingService) initOpentelemetryTracer() error {
|
||||
}
|
||||
}
|
||||
|
||||
tp = NewProfilingTracerProvider(tp)
|
||||
if ots.cfg.ProfilingIntegration {
|
||||
tp = NewProfilingTracerProvider(tp)
|
||||
}
|
||||
|
||||
// Register our TracerProvider as the global so any imported
|
||||
// instrumentation in the future will default to using it
|
||||
|
||||
@@ -21,6 +21,8 @@ type TracingConfig struct {
|
||||
|
||||
ServiceName string
|
||||
ServiceVersion string
|
||||
|
||||
ProfilingIntegration bool
|
||||
}
|
||||
|
||||
func ProvideTracingConfig(cfg *setting.Cfg) (*TracingConfig, error) {
|
||||
|
||||
@@ -109,6 +109,7 @@ func (o *TracingOptions) ApplyTo(config *genericapiserver.RecommendedConfig) err
|
||||
tracingCfg.Sampler = o.SamplerType
|
||||
tracingCfg.SamplerParam = o.SamplerParam
|
||||
tracingCfg.SamplerRemoteURL = o.SamplingServiceURL
|
||||
tracingCfg.ProfilingIntegration = true
|
||||
|
||||
ts, err := tracing.ProvideService(tracingCfg)
|
||||
if err != nil {
|
||||
|
||||
@@ -59,12 +59,13 @@ func ProvideService(cfg *setting.Cfg, hooksService *hooks.HooksService) *OSSLice
|
||||
return
|
||||
}
|
||||
|
||||
if adminNode := indexData.NavTree.FindById(navtree.NavIDCfg); adminNode != nil {
|
||||
if adminNode := indexData.NavTree.FindById(navtree.NavIDCfgGeneral); adminNode != nil {
|
||||
adminNode.Children = append(adminNode.Children, &navtree.NavLink{
|
||||
Text: "Stats and license",
|
||||
Id: "upgrading",
|
||||
Url: l.LicenseURL(req.IsGrafanaAdmin),
|
||||
Icon: "unlock",
|
||||
Text: "Stats and license",
|
||||
Id: "upgrading",
|
||||
Url: l.LicenseURL(req.IsGrafanaAdmin),
|
||||
Icon: "unlock",
|
||||
SortWeight: -1,
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -136,113 +136,50 @@ func (root *NavTreeRoot) ApplyHelpVersion(version string) {
|
||||
}
|
||||
}
|
||||
|
||||
func (root *NavTreeRoot) ApplyAdminIA() {
|
||||
func (root *NavTreeRoot) ApplyCostManagementIA() {
|
||||
orgAdminNode := root.FindById(NavIDCfg)
|
||||
var costManagementApp *NavLink
|
||||
var adaptiveMetricsApp *NavLink
|
||||
var attributionsApp *NavLink
|
||||
var logVolumeExplorerApp *NavLink
|
||||
|
||||
if orgAdminNode != nil {
|
||||
adminNodeLinks := []*NavLink{}
|
||||
|
||||
generalNodeLinks := []*NavLink{}
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("upgrading")) // TODO does this even exist
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("licensing"))
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("org-settings"))
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("server-settings"))
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("global-orgs"))
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("feature-toggles"))
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("storage"))
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("migrate-to-cloud"))
|
||||
generalNodeLinks = AppendIfNotNil(generalNodeLinks, root.FindById("banner-settings"))
|
||||
|
||||
generalNode := &NavLink{
|
||||
Text: "General",
|
||||
SubTitle: "Manage default preferences and settings across Grafana",
|
||||
Id: NavIDCfgGeneral,
|
||||
Url: "/admin/general",
|
||||
Icon: "shield",
|
||||
Children: generalNodeLinks,
|
||||
for _, element := range orgAdminNode.Children {
|
||||
switch navId := element.Id; navId {
|
||||
case "plugin-page-grafana-costmanagementui-app":
|
||||
costManagementApp = element
|
||||
case "plugin-page-grafana-adaptive-metrics-app":
|
||||
adaptiveMetricsApp = element
|
||||
case "plugin-page-grafana-attributions-app":
|
||||
attributionsApp = element
|
||||
case "plugin-page-grafana-logvolumeexplorer-app":
|
||||
logVolumeExplorerApp = element
|
||||
default:
|
||||
adminNodeLinks = append(adminNodeLinks, element)
|
||||
}
|
||||
}
|
||||
|
||||
pluginsNodeLinks := []*NavLink{}
|
||||
pluginsNodeLinks = AppendIfNotNil(pluginsNodeLinks, root.FindById("plugins"))
|
||||
pluginsNodeLinks = AppendIfNotNil(pluginsNodeLinks, root.FindById("datasources"))
|
||||
pluginsNodeLinks = AppendIfNotNil(pluginsNodeLinks, root.FindById("recordedQueries"))
|
||||
pluginsNodeLinks = AppendIfNotNil(pluginsNodeLinks, root.FindById("correlations"))
|
||||
pluginsNodeLinks = AppendIfNotNil(pluginsNodeLinks, root.FindById("plugin-page-grafana-cloud-link-app"))
|
||||
if costManagementApp != nil {
|
||||
costManagementMetricsNode := FindByURL(costManagementApp.Children, "/a/grafana-costmanagementui-app/metrics")
|
||||
if costManagementMetricsNode != nil {
|
||||
if adaptiveMetricsApp != nil {
|
||||
costManagementMetricsNode.Children = append(costManagementMetricsNode.Children, adaptiveMetricsApp)
|
||||
}
|
||||
if attributionsApp != nil {
|
||||
costManagementMetricsNode.Children = append(costManagementMetricsNode.Children, attributionsApp)
|
||||
}
|
||||
}
|
||||
|
||||
pluginsNode := &NavLink{
|
||||
Text: "Plugins and data",
|
||||
SubTitle: "Install plugins and define the relationships between data",
|
||||
Id: NavIDCfgPlugins,
|
||||
Url: "/admin/plugins",
|
||||
Icon: "shield",
|
||||
Children: pluginsNodeLinks,
|
||||
}
|
||||
|
||||
accessNodeLinks := []*NavLink{}
|
||||
accessNodeLinks = AppendIfNotNil(accessNodeLinks, root.FindById("global-users"))
|
||||
accessNodeLinks = AppendIfNotNil(accessNodeLinks, root.FindById("teams"))
|
||||
accessNodeLinks = AppendIfNotNil(accessNodeLinks, root.FindById("standalone-plugin-page-/a/grafana-auth-app"))
|
||||
accessNodeLinks = AppendIfNotNil(accessNodeLinks, root.FindById("serviceaccounts"))
|
||||
accessNodeLinks = AppendIfNotNil(accessNodeLinks, root.FindById("apikeys"))
|
||||
|
||||
usersNode := &NavLink{
|
||||
Text: "Users and access",
|
||||
SubTitle: "Configure access for individual users, teams, and service accounts",
|
||||
Id: NavIDCfgAccess,
|
||||
Url: "/admin/access",
|
||||
Icon: "shield",
|
||||
Children: accessNodeLinks,
|
||||
}
|
||||
|
||||
if len(generalNode.Children) > 0 {
|
||||
adminNodeLinks = append(adminNodeLinks, generalNode)
|
||||
}
|
||||
|
||||
if len(pluginsNode.Children) > 0 {
|
||||
adminNodeLinks = append(adminNodeLinks, pluginsNode)
|
||||
}
|
||||
|
||||
if len(usersNode.Children) > 0 {
|
||||
adminNodeLinks = append(adminNodeLinks, usersNode)
|
||||
}
|
||||
|
||||
authenticationNode := root.FindById("authentication")
|
||||
if authenticationNode != nil {
|
||||
authenticationNode.IsSection = true
|
||||
adminNodeLinks = append(adminNodeLinks, authenticationNode)
|
||||
}
|
||||
|
||||
costManagementNode := root.FindById("plugin-page-grafana-costmanagementui-app")
|
||||
|
||||
if costManagementNode != nil {
|
||||
adminNodeLinks = append(adminNodeLinks, costManagementNode)
|
||||
}
|
||||
|
||||
costManagementMetricsNode := root.FindByURL("/a/grafana-costmanagementui-app/metrics")
|
||||
adaptiveMetricsNode := root.FindById("plugin-page-grafana-adaptive-metrics-app")
|
||||
|
||||
if costManagementMetricsNode != nil && adaptiveMetricsNode != nil {
|
||||
costManagementMetricsNode.Children = append(costManagementMetricsNode.Children, adaptiveMetricsNode)
|
||||
}
|
||||
|
||||
attributionsNode := root.FindById("plugin-page-grafana-attributions-app")
|
||||
|
||||
if costManagementMetricsNode != nil && attributionsNode != nil {
|
||||
costManagementMetricsNode.Children = append(costManagementMetricsNode.Children, attributionsNode)
|
||||
}
|
||||
|
||||
costManagementLogsNode := root.FindByURL("/a/grafana-costmanagementui-app/logs")
|
||||
logVolumeExplorerNode := root.FindById("plugin-page-grafana-logvolumeexplorer-app")
|
||||
|
||||
if costManagementLogsNode != nil && logVolumeExplorerNode != nil {
|
||||
costManagementLogsNode.Children = append(costManagementLogsNode.Children, logVolumeExplorerNode)
|
||||
}
|
||||
|
||||
if len(adminNodeLinks) > 0 {
|
||||
orgAdminNode.Children = adminNodeLinks
|
||||
} else {
|
||||
root.RemoveSection(orgAdminNode)
|
||||
costManagementLogsNode := FindByURL(costManagementApp.Children, "/a/grafana-costmanagementui-app/logs")
|
||||
if costManagementLogsNode != nil {
|
||||
if logVolumeExplorerApp != nil {
|
||||
costManagementLogsNode.Children = append(costManagementLogsNode.Children, logVolumeExplorerApp)
|
||||
}
|
||||
}
|
||||
adminNodeLinks = append(adminNodeLinks, costManagementApp)
|
||||
}
|
||||
orgAdminNode.Children = adminNodeLinks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,60 +21,9 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
orgsAccessEvaluator := ac.EvalPermission(ac.ActionOrgsRead)
|
||||
authConfigUIAvailable := s.license.FeatureEnabled(social.SAMLProviderName) || s.cfg.LDAPAuthEnabled
|
||||
|
||||
// FIXME: If plugin admin is disabled or externally managed, server admins still need to access the page, this is why
|
||||
// while we don't have a permissions for listing plugins the legacy check has to stay as a default
|
||||
if pluginaccesscontrol.ReqCanAdminPlugins(s.cfg)(c) || hasAccess(pluginaccesscontrol.AdminAccessEvaluator) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Plugins",
|
||||
Id: "plugins",
|
||||
SubTitle: "Extend the Grafana experience with plugins",
|
||||
Icon: "plug",
|
||||
Url: s.cfg.AppSubURL + "/plugins",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Users", SubTitle: "Manage users in Grafana", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.TeamsAccessEvaluator) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Teams",
|
||||
Id: "teams",
|
||||
SubTitle: "Groups of users that have common dashboard and permission needs",
|
||||
Icon: "users-alt",
|
||||
Url: s.cfg.AppSubURL + "/org/teams",
|
||||
})
|
||||
}
|
||||
|
||||
if enableServiceAccount(s, c) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Service accounts",
|
||||
Id: "serviceaccounts",
|
||||
SubTitle: "Use service accounts to run automated workloads in Grafana",
|
||||
Icon: "gf-service-account",
|
||||
Url: s.cfg.AppSubURL + "/org/serviceaccounts",
|
||||
})
|
||||
}
|
||||
|
||||
disabled, err := s.apiKeyService.IsDisabled(ctx, c.SignedInUser.GetOrgID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasAccess(ac.ApiKeyAccessEvaluator) && !disabled {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "API keys",
|
||||
Id: "apikeys",
|
||||
SubTitle: "Manage and create API keys that are used to interact with Grafana HTTP APIs",
|
||||
Icon: "key-skeleton-alt",
|
||||
Url: s.cfg.AppSubURL + "/org/apikeys",
|
||||
})
|
||||
}
|
||||
|
||||
generalNodeLinks := []*navtree.NavLink{}
|
||||
if hasAccess(ac.OrgPreferencesAccessEvaluator) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Default preferences",
|
||||
Id: "org-settings",
|
||||
SubTitle: "Manage preferences across an organization",
|
||||
@@ -82,32 +31,18 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
Url: s.cfg.AppSubURL + "/org",
|
||||
})
|
||||
}
|
||||
|
||||
if authConfigUIAvailable && hasAccess(ssoutils.EvalAuthenticationSettings(s.cfg)) ||
|
||||
(hasAccess(ssoutils.OauthSettingsEvaluator(s.cfg)) && s.features.IsEnabled(ctx, featuremgmt.FlagSsoSettingsApi)) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Authentication",
|
||||
Id: "authentication",
|
||||
SubTitle: "Manage your auth settings and configure single sign-on",
|
||||
Icon: "signin",
|
||||
Url: s.cfg.AppSubURL + "/admin/authentication",
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Settings", SubTitle: "View the settings defined in your Grafana config", Id: "server-settings", Url: s.cfg.AppSubURL + "/admin/settings", Icon: "sliders-v-alt",
|
||||
})
|
||||
}
|
||||
|
||||
if hasGlobalAccess(orgsAccessEvaluator) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Organizations", SubTitle: "Isolated instances of Grafana running on the same server", Id: "global-orgs", Url: s.cfg.AppSubURL + "/admin/orgs", Icon: "building",
|
||||
})
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagFeatureToggleAdminPage) && hasAccess(ac.EvalPermission(ac.ActionFeatureManagementRead)) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Feature Toggles",
|
||||
SubTitle: "View and edit feature toggles",
|
||||
Id: "feature-toggles",
|
||||
@@ -115,9 +50,51 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
Icon: "toggle-on",
|
||||
})
|
||||
}
|
||||
if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(ctx, featuremgmt.FlagStorage) {
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Storage",
|
||||
Id: "storage",
|
||||
SubTitle: "Manage file storage",
|
||||
Icon: "cube",
|
||||
Url: s.cfg.AppSubURL + "/admin/storage",
|
||||
})
|
||||
}
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagOnPremToCloudMigrations) && c.SignedInUser.HasRole(org.RoleAdmin) {
|
||||
generalNodeLinks = append(generalNodeLinks, &navtree.NavLink{
|
||||
Text: "Migrate to Grafana Cloud",
|
||||
Id: "migrate-to-cloud",
|
||||
SubTitle: "Copy configuration from your self-managed installation to a cloud stack",
|
||||
Url: s.cfg.AppSubURL + "/admin/migrate-to-cloud",
|
||||
})
|
||||
}
|
||||
|
||||
generalNode := &navtree.NavLink{
|
||||
Text: "General",
|
||||
SubTitle: "Manage default preferences and settings across Grafana",
|
||||
Id: navtree.NavIDCfgGeneral,
|
||||
Url: "/admin/general",
|
||||
Icon: "shield",
|
||||
Children: generalNodeLinks,
|
||||
}
|
||||
|
||||
if len(generalNode.Children) > 0 {
|
||||
configNodes = append(configNodes, generalNode)
|
||||
}
|
||||
|
||||
pluginsNodeLinks := []*navtree.NavLink{}
|
||||
// FIXME: If plugin admin is disabled or externally managed, server admins still need to access the page, this is why
|
||||
// while we don't have a permissions for listing plugins the legacy check has to stay as a default
|
||||
if pluginaccesscontrol.ReqCanAdminPlugins(s.cfg)(c) || hasAccess(pluginaccesscontrol.AdminAccessEvaluator) {
|
||||
pluginsNodeLinks = append(pluginsNodeLinks, &navtree.NavLink{
|
||||
Text: "Plugins",
|
||||
Id: "plugins",
|
||||
SubTitle: "Extend the Grafana experience with plugins",
|
||||
Icon: "plug",
|
||||
Url: s.cfg.AppSubURL + "/plugins",
|
||||
})
|
||||
}
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagCorrelations) && hasAccess(correlations.ConfigurationPageAccess) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
pluginsNodeLinks = append(pluginsNodeLinks, &navtree.NavLink{
|
||||
Text: "Correlations",
|
||||
Icon: "gf-glue",
|
||||
SubTitle: "Add and configure correlations",
|
||||
@@ -126,25 +103,80 @@ func (s *ServiceImpl) getAdminNode(c *contextmodel.ReqContext) (*navtree.NavLink
|
||||
})
|
||||
}
|
||||
|
||||
if hasAccess(ac.EvalPermission(ac.ActionSettingsRead, ac.ScopeSettingsAll)) && s.features.IsEnabled(ctx, featuremgmt.FlagStorage) {
|
||||
storage := &navtree.NavLink{
|
||||
Text: "Storage",
|
||||
Id: "storage",
|
||||
SubTitle: "Manage file storage",
|
||||
Icon: "cube",
|
||||
Url: s.cfg.AppSubURL + "/admin/storage",
|
||||
}
|
||||
configNodes = append(configNodes, storage)
|
||||
pluginsNode := &navtree.NavLink{
|
||||
Text: "Plugins and data",
|
||||
SubTitle: "Install plugins and define the relationships between data",
|
||||
Id: navtree.NavIDCfgPlugins,
|
||||
Url: "/admin/plugins",
|
||||
Icon: "shield",
|
||||
Children: pluginsNodeLinks,
|
||||
}
|
||||
|
||||
if s.features.IsEnabled(ctx, featuremgmt.FlagOnPremToCloudMigrations) && c.SignedInUser.HasRole(org.RoleAdmin) {
|
||||
migrateToCloud := &navtree.NavLink{
|
||||
Text: "Migrate to Grafana Cloud",
|
||||
Id: "migrate-to-cloud",
|
||||
SubTitle: "Copy configuration from your self-managed installation to a cloud stack",
|
||||
Url: s.cfg.AppSubURL + "/admin/migrate-to-cloud",
|
||||
}
|
||||
configNodes = append(configNodes, migrateToCloud)
|
||||
if len(pluginsNode.Children) > 0 {
|
||||
configNodes = append(configNodes, pluginsNode)
|
||||
}
|
||||
|
||||
accessNodeLinks := []*navtree.NavLink{}
|
||||
if hasAccess(ac.EvalAny(ac.EvalPermission(ac.ActionOrgUsersRead), ac.EvalPermission(ac.ActionUsersRead, ac.ScopeGlobalUsersAll))) {
|
||||
accessNodeLinks = append(accessNodeLinks, &navtree.NavLink{
|
||||
Text: "Users", SubTitle: "Manage users in Grafana", Id: "global-users", Url: s.cfg.AppSubURL + "/admin/users", Icon: "user",
|
||||
})
|
||||
}
|
||||
if hasAccess(ac.TeamsAccessEvaluator) {
|
||||
accessNodeLinks = append(accessNodeLinks, &navtree.NavLink{
|
||||
Text: "Teams",
|
||||
Id: "teams",
|
||||
SubTitle: "Groups of users that have common dashboard and permission needs",
|
||||
Icon: "users-alt",
|
||||
Url: s.cfg.AppSubURL + "/org/teams",
|
||||
})
|
||||
}
|
||||
if enableServiceAccount(s, c) {
|
||||
accessNodeLinks = append(accessNodeLinks, &navtree.NavLink{
|
||||
Text: "Service accounts",
|
||||
Id: "serviceaccounts",
|
||||
SubTitle: "Use service accounts to run automated workloads in Grafana",
|
||||
Icon: "gf-service-account",
|
||||
Url: s.cfg.AppSubURL + "/org/serviceaccounts",
|
||||
})
|
||||
}
|
||||
disabled, err := s.apiKeyService.IsDisabled(ctx, c.SignedInUser.GetOrgID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if hasAccess(ac.ApiKeyAccessEvaluator) && !disabled {
|
||||
accessNodeLinks = append(accessNodeLinks, &navtree.NavLink{
|
||||
Text: "API keys",
|
||||
Id: "apikeys",
|
||||
SubTitle: "Manage and create API keys that are used to interact with Grafana HTTP APIs",
|
||||
Icon: "key-skeleton-alt",
|
||||
Url: s.cfg.AppSubURL + "/org/apikeys",
|
||||
})
|
||||
}
|
||||
|
||||
usersNode := &navtree.NavLink{
|
||||
Text: "Users and access",
|
||||
SubTitle: "Configure access for individual users, teams, and service accounts",
|
||||
Id: navtree.NavIDCfgAccess,
|
||||
Url: "/admin/access",
|
||||
Icon: "shield",
|
||||
Children: accessNodeLinks,
|
||||
}
|
||||
|
||||
if len(usersNode.Children) > 0 {
|
||||
configNodes = append(configNodes, usersNode)
|
||||
}
|
||||
|
||||
if authConfigUIAvailable && hasAccess(ssoutils.EvalAuthenticationSettings(s.cfg)) ||
|
||||
(hasAccess(ssoutils.OauthSettingsEvaluator(s.cfg)) && s.features.IsEnabled(ctx, featuremgmt.FlagSsoSettingsApi)) {
|
||||
configNodes = append(configNodes, &navtree.NavLink{
|
||||
Text: "Authentication",
|
||||
Id: "authentication",
|
||||
SubTitle: "Manage your auth settings and configure single sign-on",
|
||||
Icon: "signin",
|
||||
IsSection: true,
|
||||
Url: s.cfg.AppSubURL + "/admin/authentication",
|
||||
})
|
||||
}
|
||||
|
||||
configNode := &navtree.NavLink{
|
||||
|
||||
@@ -295,7 +295,7 @@ func (s *ServiceImpl) readNavigationSettings() {
|
||||
"grafana-incident-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 2, Text: "Incidents"},
|
||||
"grafana-ml-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 3, Text: "Machine Learning"},
|
||||
"grafana-slo-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 4},
|
||||
"grafana-cloud-link-app": {SectionID: navtree.NavIDCfg},
|
||||
"grafana-cloud-link-app": {SectionID: navtree.NavIDCfgPlugins, SortWeight: 3},
|
||||
"grafana-costmanagementui-app": {SectionID: navtree.NavIDCfg, Text: "Cost management"},
|
||||
"grafana-adaptive-metrics-app": {SectionID: navtree.NavIDCfg, Text: "Adaptive Metrics"},
|
||||
"grafana-attributions-app": {SectionID: navtree.NavIDCfg, Text: "Attributions"},
|
||||
@@ -307,7 +307,7 @@ func (s *ServiceImpl) readNavigationSettings() {
|
||||
}
|
||||
|
||||
s.navigationAppPathConfig = map[string]NavigationAppConfig{
|
||||
"/a/grafana-auth-app": {SectionID: navtree.NavIDCfg, SortWeight: 7},
|
||||
"/a/grafana-auth-app": {SectionID: navtree.NavIDCfgAccess, SortWeight: 2},
|
||||
}
|
||||
|
||||
appSections := s.cfg.Raw.Section("navigation.app_sections")
|
||||
|
||||
@@ -151,7 +151,7 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere
|
||||
|
||||
orgAdminNode, err := s.getAdminNode(c)
|
||||
|
||||
if orgAdminNode != nil {
|
||||
if orgAdminNode != nil && len(orgAdminNode.Children) > 0 {
|
||||
treeRoot.AddSection(orgAdminNode)
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
package store
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
)
|
||||
|
||||
// Really just spitballing here :) this should hook into a system that can give better display info
|
||||
func GetUserIDString(user *user.SignedInUser) string {
|
||||
// TODO: should we check IsDisabled?
|
||||
// TODO: could we use the NamespacedID.ID() as prefix instead of manually
|
||||
// setting "anon", "key", etc.?
|
||||
// TODO: the default unauthenticated user is not anonymous and would be
|
||||
// returned as `sys:0:` here. We may want to do something special in that
|
||||
// case
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
if user.IsAnonymous {
|
||||
return "anon"
|
||||
}
|
||||
if user.ApiKeyID > 0 {
|
||||
return fmt.Sprintf("key:%d", user.UserID)
|
||||
}
|
||||
if user.IsRealUser() {
|
||||
return fmt.Sprintf("user:%d:%s", user.UserID, user.Login)
|
||||
}
|
||||
return fmt.Sprintf("sys:%d:%s", user.UserID, user.Login)
|
||||
}
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
"text/template"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/services/store"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity/db"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate"
|
||||
)
|
||||
@@ -33,7 +32,7 @@ func getCurrentUser(ctx context.Context) (string, error) {
|
||||
return "", fmt.Errorf("%w: %w", ErrUserNotFoundInContext, err)
|
||||
}
|
||||
|
||||
return store.GetUserIDString(user), nil
|
||||
return user.GetUID().String(), nil
|
||||
}
|
||||
|
||||
// ptrOr returns the first non-nil pointer in the list or a new non-nil pointer.
|
||||
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/services/store"
|
||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||
)
|
||||
|
||||
@@ -122,7 +121,7 @@ func TestIntegrationEntityServer(t *testing.T) {
|
||||
testCtx := createTestContext(t)
|
||||
ctx := appcontext.WithUser(testCtx.ctx, testCtx.user)
|
||||
|
||||
fakeUser := store.GetUserIDString(testCtx.user)
|
||||
fakeUser := testCtx.user.GetUID().String()
|
||||
firstVersion := int64(0)
|
||||
group := "test.grafana.app"
|
||||
resource := "jsonobjs"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { config, reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Drawer, Dropdown, Icon, Menu, MenuItem } from '@grafana/ui';
|
||||
import {
|
||||
getNewDashboardPhrase,
|
||||
@@ -50,11 +50,11 @@ export default function CreateNewButton({ parentFolder, canCreateDashboard, canC
|
||||
label={getNewDashboardPhrase()}
|
||||
onClick={() =>
|
||||
reportInteraction('grafana_menu_item_clicked', {
|
||||
url: addFolderUidToUrl('/dashboard/new', parentFolder?.uid),
|
||||
url: buildUrl('/dashboard/new', parentFolder?.uid),
|
||||
from: location.pathname,
|
||||
})
|
||||
}
|
||||
url={addFolderUidToUrl('/dashboard/new', parentFolder?.uid)}
|
||||
url={buildUrl('/dashboard/new', parentFolder?.uid)}
|
||||
/>
|
||||
)}
|
||||
{canCreateFolder && <MenuItem onClick={() => setShowNewFolderDrawer(true)} label={getNewFolderPhrase()} />}
|
||||
@@ -63,11 +63,11 @@ export default function CreateNewButton({ parentFolder, canCreateDashboard, canC
|
||||
label={getImportPhrase()}
|
||||
onClick={() =>
|
||||
reportInteraction('grafana_menu_item_clicked', {
|
||||
url: addFolderUidToUrl('/dashboard/import', parentFolder?.uid),
|
||||
url: buildUrl('/dashboard/import', parentFolder?.uid),
|
||||
from: location.pathname,
|
||||
})
|
||||
}
|
||||
url={addFolderUidToUrl('/dashboard/import', parentFolder?.uid)}
|
||||
url={buildUrl('/dashboard/import', parentFolder?.uid)}
|
||||
/>
|
||||
)}
|
||||
</Menu>
|
||||
@@ -101,6 +101,7 @@ export default function CreateNewButton({ parentFolder, canCreateDashboard, canC
|
||||
* @param folderUid folder id
|
||||
* @returns url with paramter if folder is present
|
||||
*/
|
||||
function addFolderUidToUrl(url: string, folderUid: string | undefined) {
|
||||
return folderUid ? url + '?folderUid=' + folderUid : url;
|
||||
function buildUrl(url: string, folderUid: string | undefined) {
|
||||
const baseUrl = folderUid ? url + '?folderUid=' + folderUid : url;
|
||||
return config.appSubUrl ? config.appSubUrl + baseUrl : baseUrl;
|
||||
}
|
||||
|
||||
@@ -152,6 +152,14 @@ describe('PanelOptions', () => {
|
||||
|
||||
expect(screen.queryByLabelText(overrideRuleTooltipDescription)).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Can delete rule', async () => {
|
||||
const {} = setup();
|
||||
|
||||
await userEvent.click(screen.getByLabelText('Remove override'));
|
||||
|
||||
expect(screen.queryByLabelText(overrideRuleTooltipDescription)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('gets library panel options when the editing a library panel', async () => {
|
||||
|
||||
@@ -62,7 +62,7 @@ export const PanelOptions = React.memo<Props>(({ vizManager, searchQuery, listMo
|
||||
data?.series ?? [],
|
||||
searchQuery,
|
||||
(newConfig) => {
|
||||
panel.onFieldConfigChange(newConfig);
|
||||
panel.onFieldConfigChange(newConfig, true);
|
||||
}
|
||||
),
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import React from 'react';
|
||||
import { finalize, from, Subscription } from 'rxjs';
|
||||
|
||||
import { Scope } from '@grafana/data';
|
||||
import {
|
||||
@@ -33,6 +34,8 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
||||
|
||||
protected _urlSync = new SceneObjectUrlSyncConfig(this, { keys: ['scopes'] });
|
||||
|
||||
private nodesFetchingSub: Subscription | undefined;
|
||||
|
||||
get scopesParent(): ScopesScene {
|
||||
return sceneGraph.getAncestor(this, ScopesScene);
|
||||
}
|
||||
@@ -61,6 +64,10 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
||||
|
||||
this.addActivationHandler(() => {
|
||||
this.fetchBaseNodes();
|
||||
|
||||
return () => {
|
||||
this.nodesFetchingSub?.unsubscribe();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -80,6 +87,8 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
||||
}
|
||||
|
||||
public async updateNode(path: string[], isExpanded: boolean, query: string) {
|
||||
this.nodesFetchingSub?.unsubscribe();
|
||||
|
||||
let nodes = { ...this.state.nodes };
|
||||
let currentLevel: NodesMap = nodes;
|
||||
|
||||
@@ -90,16 +99,30 @@ export class ScopesFiltersScene extends SceneObjectBase<ScopesFiltersSceneState>
|
||||
const name = path[path.length - 1];
|
||||
const currentNode = currentLevel[name];
|
||||
|
||||
if (isExpanded || currentNode.query !== query) {
|
||||
this.setState({ loadingNodeName: name });
|
||||
|
||||
currentNode.nodes = await fetchNodes(name, query);
|
||||
}
|
||||
const isDifferentQuery = currentNode.query !== query;
|
||||
|
||||
currentNode.isExpanded = isExpanded;
|
||||
currentNode.query = query;
|
||||
|
||||
this.setState({ nodes, loadingNodeName: undefined });
|
||||
|
||||
if (isExpanded || isDifferentQuery) {
|
||||
this.setState({ loadingNodeName: name });
|
||||
|
||||
this.nodesFetchingSub = from(fetchNodes(name, query))
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.setState({ loadingNodeName: undefined });
|
||||
})
|
||||
)
|
||||
.subscribe((childNodes) => {
|
||||
currentNode.nodes = childNodes;
|
||||
|
||||
this.setState({ nodes });
|
||||
|
||||
this.nodesFetchingSub?.unsubscribe();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public toggleNodeSelect(path: string[]) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { debounce } from 'lodash';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import Skeleton from 'react-loading-skeleton';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Checkbox, Icon, IconButton, Input, useStyles2 } from '@grafana/ui';
|
||||
@@ -33,89 +34,86 @@ export function ScopesTreeLevel({
|
||||
const node = nodes[nodeId];
|
||||
const childNodes = node.nodes;
|
||||
const childNodesArr = Object.values(childNodes);
|
||||
const isNodeLoading = loadingNodeName === nodeId;
|
||||
|
||||
const anyChildExpanded = childNodesArr.some(({ isExpanded }) => isExpanded);
|
||||
const anyChildSelected = childNodesArr.some(({ linkId }) => linkId && scopeNames.includes(linkId!));
|
||||
|
||||
const onQueryUpdate = useMemo(() => debounce(onNodeUpdate, 500), [onNodeUpdate]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showQuery && !anyChildExpanded && (
|
||||
<Input
|
||||
prefix={<Icon name="filter" />}
|
||||
className={styles.searchInput}
|
||||
disabled={!!loadingNodeName}
|
||||
placeholder={t('scopes.tree.search', 'Filter')}
|
||||
defaultValue={node.query}
|
||||
data-testid={`scopes-tree-${nodeId}-search`}
|
||||
onChange={debounce((evt) => {
|
||||
onNodeUpdate(nodePath, true, evt.target.value);
|
||||
}, 500)}
|
||||
onInput={(evt) => onQueryUpdate(nodePath, true, evt.currentTarget.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div role="tree">
|
||||
{childNodesArr.map((childNode) => {
|
||||
const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!);
|
||||
{isNodeLoading && <Skeleton count={5} className={styles.loader} />}
|
||||
|
||||
if (anyChildExpanded && !childNode.isExpanded && !isSelected) {
|
||||
return null;
|
||||
}
|
||||
{!isNodeLoading &&
|
||||
childNodesArr.map((childNode) => {
|
||||
const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!);
|
||||
|
||||
const childNodePath = [...nodePath, childNode.name];
|
||||
if (anyChildExpanded && !childNode.isExpanded && !isSelected) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div key={childNode.name} role="treeitem" aria-selected={childNode.isExpanded}>
|
||||
<div className={styles.itemTitle}>
|
||||
{childNode.isSelectable && !childNode.isExpanded ? (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={!!loadingNodeName || (anyChildSelected && !isSelected && node.disableMultiSelect)}
|
||||
data-testid={`scopes-tree-${childNode.name}-checkbox`}
|
||||
onChange={() => {
|
||||
onNodeSelectToggle(childNodePath);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
const childNodePath = [...nodePath, childNode.name];
|
||||
|
||||
{childNode.isExpandable && (
|
||||
<IconButton
|
||||
disabled={(anyChildSelected && !childNode.isExpanded) || !!loadingNodeName}
|
||||
name={
|
||||
!childNode.isExpanded
|
||||
? 'angle-right'
|
||||
: loadingNodeName === childNode.name
|
||||
? 'spinner'
|
||||
: 'angle-down'
|
||||
}
|
||||
aria-label={
|
||||
childNode.isExpanded ? t('scopes.tree.collapse', 'Collapse') : t('scopes.tree.expand', 'Expand')
|
||||
}
|
||||
data-testid={`scopes-tree-${childNode.name}-expand`}
|
||||
onClick={() => {
|
||||
onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
return (
|
||||
<div key={childNode.name} role="treeitem" aria-selected={childNode.isExpanded}>
|
||||
<div className={styles.itemTitle}>
|
||||
{childNode.isSelectable && !childNode.isExpanded ? (
|
||||
<Checkbox
|
||||
checked={isSelected}
|
||||
disabled={anyChildSelected && !isSelected && node.disableMultiSelect}
|
||||
data-testid={`scopes-tree-${childNode.name}-checkbox`}
|
||||
onChange={() => {
|
||||
onNodeSelectToggle(childNodePath);
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
<span data-testid={`scopes-tree-${childNode.name}-title`}>{childNode.title}</span>
|
||||
{childNode.isExpandable && (
|
||||
<IconButton
|
||||
disabled={anyChildSelected && !childNode.isExpanded}
|
||||
name={!childNode.isExpanded ? 'angle-right' : 'angle-down'}
|
||||
aria-label={
|
||||
childNode.isExpanded ? t('scopes.tree.collapse', 'Collapse') : t('scopes.tree.expand', 'Expand')
|
||||
}
|
||||
data-testid={`scopes-tree-${childNode.name}-expand`}
|
||||
onClick={() => {
|
||||
onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<span data-testid={`scopes-tree-${childNode.name}-title`}>{childNode.title}</span>
|
||||
</div>
|
||||
|
||||
<div className={styles.itemChildren}>
|
||||
{childNode.isExpanded && (
|
||||
<ScopesTreeLevel
|
||||
showQuery={showQuery}
|
||||
nodes={node.nodes}
|
||||
nodePath={childNodePath}
|
||||
loadingNodeName={loadingNodeName}
|
||||
scopeNames={scopeNames}
|
||||
onNodeUpdate={onNodeUpdate}
|
||||
onNodeSelectToggle={onNodeSelectToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.itemChildren}>
|
||||
{childNode.isExpanded && (
|
||||
<ScopesTreeLevel
|
||||
showQuery={showQuery}
|
||||
nodes={node.nodes}
|
||||
nodePath={childNodePath}
|
||||
loadingNodeName={loadingNodeName}
|
||||
scopeNames={scopeNames}
|
||||
onNodeUpdate={onNodeUpdate}
|
||||
onNodeSelectToggle={onNodeSelectToggle}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
@@ -126,6 +124,9 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
searchInput: css({
|
||||
margin: theme.spacing(1, 0),
|
||||
}),
|
||||
loader: css({
|
||||
margin: theme.spacing(0.5, 0),
|
||||
}),
|
||||
itemTitle: css({
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
|
||||
@@ -4,7 +4,7 @@ import React, { HTMLAttributes } from 'react';
|
||||
import { DataSourceSettings as DataSourceSettingsType, GrafanaTheme2 } from '@grafana/data';
|
||||
import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
|
||||
import { TestingStatus, config } from '@grafana/runtime';
|
||||
import { AlertVariant, Alert, useTheme2, Link } from '@grafana/ui';
|
||||
import { AlertVariant, Alert, useTheme2, Link, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { contextSrv } from '../../../core/core';
|
||||
import { trackCreateDashboardClicked } from '../tracking';
|
||||
@@ -25,16 +25,16 @@ interface AlertMessageProps extends HTMLAttributes<HTMLDivElement> {
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, hasTitle: boolean) => {
|
||||
return {
|
||||
content: css`
|
||||
color: ${theme.colors.text.secondary};
|
||||
padding-top: ${hasTitle ? theme.spacing(1) : 0};
|
||||
max-height: 50vh;
|
||||
overflow-y: auto;
|
||||
`,
|
||||
disabled: css`
|
||||
pointer-events: none;
|
||||
color: ${theme.colors.text.secondary};
|
||||
`,
|
||||
content: css({
|
||||
color: theme.colors.text.secondary,
|
||||
paddingTop: hasTitle ? theme.spacing(1) : 0,
|
||||
maxHeight: '50vh',
|
||||
overflowY: 'auto',
|
||||
}),
|
||||
disabled: css({
|
||||
pointerEvents: 'none',
|
||||
color: theme.colors.text.secondary,
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -95,10 +95,11 @@ export function DataSourceTestingStatus({ testingStatus, exploreUrl, dataSource
|
||||
path: location.pathname,
|
||||
});
|
||||
};
|
||||
const styles = useStyles2(getTestingStatusStyles);
|
||||
|
||||
if (message) {
|
||||
return (
|
||||
<div className="gf-form-group p-t-2">
|
||||
<div className={cx('gf-form-group', styles.container)}>
|
||||
<Alert severity={severity} title={message} data-testid={e2eSelectors.pages.DataSource.alert}>
|
||||
{testingStatus?.details && (
|
||||
<>
|
||||
@@ -123,3 +124,9 @@ export function DataSourceTestingStatus({ testingStatus, exploreUrl, dataSource
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const getTestingStatusStyles = (theme: GrafanaTheme2) => ({
|
||||
container: css({
|
||||
paddingTop: theme.spacing(3),
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -53,7 +53,7 @@ describe('buildCategories', () => {
|
||||
it('should add enterprise phantom plugins', () => {
|
||||
const enterprisePluginsCategory = categories[3];
|
||||
expect(enterprisePluginsCategory.title).toBe('Enterprise plugins');
|
||||
expect(enterprisePluginsCategory.plugins.length).toBe(20);
|
||||
expect(enterprisePluginsCategory.plugins.length).toBe(21);
|
||||
expect(enterprisePluginsCategory.plugins[0].name).toBe('AppDynamics');
|
||||
expect(enterprisePluginsCategory.plugins[enterprisePluginsCategory.plugins.length - 1].name).toBe('Wavefront');
|
||||
});
|
||||
|
||||
@@ -209,6 +209,12 @@ function getEnterprisePhantomPlugins(): DataSourcePluginMeta[] {
|
||||
name: 'PagerDuty',
|
||||
imgUrl: 'public/img/plugins/pagerduty.svg',
|
||||
}),
|
||||
getPhantomPlugin({
|
||||
id: 'grafana-catchpoint-datasource',
|
||||
description: 'Catchpoint datasource',
|
||||
name: 'Catchpoint',
|
||||
imgUrl: 'public/img/plugins/catchpoint.svg',
|
||||
}),
|
||||
getPhantomPlugin({
|
||||
id: 'grafana-azurecosmosdb-datasource',
|
||||
description: 'Azure CosmosDB datasource',
|
||||
|
||||
@@ -27,7 +27,7 @@ export const InspectStatsTable = ({ timeZone, name, stats }: InspectStatsTablePr
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className="section-heading">{name}</div>
|
||||
<div className={styles.heading}>{name}</div>
|
||||
<table className="filter-table width-30">
|
||||
<tbody>
|
||||
{stats.map((stat, index) => {
|
||||
@@ -57,10 +57,14 @@ function formatStat(stat: QueryResultMetaStat, timeZone: TimeZone, theme: Grafan
|
||||
}
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
wrapper: css`
|
||||
padding-bottom: ${theme.spacing(2)};
|
||||
`,
|
||||
cell: css`
|
||||
text-align: right;
|
||||
`,
|
||||
heading: css({
|
||||
fontSize: theme.typography.body.fontSize,
|
||||
marginBottom: theme.spacing(1),
|
||||
}),
|
||||
wrapper: css({
|
||||
paddingBottom: theme.spacing(2),
|
||||
}),
|
||||
cell: css({
|
||||
textAlign: 'right',
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -18,7 +18,7 @@ export const InspectStatsTraceIdsTable = ({ name, traceIds }: Props) => {
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className="section-heading">{name}</div>
|
||||
<div className={styles.heading}>{name}</div>
|
||||
<table className="filter-table width-30">
|
||||
<tbody>
|
||||
{traceIds.map((traceId, index) => {
|
||||
@@ -35,10 +35,14 @@ export const InspectStatsTraceIdsTable = ({ name, traceIds }: Props) => {
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
wrapper: css`
|
||||
padding-bottom: ${theme.spacing(2)};
|
||||
`,
|
||||
cell: css`
|
||||
text-align: right;
|
||||
`,
|
||||
heading: css({
|
||||
fontSize: theme.typography.body.fontSize,
|
||||
marginBottom: theme.spacing(1),
|
||||
}),
|
||||
wrapper: css({
|
||||
paddingBottom: theme.spacing(2),
|
||||
}),
|
||||
cell: css({
|
||||
textAlign: 'right',
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -228,7 +228,7 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div aria-label={selectors.components.PanelInspector.Query.content}>
|
||||
<h3 className="section-heading">Query inspector</h3>
|
||||
<h3 className={styles.heading}>Query inspector</h3>
|
||||
<p className="small muted">
|
||||
<Trans i18nKey="inspector.query.description">
|
||||
Query inspector allows you to view raw request and response. To collect this data Grafana needs to issue a
|
||||
|
||||
@@ -11,62 +11,66 @@ export const getPanelInspectorStyles = stylesFactory(() => {
|
||||
|
||||
export const getPanelInspectorStyles2 = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
wrap: css`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
flex: 1 1 0;
|
||||
min-height: 0;
|
||||
`,
|
||||
toolbar: css`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-grow: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: ${theme.v1.spacing.sm};
|
||||
`,
|
||||
toolbarItem: css`
|
||||
margin-left: ${theme.v1.spacing.md};
|
||||
`,
|
||||
content: css`
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
`,
|
||||
editor: css`
|
||||
font-family: monospace;
|
||||
height: 100%;
|
||||
flex-grow: 1;
|
||||
`,
|
||||
viewer: css`
|
||||
overflow: scroll;
|
||||
`,
|
||||
dataFrameSelect: css`
|
||||
flex-grow: 2;
|
||||
`,
|
||||
leftActions: css`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
heading: css({
|
||||
fontSize: theme.typography.body.fontSize,
|
||||
marginBottom: theme.spacing(1),
|
||||
}),
|
||||
wrap: css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
flex: '1 1 0',
|
||||
minHeight: 0,
|
||||
}),
|
||||
toolbar: css({
|
||||
display: 'flex',
|
||||
width: '100%',
|
||||
flexGrow: 0,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-end',
|
||||
marginBottom: theme.v1.spacing.sm,
|
||||
}),
|
||||
toolbarItem: css({
|
||||
marginLeft: theme.v1.spacing.md,
|
||||
}),
|
||||
content: css({
|
||||
flexGrow: 1,
|
||||
height: '100%',
|
||||
}),
|
||||
editor: css({
|
||||
fontFamily: 'monospace',
|
||||
height: '100%',
|
||||
flexGrow: 1,
|
||||
}),
|
||||
viewer: css({
|
||||
overflow: 'scroll',
|
||||
}),
|
||||
dataFrameSelect: css({
|
||||
flexGrow: 2,
|
||||
}),
|
||||
leftActions: css({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
|
||||
max-width: 85%;
|
||||
@media (max-width: 1345px) {
|
||||
max-width: 75%;
|
||||
}
|
||||
`,
|
||||
options: css`
|
||||
padding-top: ${theme.v1.spacing.sm};
|
||||
`,
|
||||
dataDisplayOptions: css`
|
||||
flex-grow: 1;
|
||||
min-width: 300px;
|
||||
margin-right: ${theme.v1.spacing.sm};
|
||||
`,
|
||||
selects: css`
|
||||
display: flex;
|
||||
> * {
|
||||
margin-right: ${theme.v1.spacing.sm};
|
||||
}
|
||||
`,
|
||||
maxWidth: '85%',
|
||||
'@media (max-width: 1345px)': {
|
||||
maxWidth: '75%',
|
||||
},
|
||||
}),
|
||||
options: css({
|
||||
paddingTop: theme.v1.spacing.sm,
|
||||
}),
|
||||
dataDisplayOptions: css({
|
||||
flexGrow: 1,
|
||||
minWidth: '300px',
|
||||
marginRight: theme.v1.spacing.sm,
|
||||
}),
|
||||
selects: css({
|
||||
display: 'flex',
|
||||
'> *': {
|
||||
marginRight: theme.v1.spacing.sm,
|
||||
},
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { css, cx } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { Button, Field, Input } from '@grafana/ui';
|
||||
import { Button, Field, Input, useStyles2 } from '@grafana/ui';
|
||||
import { Form } from 'app/core/components/Form/Form';
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { getConfig } from 'app/core/config';
|
||||
@@ -37,6 +39,7 @@ export const SignupInvitedPage = ({ match }: Props) => {
|
||||
const [initFormModel, setInitFormModel] = useState<FormModel>();
|
||||
const [greeting, setGreeting] = useState<string>();
|
||||
const [invitedBy, setInvitedBy] = useState<string>();
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
useAsync(async () => {
|
||||
const invite = await getBackendSrv().get(`/api/user/invite/${code}`);
|
||||
@@ -65,7 +68,7 @@ export const SignupInvitedPage = ({ match }: Props) => {
|
||||
<Page.Contents>
|
||||
<h3 className="page-sub-heading">Hello {greeting || 'there'}.</h3>
|
||||
|
||||
<div className="modal-tagline p-b-2">
|
||||
<div className={cx('modal-tagline', styles.tagline)}>
|
||||
<em>{invitedBy || 'Someone'}</em> has invited you to join Grafana and the organization{' '}
|
||||
<span className="highlight-word">{contextSrv.user.orgName}</span>
|
||||
<br />
|
||||
@@ -109,4 +112,10 @@ export const SignupInvitedPage = ({ match }: Props) => {
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
tagline: css({
|
||||
paddingBottom: theme.spacing(3),
|
||||
}),
|
||||
});
|
||||
|
||||
export default SignupInvitedPage;
|
||||
|
||||
+30
-10
@@ -5,6 +5,7 @@ import { EditorField, EditorRow, InlineSelect } from '@grafana/experimental';
|
||||
import { ConfirmModal, Input, RadioButtonGroup, Space } from '@grafana/ui';
|
||||
|
||||
import { CloudWatchDatasource } from '../../../datasource';
|
||||
import { DEFAULT_METRICS_QUERY } from '../../../defaultQueries';
|
||||
import useMigratedMetricsQuery from '../../../migrations/useMigratedMetricsQuery';
|
||||
import {
|
||||
CloudWatchJsonData,
|
||||
@@ -39,13 +40,13 @@ const editorModes = [
|
||||
export const MetricsQueryEditor = (props: Props) => {
|
||||
const { query, datasource, extraHeaderElementLeft, extraHeaderElementRight, onChange } = props;
|
||||
const [showConfirm, setShowConfirm] = useState(false);
|
||||
const [sqlCodeEditorIsDirty, setSQLCodeEditorIsDirty] = useState(false);
|
||||
const [codeEditorIsDirty, setCodeEditorIsDirty] = useState(false);
|
||||
const migratedQuery = useMigratedMetricsQuery(query, props.onChange);
|
||||
|
||||
const onEditorModeChange = useCallback(
|
||||
(newMetricEditorMode: MetricEditorMode) => {
|
||||
if (
|
||||
sqlCodeEditorIsDirty &&
|
||||
codeEditorIsDirty &&
|
||||
query.metricQueryType === MetricQueryType.Query &&
|
||||
query.metricEditorMode === MetricEditorMode.Code
|
||||
) {
|
||||
@@ -54,7 +55,7 @@ export const MetricsQueryEditor = (props: Props) => {
|
||||
}
|
||||
onChange({ ...query, metricEditorMode: newMetricEditorMode });
|
||||
},
|
||||
[setShowConfirm, onChange, sqlCodeEditorIsDirty, query]
|
||||
[setShowConfirm, onChange, codeEditorIsDirty, query]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -64,6 +65,14 @@ export const MetricsQueryEditor = (props: Props) => {
|
||||
value={metricEditorModes.find((m) => m.value === query.metricQueryType)}
|
||||
options={metricEditorModes}
|
||||
onChange={({ value }) => {
|
||||
if (
|
||||
codeEditorIsDirty &&
|
||||
query.metricQueryType === MetricQueryType.Search &&
|
||||
query.metricEditorMode === MetricEditorMode.Builder
|
||||
) {
|
||||
setShowConfirm(true);
|
||||
return;
|
||||
}
|
||||
onChange({ ...query, metricQueryType: value });
|
||||
}}
|
||||
/>
|
||||
@@ -80,13 +89,19 @@ export const MetricsQueryEditor = (props: Props) => {
|
||||
<ConfirmModal
|
||||
isOpen={showConfirm}
|
||||
title="Are you sure?"
|
||||
body="You will lose manual changes done to the query if you go back to the visual builder."
|
||||
body="You will lose changes made to the query if you change to Metric Query Builder mode."
|
||||
confirmText="Yes, I am sure."
|
||||
dismissText="No, continue editing the query manually."
|
||||
dismissText="No, continue editing the query."
|
||||
icon="exclamation-triangle"
|
||||
onConfirm={() => {
|
||||
setShowConfirm(false);
|
||||
onChange({ ...query, metricEditorMode: MetricEditorMode.Builder });
|
||||
setCodeEditorIsDirty(false);
|
||||
onChange({
|
||||
...query,
|
||||
...DEFAULT_METRICS_QUERY,
|
||||
metricQueryType: MetricQueryType.Query,
|
||||
metricEditorMode: MetricEditorMode.Builder,
|
||||
});
|
||||
}}
|
||||
onDismiss={() => setShowConfirm(false)}
|
||||
/>
|
||||
@@ -99,7 +114,7 @@ export const MetricsQueryEditor = (props: Props) => {
|
||||
};
|
||||
}, [
|
||||
query,
|
||||
sqlCodeEditorIsDirty,
|
||||
codeEditorIsDirty,
|
||||
datasource,
|
||||
onChange,
|
||||
extraHeaderElementLeft,
|
||||
@@ -119,7 +134,12 @@ export const MetricsQueryEditor = (props: Props) => {
|
||||
{...props}
|
||||
refId={query.refId}
|
||||
metricStat={query}
|
||||
onChange={(metricStat: MetricStat) => props.onChange({ ...query, ...metricStat })}
|
||||
onChange={(metricStat: MetricStat) => {
|
||||
if (!codeEditorIsDirty) {
|
||||
setCodeEditorIsDirty(true);
|
||||
}
|
||||
props.onChange({ ...query, ...metricStat });
|
||||
}}
|
||||
></MetricStatEditor>
|
||||
)}
|
||||
{query.metricEditorMode === MetricEditorMode.Code && (
|
||||
@@ -138,8 +158,8 @@ export const MetricsQueryEditor = (props: Props) => {
|
||||
region={query.region}
|
||||
sql={query.sqlExpression ?? ''}
|
||||
onChange={(sqlExpression) => {
|
||||
if (!sqlCodeEditorIsDirty) {
|
||||
setSQLCodeEditorIsDirty(true);
|
||||
if (!codeEditorIsDirty) {
|
||||
setCodeEditorIsDirty(true);
|
||||
}
|
||||
props.onChange({ ...migratedQuery, sqlExpression });
|
||||
}}
|
||||
|
||||
@@ -21,6 +21,7 @@ export const DEFAULT_METRICS_QUERY: Omit<CloudWatchMetricsQuery, 'refId'> = {
|
||||
period: '',
|
||||
metricQueryType: MetricQueryType.Search,
|
||||
metricEditorMode: MetricEditorMode.Builder,
|
||||
sql: undefined,
|
||||
sqlExpression: '',
|
||||
matchExact: true,
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
import { QueryEditorProps } from '@grafana/data';
|
||||
import { Box, InlineField, Input, TagsInput } from '@grafana/ui';
|
||||
import { GrafanaTheme2, QueryEditorProps } from '@grafana/data';
|
||||
import { Box, InlineField, Input, TagsInput, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { GraphiteDatasource } from '../datasource';
|
||||
import { GraphiteQuery, GraphiteOptions } from '../types';
|
||||
@@ -32,6 +33,7 @@ export const AnnotationEditor = (props: QueryEditorProps<GraphiteDatasource, Gra
|
||||
setTags(tagsInput);
|
||||
updateValue('tags', tagsInput);
|
||||
};
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
return (
|
||||
<Box marginBottom={5}>
|
||||
@@ -44,7 +46,7 @@ export const AnnotationEditor = (props: QueryEditorProps<GraphiteDatasource, Gra
|
||||
/>
|
||||
</InlineField>
|
||||
|
||||
<h5 className="section-heading">Or</h5>
|
||||
<h5 className={styles.heading}>Or</h5>
|
||||
|
||||
<InlineField label="Graphite events tags" labelWidth={24}>
|
||||
<TagsInput id="tags-input" width={50} tags={tags} onChange={onTagsChange} placeholder="Example: event_tag" />
|
||||
@@ -52,3 +54,10 @@ export const AnnotationEditor = (props: QueryEditorProps<GraphiteDatasource, Gra
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
heading: css({
|
||||
fontSize: theme.typography.body.fontSize,
|
||||
marginBottom: theme.spacing(1),
|
||||
}),
|
||||
});
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 38 KiB |
@@ -1,6 +1,8 @@
|
||||
// these styles are only used by angular components/pages
|
||||
// once angular is disabled, this file can be deleted
|
||||
|
||||
@use 'sass:map';
|
||||
|
||||
.edit-tab-content {
|
||||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
@@ -1792,3 +1794,41 @@ $easing: cubic-bezier(0, 0, 0.265, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
font-size: $font-size-md;
|
||||
margin-bottom: $space-sm;
|
||||
}
|
||||
|
||||
@each $prop, $abbrev in (margin: m, padding: p) {
|
||||
@each $size, $lengths in $spacers {
|
||||
$length-x: map.get($lengths, x);
|
||||
$length-y: map.get($lengths, y);
|
||||
|
||||
.#{$abbrev}-a-#{$size} {
|
||||
#{$prop}: $length-y $length-x !important;
|
||||
} // a = All sides
|
||||
.#{$abbrev}-t-#{$size} {
|
||||
#{$prop}-top: $length-y !important;
|
||||
}
|
||||
.#{$abbrev}-r-#{$size} {
|
||||
#{$prop}-right: $length-x !important;
|
||||
}
|
||||
.#{$abbrev}-b-#{$size} {
|
||||
#{$prop}-bottom: $length-y !important;
|
||||
}
|
||||
.#{$abbrev}-l-#{$size} {
|
||||
#{$prop}-left: $length-x !important;
|
||||
}
|
||||
|
||||
// Axes
|
||||
.#{$abbrev}-x-#{$size} {
|
||||
#{$prop}-right: $length-x !important;
|
||||
#{$prop}-left: $length-x !important;
|
||||
}
|
||||
.#{$abbrev}-y-#{$size} {
|
||||
#{$prop}-top: $length-y !important;
|
||||
#{$prop}-bottom: $length-y !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,12 +13,10 @@
|
||||
@import 'base/type';
|
||||
@import 'base/forms';
|
||||
@import 'base/grid';
|
||||
@import 'base/fonts';
|
||||
@import 'base/code';
|
||||
@import 'base/font_awesome';
|
||||
|
||||
// UTILS
|
||||
@import 'utils/utils';
|
||||
@import 'utils/spacings';
|
||||
@import 'utils/widths';
|
||||
|
||||
// COMPONENTS
|
||||
@@ -33,7 +31,6 @@
|
||||
@import 'components/dropdown';
|
||||
@import 'components/infobox';
|
||||
@import 'components/query_editor';
|
||||
@import 'components/tabbed_view';
|
||||
@import 'components/query_part';
|
||||
@import 'components/json_explorer';
|
||||
@import 'components/dashboard_grid';
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
//
|
||||
// Code (inline and blocK)
|
||||
// --------------------------------------------------
|
||||
|
||||
// Inline and block code styles
|
||||
code,
|
||||
pre {
|
||||
@include font-family-monospace();
|
||||
font-size: $font-size-base - 2;
|
||||
background-color: $code-tag-bg;
|
||||
color: $text-color;
|
||||
border: 1px solid $code-tag-border;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
// Inline code
|
||||
code {
|
||||
color: $text-color;
|
||||
white-space: nowrap;
|
||||
padding: 2px 5px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
code.code--small {
|
||||
font-size: $font-size-xs;
|
||||
padding: $space-xxs;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
// Blocks of code
|
||||
pre {
|
||||
display: block;
|
||||
margin: 0 0 $line-height-base;
|
||||
line-height: $line-height-base;
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
background-color: $code-tag-bg;
|
||||
padding: 10px;
|
||||
|
||||
&.pre--no-style {
|
||||
background: transparent;
|
||||
border: none;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
// Make prettyprint styles more spaced out for readability
|
||||
&.prettyprint {
|
||||
margin-bottom: $line-height-base;
|
||||
}
|
||||
|
||||
// Account for some code outputs that place code tags in pre tags
|
||||
code {
|
||||
padding: 0;
|
||||
color: inherit;
|
||||
white-space: pre;
|
||||
white-space: pre-wrap;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
@import 'font_awesome';
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url(#{$font-file-path}/roboto/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
|
||||
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/* latin */
|
||||
@font-face {
|
||||
font-family: 'Roboto Mono';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url(#{$font-file-path}/roboto/L0xTDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vrtSM1J-gEPT5Ese6hmHSh0mQ.woff2)
|
||||
format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC,
|
||||
U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
/*
|
||||
To add new variations/version of Inter, download from https://rsms.me/inter/ and add the
|
||||
web font files to the public/fonts/inter folder. Do not download the fonts from Google Fonts
|
||||
or somewhere else because they don't support the features we require (like tabular numerals).
|
||||
|
||||
If adding additional weights, consider switching to the InterVariable variable font as combined
|
||||
it may take less space than multiple static weights.
|
||||
*/
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('#{$font-file-path}/inter/Inter-Regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Inter';
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
font-display: swap;
|
||||
src: url('#{$font-file-path}/inter/Inter-Medium.woff2') format('woff2');
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
.section-heading {
|
||||
font-size: $font-size-md;
|
||||
margin-bottom: $space-sm;
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
@use 'sass:map';
|
||||
// Margin and Padding
|
||||
|
||||
.m-x-auto {
|
||||
margin-right: auto !important;
|
||||
margin-left: auto !important;
|
||||
}
|
||||
|
||||
@each $prop, $abbrev in (margin: m, padding: p) {
|
||||
@each $size, $lengths in $spacers {
|
||||
$length-x: map.get($lengths, x);
|
||||
$length-y: map.get($lengths, y);
|
||||
|
||||
.#{$abbrev}-a-#{$size} {
|
||||
#{$prop}: $length-y $length-x !important;
|
||||
} // a = All sides
|
||||
.#{$abbrev}-t-#{$size} {
|
||||
#{$prop}-top: $length-y !important;
|
||||
}
|
||||
.#{$abbrev}-r-#{$size} {
|
||||
#{$prop}-right: $length-x !important;
|
||||
}
|
||||
.#{$abbrev}-b-#{$size} {
|
||||
#{$prop}-bottom: $length-y !important;
|
||||
}
|
||||
.#{$abbrev}-l-#{$size} {
|
||||
#{$prop}-left: $length-x !important;
|
||||
}
|
||||
|
||||
// Axes
|
||||
.#{$abbrev}-x-#{$size} {
|
||||
#{$prop}-right: $length-x !important;
|
||||
#{$prop}-left: $length-x !important;
|
||||
}
|
||||
.#{$abbrev}-y-#{$size} {
|
||||
#{$prop}-top: $length-y !important;
|
||||
#{$prop}-bottom: $length-y !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Positioning
|
||||
|
||||
.pos-f-t {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
z-index: $zindex-navbar-fixed;
|
||||
}
|
||||
@@ -42,6 +42,7 @@ load(
|
||||
)
|
||||
|
||||
ver_mode = "release"
|
||||
semver_regex = r"^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
|
||||
|
||||
def retrieve_npm_packages_step():
|
||||
return {
|
||||
@@ -59,6 +60,34 @@ def retrieve_npm_packages_step():
|
||||
"commands": ["./bin/build artifacts npm retrieve --tag ${DRONE_TAG}"],
|
||||
}
|
||||
|
||||
def release_pr_step(depends_on = []):
|
||||
return {
|
||||
"name": "create-release-pr",
|
||||
"image": images["curl"],
|
||||
"depends_on": depends_on,
|
||||
"environment": {
|
||||
"GITHUB_TOKEN": from_secret("github_token"),
|
||||
"GH_CLI_URL": "https://github.com/cli/cli/releases/download/v2.50.0/gh_2.50.0_linux_amd64.tar.gz",
|
||||
},
|
||||
"commands": [
|
||||
"apk add perl",
|
||||
"v_target=`echo $${{TAG}} | perl -pe 's/{}/v\\1.\\2.x/'`".format(semver_regex),
|
||||
"default_target=`if [[ -n $$LATEST ]]; then echo 'main'; else echo $$v_target; fi`",
|
||||
"backport=`if [[ -n $$LATEST ]]; then echo $$v_target; fi`",
|
||||
# Install gh CLI
|
||||
"curl -L $${GH_CLI_URL} | tar -xz --strip-components=1 -C /usr",
|
||||
# Run the release-pr workflow
|
||||
"gh workflow run " +
|
||||
"-f dry_run=$${DRY_RUN} " +
|
||||
"-f version=$${TAG} " +
|
||||
# If the submitter has set a target branch, then use that, otherwise use the default
|
||||
"-f target=$${TARGET:-$default_target} " +
|
||||
# If the submitter has set a backport branch, then use that, otherwise use the default
|
||||
"-f backport=$${BACKPORT:-$default_backport} " +
|
||||
"--repo=grafana/grafana release-pr.yml",
|
||||
],
|
||||
}
|
||||
|
||||
def release_npm_packages_step():
|
||||
return {
|
||||
"name": "release-npm-packages",
|
||||
@@ -136,9 +165,20 @@ def publish_artifacts_pipelines(mode):
|
||||
publish_artifacts_step(),
|
||||
publish_static_assets_step(),
|
||||
publish_storybook_step(),
|
||||
release_pr_step(depends_on = ["publish-artifacts", "publish-static-assets"]),
|
||||
]
|
||||
|
||||
return [
|
||||
pipeline(
|
||||
name = "create-release-pr",
|
||||
trigger = {
|
||||
"event": ["promote"],
|
||||
"target": "release-pr",
|
||||
},
|
||||
steps = [
|
||||
release_pr_step(),
|
||||
],
|
||||
),
|
||||
pipeline(
|
||||
name = "publish-artifacts-{}".format(mode),
|
||||
trigger = trigger,
|
||||
|
||||
Reference in New Issue
Block a user