diff --git a/e2e/various-suite/loki-table-explore-to-dash.spec.ts b/e2e/various-suite/loki-table-explore-to-dash.spec.ts
index 1477f6f0165..f4867855ca3 100644
--- a/e2e/various-suite/loki-table-explore-to-dash.spec.ts
+++ b/e2e/various-suite/loki-table-explore-to-dash.spec.ts
@@ -175,6 +175,7 @@ describe('Loki Query Editor', () => {
// One row with two cells
cy.get('[role="cell"]').should('have.length', 2);
+ cy.contains('label', 'targetLabelName').scrollIntoView();
cy.contains('label', 'targetLabelName').should('be.visible');
cy.contains('label', 'targetLabelName').click();
cy.contains('label', 'targetLabelName').within(() => {
diff --git a/packages/grafana-data/src/types/icon.ts b/packages/grafana-data/src/types/icon.ts
index 357cf07e46e..b1092680181 100644
--- a/packages/grafana-data/src/types/icon.ts
+++ b/packages/grafana-data/src/types/icon.ts
@@ -93,6 +93,7 @@ export const availableIconsIndex = {
'document-layout-left': true,
'download-alt': true,
draggabledots: true,
+ drilldown: true,
edit: true,
'ellipsis-v': true,
enter: true,
diff --git a/packages/grafana-data/src/types/navModel.ts b/packages/grafana-data/src/types/navModel.ts
index 066d879df3d..f9ebb23fc07 100644
--- a/packages/grafana-data/src/types/navModel.ts
+++ b/packages/grafana-data/src/types/navModel.ts
@@ -14,6 +14,7 @@ export interface NavLinkDTO {
sortWeight?: number;
hideFromTabs?: boolean;
roundIcon?: boolean;
+ isNew?: boolean;
/**
* This is true for some sections that have no children (but is still a section)
**/
diff --git a/packages/grafana-icons/svg/drilldown.svg b/packages/grafana-icons/svg/drilldown.svg
new file mode 100644
index 00000000000..27f627ce19f
--- /dev/null
+++ b/packages/grafana-icons/svg/drilldown.svg
@@ -0,0 +1,6 @@
+
diff --git a/pkg/api/accesscontrol.go b/pkg/api/accesscontrol.go
index f74e32f1b50..1855531c0ce 100644
--- a/pkg/api/accesscontrol.go
+++ b/pkg/api/accesscontrol.go
@@ -60,12 +60,15 @@ func (hs *HTTPServer) declareFixedRoles() error {
Role: ac.RoleDTO{
Name: "fixed:datasources:explorer",
DisplayName: "Explorer",
- Description: "Enable the Explore feature. Data source permissions still apply; you can only query data sources for which you have query permissions.",
+ Description: "Enable the Explore and Drilldown features. Data source permissions still apply; you can only query data sources for which you have query permissions.",
Group: "Data sources",
Permissions: []ac.Permission{
{
Action: ac.ActionDatasourcesExplore,
},
+ {
+ Action: ac.ActionDatasourcesDrilldown,
+ },
},
},
Grants: []string{string(org.RoleEditor)},
diff --git a/pkg/api/api.go b/pkg/api/api.go
index acc8a29c21f..5b085c89d00 100644
--- a/pkg/api/api.go
+++ b/pkg/api/api.go
@@ -183,6 +183,7 @@ func (hs *HTTPServer) registerRoutes() {
}
r.Get("/explore", authorize(ac.EvalPermission(ac.ActionDatasourcesExplore)), hs.Index)
+ r.Get("/drilldown", authorize(ac.EvalPermission(ac.ActionDatasourcesDrilldown)), hs.Index)
r.Get("/playlists/", reqSignedIn, hs.Index)
r.Get("/playlists/*", reqSignedIn, hs.Index)
diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go
index dc12171eaa5..4c15e3c5e56 100644
--- a/pkg/services/accesscontrol/models.go
+++ b/pkg/services/accesscontrol/models.go
@@ -386,7 +386,8 @@ const (
ActionSettingsWrite = "settings:write"
// Datasources actions
- ActionDatasourcesExplore = "datasources:explore"
+ ActionDatasourcesExplore = "datasources:explore"
+ ActionDatasourcesDrilldown = "datasources:drilldown"
// Global Scopes
ScopeGlobalUsersAll = "global.users:*"
diff --git a/pkg/services/navtree/models.go b/pkg/services/navtree/models.go
index bf324abc076..97d7ab1112b 100644
--- a/pkg/services/navtree/models.go
+++ b/pkg/services/navtree/models.go
@@ -16,6 +16,7 @@ const (
WeightSavedItems
WeightDashboard
WeightExplore
+ WeightDrilldown
WeightAlerting
WeightAlertsAndIncidents
WeightTestingAndSynthetics
@@ -37,6 +38,7 @@ const (
NavIDRoot = "root"
NavIDDashboards = "dashboards/browse"
NavIDExplore = "explore"
+ NavIDDrilldown = "drilldown"
NavIDCfg = "cfg" // NavIDCfg is the id for org configuration navigation node
NavIDAlertsAndIncidents = "alerts-and-incidents"
NavIDTestingAndSynthetics = "testing-and-synthetics"
@@ -70,6 +72,7 @@ type NavLink struct {
EmptyMessageId string `json:"emptyMessageId,omitempty"`
PluginID string `json:"pluginId,omitempty"` // (Optional) The ID of the plugin that registered nav link (e.g. as a standalone plugin page)
IsCreateAction bool `json:"isCreateAction,omitempty"`
+ IsNew bool `json:"isNew,omitempty"` // (Optional) Adds "New!" badge to the nav link and expands it by default
Keywords []string `json:"keywords,omitempty"`
ParentItem *NavLink `json:"parentItem,omitempty"` // (Optional) The parent item of the nav link
}
diff --git a/pkg/services/navtree/navtreeimpl/applinks.go b/pkg/services/navtree/navtreeimpl/applinks.go
index 1adc19e0d57..5c6b88756d3 100644
--- a/pkg/services/navtree/navtreeimpl/applinks.go
+++ b/pkg/services/navtree/navtreeimpl/applinks.go
@@ -286,9 +286,9 @@ func (s *ServiceImpl) readNavigationSettings() {
"grafana-k8s-app": {SectionID: navtree.NavIDInfrastructure, SortWeight: 1, Text: "Kubernetes"},
"grafana-dbo11y-app": {SectionID: navtree.NavIDInfrastructure, SortWeight: 2, Text: "Databases"},
"grafana-app-observability-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightApplication, Text: "Application", Icon: "graph-bar"},
- "grafana-lokiexplore-app": {SectionID: navtree.NavIDExplore, SortWeight: 2, Text: "Logs"},
- "grafana-exploretraces-app": {SectionID: navtree.NavIDExplore, SortWeight: 3, Text: "Traces"},
- "grafana-pyroscope-app": {SectionID: navtree.NavIDExplore, SortWeight: 4, Text: "Profiles"},
+ "grafana-lokiexplore-app": {SectionID: navtree.NavIDDrilldown, SortWeight: 2, Text: "Logs"},
+ "grafana-exploretraces-app": {SectionID: navtree.NavIDDrilldown, SortWeight: 3, Text: "Traces"},
+ "grafana-pyroscope-app": {SectionID: navtree.NavIDDrilldown, SortWeight: 4, Text: "Profiles"},
"grafana-kowalski-app": {SectionID: navtree.NavIDRoot, SortWeight: navtree.WeightFrontend, Text: "Frontend", Icon: "frontend-observability"},
"grafana-synthetic-monitoring-app": {SectionID: navtree.NavIDTestingAndSynthetics, SortWeight: 2, Text: "Synthetics"},
"grafana-irm-app": {SectionID: navtree.NavIDAlertsAndIncidents, SortWeight: 1, Text: "IRM"},
@@ -310,7 +310,7 @@ func (s *ServiceImpl) readNavigationSettings() {
}
if s.features.IsEnabledGlobally(featuremgmt.FlagExploreMetricsUseExternalAppPlugin) {
- s.navigationAppConfig["grafana-metricsdrilldown-app"] = NavigationAppConfig{SectionID: navtree.NavIDExplore, SortWeight: 1, Text: "Metrics"}
+ s.navigationAppConfig["grafana-metricsdrilldown-app"] = NavigationAppConfig{SectionID: navtree.NavIDDrilldown, SortWeight: 1, Text: "Metrics"}
}
if s.features.IsEnabledGlobally(featuremgmt.FlagGrafanaAdvisor) {
diff --git a/pkg/services/navtree/navtreeimpl/navtree.go b/pkg/services/navtree/navtreeimpl/navtree.go
index 1f461db67f3..7158c8884dc 100644
--- a/pkg/services/navtree/navtreeimpl/navtree.go
+++ b/pkg/services/navtree/navtreeimpl/navtree.go
@@ -120,7 +120,6 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere
}
if s.cfg.ExploreEnabled && hasAccess(ac.EvalPermission(ac.ActionDatasourcesExplore)) {
- exploreChildNavLinks := s.buildExploreNavLinks(c)
treeRoot.AddSection(&navtree.NavLink{
Text: "Explore",
Id: navtree.NavIDExplore,
@@ -128,7 +127,20 @@ func (s *ServiceImpl) GetNavTree(c *contextmodel.ReqContext, prefs *pref.Prefere
Icon: "compass",
SortWeight: navtree.WeightExplore,
Url: s.cfg.AppSubURL + "/explore",
- Children: exploreChildNavLinks,
+ })
+ }
+
+ if hasAccess(ac.EvalPermission(ac.ActionDatasourcesDrilldown)) {
+ drilldownChildNavLinks := s.buildDrilldownNavLinks(c)
+ treeRoot.AddSection(&navtree.NavLink{
+ Text: "Drilldown",
+ Id: navtree.NavIDDrilldown,
+ SubTitle: "Drill down into your data using Grafana's powerful queryless apps",
+ Icon: "drilldown",
+ IsNew: true,
+ SortWeight: navtree.WeightDrilldown,
+ Url: s.cfg.AppSubURL + "/drilldown",
+ Children: drilldownChildNavLinks,
})
}
@@ -560,10 +572,10 @@ func (s *ServiceImpl) buildDataConnectionsNavLink(c *contextmodel.ReqContext) *n
return nil
}
-func (s *ServiceImpl) buildExploreNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
- exploreChildNavs := []*navtree.NavLink{}
+func (s *ServiceImpl) buildDrilldownNavLinks(c *contextmodel.ReqContext) []*navtree.NavLink {
+ drilldownChildNavs := []*navtree.NavLink{}
if s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetrics) && !s.features.IsEnabled(c.Req.Context(), featuremgmt.FlagExploreMetricsUseExternalAppPlugin) {
- exploreChildNavs = append(exploreChildNavs, &navtree.NavLink{
+ drilldownChildNavs = append(drilldownChildNavs, &navtree.NavLink{
Text: "Metrics",
SubTitle: "Queryless exploration of your metrics",
Id: "explore/metrics",
@@ -571,5 +583,5 @@ func (s *ServiceImpl) buildExploreNavLinks(c *contextmodel.ReqContext) []*navtre
Icon: "code-branch",
})
}
- return exploreChildNavs
+ return drilldownChildNavs
}
diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx
index ce8b9a53d40..4379283f3c5 100644
--- a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx
+++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItem.tsx
@@ -5,8 +5,9 @@ import { useLocation } from 'react-router-dom-v5-compat';
import { useLocalStorage } from 'react-use';
import { GrafanaTheme2, NavModelItem, toIconName } from '@grafana/data';
-import { useStyles2, Text, IconButton, Icon, Stack } from '@grafana/ui';
+import { useStyles2, Text, IconButton, Icon, Stack, Badge } from '@grafana/ui';
import { useGrafana } from 'app/core/context/GrafanaContext';
+import { t } from 'app/core/internationalization';
import { Indent } from '../../Indent/Indent';
@@ -35,7 +36,7 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick, onPin, isPi
const isActive = link === activeItem || (level === MAX_DEPTH && hasActiveChild);
const [sectionExpanded, setSectionExpanded] = useLocalStorage(
`grafana.navigation.expanded[${link.text}]`,
- Boolean(hasActiveChild)
+ Boolean(hasActiveChild || link.isNew)
);
const showExpandButton = level < MAX_DEPTH && Boolean(linkHasChildren(link) || link.emptyMessage);
const item = useRef(null);
@@ -107,6 +108,7 @@ export function MegaMenuItem({ link, activeItem, level = 0, onClick, onPin, isPi
>
{level === 0 && iconElement && {iconElement}}
{link.text}
+ {link.isNew && }
diff --git a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx
index 471450c7d23..b6d18b50d9d 100644
--- a/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx
+++ b/public/app/core/components/AppChrome/MegaMenu/MegaMenuItemText.tsx
@@ -114,7 +114,7 @@ const getStyles = (theme: GrafanaTheme2, isActive: Props['isActive']) => ({
position: 'relative',
width: '100%',
- '&:hover, &:focus-visible': {
+ '&:hover span, &:focus-visible span': {
color: theme.colors.text.primary,
textDecoration: 'underline',
},
diff --git a/public/app/core/utils/navBarItem-translations.ts b/public/app/core/utils/navBarItem-translations.ts
index 993a914d2c7..eb533d88b07 100644
--- a/public/app/core/utils/navBarItem-translations.ts
+++ b/public/app/core/utils/navBarItem-translations.ts
@@ -61,6 +61,8 @@ export function getNavTitle(navId: string | undefined) {
return t('nav.scenes.title', 'Scenes');
case 'explore':
return t('nav.explore.title', 'Explore');
+ case 'drilldown':
+ return t('nav.drilldown.title', 'Drilldown');
case 'alerting':
return t('nav.alerting.title', 'Alerting');
case 'plugin-page-grafana-oncall-app':
diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx
index 28ad370648e..4927fc5be4a 100644
--- a/public/app/features/explore/Explore.tsx
+++ b/public/app/features/explore/Explore.tsx
@@ -23,14 +23,18 @@ import { getDataSourceSrv, reportInteraction } from '@grafana/runtime';
import { DataQuery } from '@grafana/schema';
import {
AdHocFilterItem,
+ Alert,
ErrorBoundaryAlert,
+ LinkButton,
PanelContainer,
ScrollContainer,
+ Stack,
Themeable2,
withTheme2,
} from '@grafana/ui';
import { FILTER_FOR_OPERATOR, FILTER_OUT_OPERATOR } from '@grafana/ui/src/components/Table/types';
import { supportedFeatures } from 'app/core/history/richHistoryStorageProvider';
+import { t, Trans } from 'app/core/internationalization';
import { MIXED_DATASOURCE_NAME } from 'app/plugins/datasource/mixed/MixedDataSource';
import { StoreState } from 'app/types';
@@ -564,6 +568,9 @@ export class Explore extends PureComponent {
if (showCorrelationHelper && correlationEditorHelperData !== undefined) {
correlationsBox = ;
}
+ const isDsCompatibleWithDrilldown = ['prometheus', 'loki', 'tempo', 'grafana-pyroscope-datasource'].includes(
+ datasourceInstance?.type || ''
+ );
return (
@@ -593,6 +600,27 @@ export class Explore extends PureComponent {
<>
+ {isDsCompatibleWithDrilldown && (
+
+
+
+
+ Looking for the Grafana Explore apps? They are now called the Grafana Drilldown apps
+ and can be found under Menu > Drilldown
+
+
+
+ Go to Grafana Drilldown
+
+
+
+ )}
{correlationsBox}
,
+ },
{
path: '/apps',
component: () => ,
diff --git a/public/img/icons/unicons/drilldown.svg b/public/img/icons/unicons/drilldown.svg
new file mode 100644
index 00000000000..27f627ce19f
--- /dev/null
+++ b/public/img/icons/unicons/drilldown.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json
index e80a59d9129..d7cba3f8990 100644
--- a/public/locales/en-US/grafana.json
+++ b/public/locales/en-US/grafana.json
@@ -1419,6 +1419,11 @@
},
"explore": {
"add-to-dashboard": "Add to dashboard",
+ "drilldownInfo": {
+ "action": "Go to Grafana Drilldown",
+ "description": "Looking for the Grafana Explore apps? They are now called the Grafana Drilldown apps and can be found under <1>Menu > Drilldown1>",
+ "title": "Explore Metrics, Logs, Traces and Profiles have moved!"
+ },
"logs": {
"logs-volume": {
"add-filters": "Add more labels to your query to narrow down your search.",
@@ -2499,6 +2504,9 @@
"detect": {
"title": "Detect"
},
+ "drilldown": {
+ "title": "Drilldown"
+ },
"explore": {
"title": "Explore"
},
@@ -2714,6 +2722,7 @@
"close": "Close menu",
"dock": "Dock menu",
"list-label": "Navigation",
+ "new": "New!",
"open": "Open menu",
"undock": "Undock menu"
},
diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json
index 43096c5ce25..2dc0f928c44 100644
--- a/public/locales/pseudo-LOCALE/grafana.json
+++ b/public/locales/pseudo-LOCALE/grafana.json
@@ -1419,6 +1419,11 @@
},
"explore": {
"add-to-dashboard": "Åđđ ŧő đäşĥþőäřđ",
+ "drilldownInfo": {
+ "action": "Ğő ŧő Ğřäƒäʼnä Đřįľľđőŵʼn",
+ "description": "Ŀőőĸįʼnģ ƒőř ŧĥę Ğřäƒäʼnä Ēχpľőřę äppş? Ŧĥęy äřę ʼnőŵ čäľľęđ ŧĥę Ğřäƒäʼnä Đřįľľđőŵʼn äppş äʼnđ čäʼn þę ƒőūʼnđ ūʼnđęř <1>Męʼnū > Đřįľľđőŵʼn1>",
+ "title": "Ēχpľőřę Męŧřįčş, Ŀőģş, Ŧřäčęş äʼnđ Přőƒįľęş ĥävę mővęđ!"
+ },
"logs": {
"logs-volume": {
"add-filters": "Åđđ mőřę ľäþęľş ŧő yőūř qūęřy ŧő ʼnäřřőŵ đőŵʼn yőūř şęäřčĥ.",
@@ -2499,6 +2504,9 @@
"detect": {
"title": "Đęŧęčŧ"
},
+ "drilldown": {
+ "title": "Đřįľľđőŵʼn"
+ },
"explore": {
"title": "Ēχpľőřę"
},
@@ -2714,6 +2722,7 @@
"close": "Cľőşę męʼnū",
"dock": "Đőčĸ męʼnū",
"list-label": "Ńävįģäŧįőʼn",
+ "new": "Ńęŵ!",
"open": "Øpęʼn męʼnū",
"undock": "Ůʼnđőčĸ męʼnū"
},