From 423c03912b72b98aa84687ca22c245852f737df5 Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Wed, 12 Jun 2024 16:18:09 +0300 Subject: [PATCH 01/10] Scopes: Remove disabled flag on nodes search input (#89041) --- .../scene/Scopes/ScopesFiltersScene.tsx | 33 ++++- .../scene/Scopes/ScopesTreeLevel.tsx | 123 +++++++++--------- 2 files changed, 90 insertions(+), 66 deletions(-) diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx index 3c048a0ae47..84c7ba88f83 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx @@ -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 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 this.addActivationHandler(() => { this.fetchBaseNodes(); + + return () => { + this.nodesFetchingSub?.unsubscribe(); + }; }); } @@ -80,6 +87,8 @@ export class ScopesFiltersScene extends SceneObjectBase } 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 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[]) { diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx index 51b7d882e02..1749f5790f0 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx @@ -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 && ( } 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)} /> )}
- {childNodesArr.map((childNode) => { - const isSelected = childNode.isSelectable && scopeNames.includes(childNode.linkId!); + {isNodeLoading && } - 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 ( -
-
- {childNode.isSelectable && !childNode.isExpanded ? ( - { - onNodeSelectToggle(childNodePath); - }} - /> - ) : null} + const childNodePath = [...nodePath, childNode.name]; - {childNode.isExpandable && ( - { - onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query); - }} - /> - )} + return ( +
+
+ {childNode.isSelectable && !childNode.isExpanded ? ( + { + onNodeSelectToggle(childNodePath); + }} + /> + ) : null} - {childNode.title} + {childNode.isExpandable && ( + { + onNodeUpdate(childNodePath, !childNode.isExpanded, childNode.query); + }} + /> + )} + + {childNode.title} +
+ +
+ {childNode.isExpanded && ( + + )} +
- -
- {childNode.isExpanded && ( - - )} -
-
- ); - })} + ); + })}
); @@ -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', From 9877aa70399b54fd728bab406eba2bf37960ef0f Mon Sep 17 00:00:00 2001 From: Isabella Siu Date: Wed, 12 Jun 2024 09:31:05 -0400 Subject: [PATCH 02/10] Cloudwatch: Metrics Query Builder should clear old query (#88950) --- .../MetricsQueryEditor/MetricsQueryEditor.tsx | 40 ++++++++++++++----- .../datasource/cloudwatch/defaultQueries.ts | 1 + 2 files changed, 31 insertions(+), 10 deletions(-) diff --git a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx index 884f9d373fd..e4a7404989f 100644 --- a/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx +++ b/public/app/plugins/datasource/cloudwatch/components/QueryEditor/MetricsQueryEditor/MetricsQueryEditor.tsx @@ -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) => { { 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 }); + }} > )} {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 }); }} diff --git a/public/app/plugins/datasource/cloudwatch/defaultQueries.ts b/public/app/plugins/datasource/cloudwatch/defaultQueries.ts index 62b72054e99..2f09d8db926 100644 --- a/public/app/plugins/datasource/cloudwatch/defaultQueries.ts +++ b/public/app/plugins/datasource/cloudwatch/defaultQueries.ts @@ -21,6 +21,7 @@ export const DEFAULT_METRICS_QUERY: Omit = { period: '', metricQueryType: MetricQueryType.Search, metricEditorMode: MetricEditorMode.Builder, + sql: undefined, sqlExpression: '', matchExact: true, }; From 13d00e09ab79ba1262d6148e18636e15515bb941 Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 12 Jun 2024 14:46:20 +0100 Subject: [PATCH 03/10] Chore: Migrate some more SCSS to emotion (#89038) * migrate section-heading * move fonts to global styles * migrate code styles to emotion globals * migrate spacings styles and leave some in angular * padding should be 2 here not 1 --- .betterer.results | 29 +---- .../src/components/ConfirmModal.tsx | 13 +- .../DataSourceHttpSettings.tsx | 58 +++++---- .../src/themes/GlobalStyles/GlobalStyles.tsx | 4 + .../src/themes/GlobalStyles/code.ts | 40 ++++++ .../src/themes/GlobalStyles/fonts.ts | 58 +++++++++ .../components/DataSourceTestingStatus.tsx | 31 +++-- .../features/inspector/InspectStatsTable.tsx | 18 +-- .../inspector/InspectStatsTraceIdsTable.tsx | 18 +-- .../app/features/inspector/QueryInspector.tsx | 2 +- public/app/features/inspector/styles.ts | 116 +++++++++--------- public/app/features/invites/SignupInvited.tsx | 13 +- .../graphite/components/AnnotationsEditor.tsx | 15 ++- public/sass/_angular.scss | 40 ++++++ public/sass/_grafana.scss | 5 +- public/sass/base/_code.scss | 62 ---------- public/sass/base/_fonts.scss | 49 -------- public/sass/components/_tabbed_view.scss | 4 - public/sass/utils/_spacings.scss | 50 -------- 19 files changed, 316 insertions(+), 309 deletions(-) create mode 100644 packages/grafana-ui/src/themes/GlobalStyles/code.ts create mode 100644 packages/grafana-ui/src/themes/GlobalStyles/fonts.ts delete mode 100644 public/sass/base/_code.scss delete mode 100644 public/sass/base/_fonts.scss delete mode 100644 public/sass/components/_tabbed_view.scss delete mode 100644 public/sass/utils/_spacings.scss diff --git a/.betterer.results b/.betterer.results index f1637bf05fe..c5e1da0bb83 100644 --- a/.betterer.results +++ b/.betterer.results @@ -3630,13 +3630,11 @@ exports[`better eslint`] = { [0, 0, 0, "No untranslated strings. Wrap text with ", "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 ", "0"], + [0, 0, 0, "No untranslated strings. Wrap text with ", "1"], [0, 0, 0, "No untranslated strings. Wrap text with ", "2"], [0, 0, 0, "No untranslated strings. Wrap text with ", "3"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "4"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "5"], - [0, 0, 0, "No untranslated strings. Wrap text with ", "6"] + [0, 0, 0, "No untranslated strings. Wrap text with ", "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 ", "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 ", "0"] ], diff --git a/packages/grafana-sql/src/components/ConfirmModal.tsx b/packages/grafana-sql/src/components/ConfirmModal.tsx index 6ee12a6ce7f..0bf1e1b67d5 100644 --- a/packages/grafana-sql/src/components/ConfirmModal.tsx +++ b/packages/grafana-sql/src/components/ConfirmModal.tsx @@ -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(null); + const styles = useStyles2(getStyles); // Moved from grafana/ui useEffect(() => { @@ -24,7 +27,7 @@ export function ConfirmModal({ isOpen, onCancel, onDiscard, onCopy }: ConfirmMod title={
- Warning + Warning
} onDismiss={onCancel} @@ -49,3 +52,9 @@ export function ConfirmModal({ isOpen, onCancel, onDiscard, onCopy }: ConfirmMod ); } + +const getStyles = (theme: GrafanaTheme2) => ({ + titleText: css({ + paddingLeft: theme.spacing(2), + }), +}); diff --git a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx index 998c614738e..d9d9d8df1fb 100644 --- a/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx +++ b/packages/grafana-ui/src/components/DataSourceSettings/DataSourceHttpSettings.tsx @@ -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 = () => ( -
-

- Access mode controls how requests to the data source will be handled. - -  Server - {' '} - should be the preferred way if nothing else is stated. -

-
Server access mode (Default):
-

- 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. -

-
Browser access mode:
-

- 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. -

-
-); +const HttpAccessHelp = () => { + const styles = useStyles2(getAccessStyles); + return ( +
+

+ Access mode controls how requests to the data source will be handled. + +  Server + {' '} + should be the preferred way if nothing else is stated. +

+
Server access mode (Default):
+

+ 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. +

+
Browser access mode:
+

+ 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. +

+
+ ); +}; + +const getAccessStyles = (theme: GrafanaTheme2) => ({ + infoBox: css({ + marginTop: theme.spacing(3), + }), +}); const LABEL_WIDTH = 26; diff --git a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx index 791adfa5391..377318481c3 100644 --- a/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx +++ b/packages/grafana-ui/src/themes/GlobalStyles/GlobalStyles.tsx @@ -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 ( { 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 ( -
+
{testingStatus?.details && ( <> @@ -123,3 +124,9 @@ export function DataSourceTestingStatus({ testingStatus, exploreUrl, dataSource return null; } + +const getTestingStatusStyles = (theme: GrafanaTheme2) => ({ + container: css({ + paddingTop: theme.spacing(3), + }), +}); diff --git a/public/app/features/inspector/InspectStatsTable.tsx b/public/app/features/inspector/InspectStatsTable.tsx index 51ff54afb15..a014e31e588 100644 --- a/public/app/features/inspector/InspectStatsTable.tsx +++ b/public/app/features/inspector/InspectStatsTable.tsx @@ -27,7 +27,7 @@ export const InspectStatsTable = ({ timeZone, name, stats }: InspectStatsTablePr return (
-
{name}
+
{name}
{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', + }), }); diff --git a/public/app/features/inspector/InspectStatsTraceIdsTable.tsx b/public/app/features/inspector/InspectStatsTraceIdsTable.tsx index 6c0855deb86..b94491db68e 100644 --- a/public/app/features/inspector/InspectStatsTraceIdsTable.tsx +++ b/public/app/features/inspector/InspectStatsTraceIdsTable.tsx @@ -18,7 +18,7 @@ export const InspectStatsTraceIdsTable = ({ name, traceIds }: Props) => { return (
-
{name}
+
{name}
{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', + }), }); diff --git a/public/app/features/inspector/QueryInspector.tsx b/public/app/features/inspector/QueryInspector.tsx index fb1e0cd8238..3650114ea7e 100644 --- a/public/app/features/inspector/QueryInspector.tsx +++ b/public/app/features/inspector/QueryInspector.tsx @@ -228,7 +228,7 @@ export class QueryInspector extends PureComponent { return (
-

Query inspector

+

Query inspector

Query inspector allows you to view raw request and response. To collect this data Grafana needs to issue a diff --git a/public/app/features/inspector/styles.ts b/public/app/features/inspector/styles.ts index 888793288ba..86646950382 100644 --- a/public/app/features/inspector/styles.ts +++ b/public/app/features/inspector/styles.ts @@ -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, + }, + }), }; }; diff --git a/public/app/features/invites/SignupInvited.tsx b/public/app/features/invites/SignupInvited.tsx index bea01970007..96c7cddeafb 100644 --- a/public/app/features/invites/SignupInvited.tsx +++ b/public/app/features/invites/SignupInvited.tsx @@ -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(); const [greeting, setGreeting] = useState(); const [invitedBy, setInvitedBy] = useState(); + const styles = useStyles2(getStyles); useAsync(async () => { const invite = await getBackendSrv().get(`/api/user/invite/${code}`); @@ -65,7 +68,7 @@ export const SignupInvitedPage = ({ match }: Props) => {

Hello {greeting || 'there'}.

-
+
{invitedBy || 'Someone'} has invited you to join Grafana and the organization{' '} {contextSrv.user.orgName}
@@ -109,4 +112,10 @@ export const SignupInvitedPage = ({ match }: Props) => { ); }; +const getStyles = (theme: GrafanaTheme2) => ({ + tagline: css({ + paddingBottom: theme.spacing(3), + }), +}); + export default SignupInvitedPage; diff --git a/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx b/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx index 0eddf2e3e8e..e27c5afce7a 100644 --- a/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx +++ b/public/app/plugins/datasource/graphite/components/AnnotationsEditor.tsx @@ -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 @@ -44,7 +46,7 @@ export const AnnotationEditor = (props: QueryEditorProps -
Or
+
Or
@@ -52,3 +54,10 @@ export const AnnotationEditor = (props: QueryEditorProps ); }; + +const getStyles = (theme: GrafanaTheme2) => ({ + heading: css({ + fontSize: theme.typography.body.fontSize, + marginBottom: theme.spacing(1), + }), +}); diff --git a/public/sass/_angular.scss b/public/sass/_angular.scss index c63debe1113..d5b8abf0bcd 100644 --- a/public/sass/_angular.scss +++ b/public/sass/_angular.scss @@ -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; + } + } +} diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index b59b8903c9a..f19901dc014 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -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'; diff --git a/public/sass/base/_code.scss b/public/sass/base/_code.scss deleted file mode 100644 index 92a34ebbede..00000000000 --- a/public/sass/base/_code.scss +++ /dev/null @@ -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; - } -} diff --git a/public/sass/base/_fonts.scss b/public/sass/base/_fonts.scss deleted file mode 100644 index f38a1bdd829..00000000000 --- a/public/sass/base/_fonts.scss +++ /dev/null @@ -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'); -} diff --git a/public/sass/components/_tabbed_view.scss b/public/sass/components/_tabbed_view.scss deleted file mode 100644 index 48b90642ed7..00000000000 --- a/public/sass/components/_tabbed_view.scss +++ /dev/null @@ -1,4 +0,0 @@ -.section-heading { - font-size: $font-size-md; - margin-bottom: $space-sm; -} diff --git a/public/sass/utils/_spacings.scss b/public/sass/utils/_spacings.scss deleted file mode 100644 index 5648e379077..00000000000 --- a/public/sass/utils/_spacings.scss +++ /dev/null @@ -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; -} From c6d807e0150cce61e2e94a9b65957d1399c9143f Mon Sep 17 00:00:00 2001 From: Kevin Minehart <5140827+kminehart@users.noreply.github.com> Date: Wed, 12 Jun 2024 08:48:50 -0500 Subject: [PATCH 04/10] CI: Trigger release pr workflow when a release is completed (#89062) --- .drone.yml | 61 ++++++++++++++++++++++++++++++- .github/workflows/release-pr.yml | 2 +- scripts/drone/events/release.star | 40 ++++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/.drone.yml b/.drone.yml index 6b412b6e277..658e2d4adde 100644 --- a/.drone.yml +++ b/.drone.yml @@ -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 ... diff --git a/.github/workflows/release-pr.yml b/.github/workflows/release-pr.yml index e788e1a19dd..6373612eca6 100644 --- a/.github/workflows/release-pr.yml +++ b/.github/workflows/release-pr.yml @@ -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 }} diff --git a/scripts/drone/events/release.star b/scripts/drone/events/release.star index cdf8c9883c0..404dafa79ca 100644 --- a/scripts/drone/events/release.star +++ b/scripts/drone/events/release.star @@ -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, From 5bb10d84e08c70d461acec3d3e584135b20d539f Mon Sep 17 00:00:00 2001 From: Taewoo K Date: Wed, 12 Jun 2024 10:05:07 -0400 Subject: [PATCH 05/10] add catchpoint to plugin list (#87438) * add catchpoint to plugin list * fix format * bump the plugins number --- .../introduction/grafana-enterprise.md | 1 + .../datasources/state/buildCategories.test.ts | 2 +- .../datasources/state/buildCategories.ts | 6 + public/img/plugins/catchpoint.svg | 1691 +++++++++++++++++ 4 files changed, 1699 insertions(+), 1 deletion(-) create mode 100644 public/img/plugins/catchpoint.svg diff --git a/docs/sources/introduction/grafana-enterprise.md b/docs/sources/introduction/grafana-enterprise.md index ab145ce8468..88b0f02d246 100644 --- a/docs/sources/introduction/grafana-enterprise.md +++ b/docs/sources/introduction/grafana-enterprise.md @@ -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) diff --git a/public/app/features/datasources/state/buildCategories.test.ts b/public/app/features/datasources/state/buildCategories.test.ts index 934d8623b5c..ee2579d559b 100644 --- a/public/app/features/datasources/state/buildCategories.test.ts +++ b/public/app/features/datasources/state/buildCategories.test.ts @@ -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'); }); diff --git a/public/app/features/datasources/state/buildCategories.ts b/public/app/features/datasources/state/buildCategories.ts index 5b65e65119c..27672bd201e 100644 --- a/public/app/features/datasources/state/buildCategories.ts +++ b/public/app/features/datasources/state/buildCategories.ts @@ -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', diff --git a/public/img/plugins/catchpoint.svg b/public/img/plugins/catchpoint.svg new file mode 100644 index 00000000000..fe626a17a05 --- /dev/null +++ b/public/img/plugins/catchpoint.svg @@ -0,0 +1,1691 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 822644714a5c6e8caf5fe94ddcc642ef0a36c2bb Mon Sep 17 00:00:00 2001 From: Ashley Harrison Date: Wed, 12 Jun 2024 16:45:13 +0100 Subject: [PATCH 06/10] Navigation: Remove `ApplyAdminIA` logic (#89113) make admin IA more normal --- pkg/api/index.go | 2 +- pkg/services/licensing/oss.go | 11 +- pkg/services/navtree/models.go | 135 ++++-------- pkg/services/navtree/navtreeimpl/admin.go | 208 +++++++++++-------- pkg/services/navtree/navtreeimpl/applinks.go | 4 +- pkg/services/navtree/navtreeimpl/navtree.go | 2 +- 6 files changed, 166 insertions(+), 196 deletions(-) diff --git a/pkg/api/index.go b/pkg/api/index.go index 7868ddce08f..23ce1e540e0 100644 --- a/pkg/api/index.go +++ b/pkg/api/index.go @@ -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() diff --git a/pkg/services/licensing/oss.go b/pkg/services/licensing/oss.go index c01caac6bab..d14b8798363 100644 --- a/pkg/services/licensing/oss.go +++ b/pkg/services/licensing/oss.go @@ -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, }) } }) diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go index c9de55bc095..b3c22e7fdb5 100644 --- a/pkg/services/navtree/models.go +++ b/pkg/services/navtree/models.go @@ -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 } } diff --git a/pkg/services/navtree/navtreeimpl/admin.go b/pkg/services/navtree/navtreeimpl/admin.go index 5dad84f681a..a81542fbb18 100644 --- a/pkg/services/navtree/navtreeimpl/admin.go +++ b/pkg/services/navtree/navtreeimpl/admin.go @@ -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{ diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go index 5030d033380..06ab60eb520 100644 --- a/pkg/services/navtree/navtreeimpl/applinks.go +++ b/pkg/services/navtree/navtreeimpl/applinks.go @@ -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") diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go index 62d0a07e8c4..4526465de90 100644 --- a/pkg/services/navtree/navtreeimpl/navtree.go +++ b/pkg/services/navtree/navtreeimpl/navtree.go @@ -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 From c58d09fd81dc9d5e25b6170ed89ee829242514c8 Mon Sep 17 00:00:00 2001 From: Josh Hunt Date: Wed, 12 Jun 2024 17:22:00 +0100 Subject: [PATCH 07/10] BrowseDashboards: Prepend subpath to New Browse Dashboard actions (#89109) --- .../components/CreateNewButton.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/public/app/features/browse-dashboards/components/CreateNewButton.tsx b/public/app/features/browse-dashboards/components/CreateNewButton.tsx index c4d20755a72..97af7819b7b 100644 --- a/public/app/features/browse-dashboards/components/CreateNewButton.tsx +++ b/public/app/features/browse-dashboards/components/CreateNewButton.tsx @@ -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 && 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)} /> )} @@ -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; } From 636910e57e5828d0c04cbc24edcc720a5c096779 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Wed, 12 Jun 2024 18:35:10 +0200 Subject: [PATCH 08/10] DashboardScene: Fixes issue removing override rule (#89124) --- .../dashboard-scene/panel-edit/PanelOptions.test.tsx | 8 ++++++++ .../features/dashboard-scene/panel-edit/PanelOptions.tsx | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx index 3bcc582600e..197ca1dd304 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptions.test.tsx @@ -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 () => { diff --git a/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx b/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx index 590715a653a..11fae6fbb83 100644 --- a/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx +++ b/public/app/features/dashboard-scene/panel-edit/PanelOptions.tsx @@ -62,7 +62,7 @@ export const PanelOptions = React.memo(({ vizManager, searchQuery, listMo data?.series ?? [], searchQuery, (newConfig) => { - panel.onFieldConfigChange(newConfig); + panel.onFieldConfigChange(newConfig, true); } ), // eslint-disable-next-line react-hooks/exhaustive-deps From 1abaa825c623bef95cf74ce5d4f802d50a6bffaf Mon Sep 17 00:00:00 2001 From: Marcus Efraimsson Date: Wed, 12 Jun 2024 18:36:31 +0200 Subject: [PATCH 09/10] Tracing: Only enable traces to profiles for api servers for now (#89126) --- pkg/infra/tracing/tracing.go | 4 +++- pkg/infra/tracing/tracing_config.go | 2 ++ pkg/services/apiserver/standalone/options/tracing.go | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/infra/tracing/tracing.go b/pkg/infra/tracing/tracing.go index 3f09c215a77..f8f1db557f7 100644 --- a/pkg/infra/tracing/tracing.go +++ b/pkg/infra/tracing/tracing.go @@ -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 diff --git a/pkg/infra/tracing/tracing_config.go b/pkg/infra/tracing/tracing_config.go index 14c878a2351..36d5ca6f908 100644 --- a/pkg/infra/tracing/tracing_config.go +++ b/pkg/infra/tracing/tracing_config.go @@ -21,6 +21,8 @@ type TracingConfig struct { ServiceName string ServiceVersion string + + ProfilingIntegration bool } func ProvideTracingConfig(cfg *setting.Cfg) (*TracingConfig, error) { diff --git a/pkg/services/apiserver/standalone/options/tracing.go b/pkg/services/apiserver/standalone/options/tracing.go index 1f9f77048a8..2e276a9f85e 100644 --- a/pkg/services/apiserver/standalone/options/tracing.go +++ b/pkg/services/apiserver/standalone/options/tracing.go @@ -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 { From ed400f0bbfe4369c29db14503af6ca3123ce96ab Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Wed, 12 Jun 2024 19:39:34 +0300 Subject: [PATCH 10/10] EntityStore: Use standard user identifier rather than custom version (#89080) user uid string --- pkg/services/store/auth.go | 30 ------------------- pkg/services/store/entity/sqlstash/utils.go | 3 +- .../entity/tests/server_integration_test.go | 3 +- 3 files changed, 2 insertions(+), 34 deletions(-) delete mode 100644 pkg/services/store/auth.go diff --git a/pkg/services/store/auth.go b/pkg/services/store/auth.go deleted file mode 100644 index af6fff1551b..00000000000 --- a/pkg/services/store/auth.go +++ /dev/null @@ -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) -} diff --git a/pkg/services/store/entity/sqlstash/utils.go b/pkg/services/store/entity/sqlstash/utils.go index 84768b3f06d..ade92f40374 100644 --- a/pkg/services/store/entity/sqlstash/utils.go +++ b/pkg/services/store/entity/sqlstash/utils.go @@ -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. diff --git a/pkg/services/store/entity/tests/server_integration_test.go b/pkg/services/store/entity/tests/server_integration_test.go index 44eec13b401..6afe43b3173 100644 --- a/pkg/services/store/entity/tests/server_integration_test.go +++ b/pkg/services/store/entity/tests/server_integration_test.go @@ -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"